ghelp.py 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300
  1. """!
  2. @package gui_core.ghelp
  3. @brief Help window
  4. Classes:
  5. - SearchModuleWindow
  6. - ItemTree
  7. - MenuTreeWindow
  8. - MenuTree
  9. - AboutWindow
  10. - InstallExtensionWindow
  11. - ExtensionTree
  12. - HelpFrame
  13. - HelpWindow
  14. - HelpPanel
  15. (C) 2008-2011 by the GRASS Development Team
  16. This program is free software under the GNU General Public License
  17. (>=v2). Read the file COPYING that comes with GRASS for details.
  18. @author Martin Landa <landa.martin gmail.com>
  19. """
  20. import os
  21. import codecs
  22. import wx
  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 wx.lib.scrolledpanel as scrolled
  31. import grass.script as grass
  32. from grass.script import task as gtask
  33. from core import globalvar
  34. from core import utils
  35. from lmgr.menudata import ManagerData
  36. from core.gcmd import GError, RunCommand, DecodeString
  37. from gui_core.widgets import GNotebook
  38. from core.forms import GUI
  39. from gui_core.dialogs import StaticWrapText
  40. class HelpFrame(wx.Frame):
  41. """!GRASS Quickstart help window"""
  42. def __init__(self, parent, id, title, size, file):
  43. wx.Frame.__init__(self, parent = parent, id = id, title = title, size = size)
  44. sizer = wx.BoxSizer(wx.VERTICAL)
  45. # text
  46. content = HelpPanel(parent = self)
  47. content.LoadPage(file)
  48. sizer.Add(item = content, proportion = 1, flag = wx.EXPAND)
  49. self.SetAutoLayout(True)
  50. self.SetSizer(sizer)
  51. self.Layout()
  52. class SearchModuleWindow(wx.Panel):
  53. """!Search module window (used in MenuTreeWindow)"""
  54. def __init__(self, parent, id = wx.ID_ANY, cmdPrompt = None,
  55. showChoice = True, showTip = False, **kwargs):
  56. self.showTip = showTip
  57. self.showChoice = showChoice
  58. self.cmdPrompt = cmdPrompt
  59. wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
  60. self._searchDict = { _('description') : 'description',
  61. _('command') : 'command',
  62. _('keywords') : 'keywords' }
  63. self.box = wx.StaticBox(parent = self, id = wx.ID_ANY,
  64. label = " %s " % _("Find module(s)"))
  65. self.searchBy = wx.Choice(parent = self, id = wx.ID_ANY,
  66. choices = [_('description'),
  67. _('keywords'),
  68. _('command')])
  69. self.searchBy.SetSelection(0)
  70. self.search = wx.TextCtrl(parent = self, id = wx.ID_ANY,
  71. value = "", size = (-1, 25),
  72. style = wx.TE_PROCESS_ENTER)
  73. self.search.Bind(wx.EVT_TEXT, self.OnSearchModule)
  74. if self.showTip:
  75. self.searchTip = StaticWrapText(parent = self, id = wx.ID_ANY,
  76. size = (-1, 35))
  77. if self.showChoice:
  78. self.searchChoice = wx.Choice(parent = self, id = wx.ID_ANY)
  79. if self.cmdPrompt:
  80. self.searchChoice.SetItems(self.cmdPrompt.GetCommandItems())
  81. self.searchChoice.Bind(wx.EVT_CHOICE, self.OnSelectModule)
  82. self._layout()
  83. def _layout(self):
  84. """!Do layout"""
  85. sizer = wx.StaticBoxSizer(self.box, wx.HORIZONTAL)
  86. gridSizer = wx.GridBagSizer(hgap = 3, vgap = 3)
  87. gridSizer.AddGrowableCol(1)
  88. gridSizer.Add(item = self.searchBy,
  89. flag = wx.ALIGN_CENTER_VERTICAL, pos = (0, 0))
  90. gridSizer.Add(item = self.search,
  91. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (0, 1))
  92. row = 1
  93. if self.showTip:
  94. gridSizer.Add(item = self.searchTip,
  95. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
  96. row += 1
  97. if self.showChoice:
  98. gridSizer.Add(item = self.searchChoice,
  99. flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
  100. sizer.Add(item = gridSizer, proportion = 1)
  101. self.SetSizer(sizer)
  102. sizer.Fit(self)
  103. def GetSelection(self):
  104. """!Get selected element"""
  105. selection = self.searchBy.GetStringSelection()
  106. return self._searchDict[selection]
  107. def SetSelection(self, i):
  108. """!Set selection element"""
  109. self.searchBy.SetSelection(i)
  110. def OnSearchModule(self, event):
  111. """!Search module by keywords or description"""
  112. if not self.cmdPrompt:
  113. event.Skip()
  114. return
  115. text = event.GetString()
  116. if not text:
  117. self.cmdPrompt.SetFilter(None)
  118. mList = self.cmdPrompt.GetCommandItems()
  119. self.searchChoice.SetItems(mList)
  120. if self.showTip:
  121. self.searchTip.SetLabel(_("%d modules found") % len(mList))
  122. event.Skip()
  123. return
  124. modules = dict()
  125. iFound = 0
  126. for module, data in self.cmdPrompt.moduleDesc.iteritems():
  127. found = False
  128. sel = self.searchBy.GetSelection()
  129. if sel == 0: # -> description
  130. if text in data['desc']:
  131. found = True
  132. elif sel == 1: # keywords
  133. if text in ','.join(data['keywords']):
  134. found = True
  135. else: # command
  136. if module[:len(text)] == text:
  137. found = True
  138. if found:
  139. iFound += 1
  140. try:
  141. group, name = module.split('.')
  142. except ValueError:
  143. continue # TODO
  144. if group not in modules:
  145. modules[group] = list()
  146. modules[group].append(name)
  147. self.cmdPrompt.SetFilter(modules)
  148. self.searchChoice.SetItems(self.cmdPrompt.GetCommandItems())
  149. if self.showTip:
  150. self.searchTip.SetLabel(_("%d modules found") % iFound)
  151. event.Skip()
  152. def OnSelectModule(self, event):
  153. """!Module selected from choice, update command prompt"""
  154. cmd = event.GetString().split(' ', 1)[0]
  155. text = cmd + ' '
  156. pos = len(text)
  157. if self.cmdPrompt:
  158. self.cmdPrompt.SetText(text)
  159. self.cmdPrompt.SetSelectionStart(pos)
  160. self.cmdPrompt.SetCurrentPos(pos)
  161. self.cmdPrompt.SetFocus()
  162. desc = self.cmdPrompt.GetCommandDesc(cmd)
  163. if self.showTip:
  164. self.searchTip.SetLabel(desc)
  165. def Reset(self):
  166. """!Reset widget"""
  167. self.searchBy.SetSelection(0)
  168. self.search.SetValue('')
  169. if self.showTip:
  170. self.searchTip.SetLabel('')
  171. class MenuTreeWindow(wx.Panel):
  172. """!Show menu tree"""
  173. def __init__(self, parent, id = wx.ID_ANY, **kwargs):
  174. self.parent = parent # LayerManager
  175. wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
  176. self.dataBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
  177. label = " %s " % _("Menu tree (double-click to run command)"))
  178. # tree
  179. self.tree = MenuTree(parent = self, data = ManagerData())
  180. self.tree.Load()
  181. # search widget
  182. self.search = SearchModuleWindow(parent = self, showChoice = False)
  183. # buttons
  184. self.btnRun = wx.Button(self, id = wx.ID_OK, label = _("&Run"))
  185. self.btnRun.SetToolTipString(_("Run selected command"))
  186. self.btnRun.Enable(False)
  187. # bindings
  188. self.btnRun.Bind(wx.EVT_BUTTON, self.OnRun)
  189. self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
  190. self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnItemSelected)
  191. self.search.Bind(wx.EVT_TEXT_ENTER, self.OnShowItem)
  192. self.search.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
  193. self._layout()
  194. self.search.SetFocus()
  195. def _layout(self):
  196. """!Do dialog layout"""
  197. sizer = wx.BoxSizer(wx.VERTICAL)
  198. # body
  199. dataSizer = wx.StaticBoxSizer(self.dataBox, wx.HORIZONTAL)
  200. dataSizer.Add(item = self.tree, proportion =1,
  201. flag = wx.EXPAND)
  202. # buttons
  203. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  204. btnSizer.Add(item = self.btnRun, proportion = 0)
  205. sizer.Add(item = dataSizer, proportion = 1,
  206. flag = wx.EXPAND | wx.ALL, border = 5)
  207. sizer.Add(item = self.search, proportion = 0,
  208. flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
  209. sizer.Add(item = btnSizer, proportion = 0,
  210. flag = wx.ALIGN_RIGHT | wx.BOTTOM | wx.RIGHT, border = 5)
  211. sizer.Fit(self)
  212. sizer.SetSizeHints(self)
  213. self.SetSizer(sizer)
  214. self.Fit()
  215. self.SetAutoLayout(True)
  216. self.Layout()
  217. def OnCloseWindow(self, event):
  218. """!Close window"""
  219. self.Destroy()
  220. def OnRun(self, event):
  221. """!Run selected command"""
  222. if not self.tree.GetSelected():
  223. return # should not happen
  224. data = self.tree.GetPyData(self.tree.GetSelected())
  225. if not data:
  226. return
  227. handler = 'self.parent.' + data['handler'].lstrip('self.')
  228. if data['handler'] == 'self.OnXTerm':
  229. wx.MessageBox(parent = self,
  230. message = _('You must run this command from the menu or command line',
  231. 'This command require an XTerm'),
  232. caption = _('Message'), style = wx.OK | wx.ICON_ERROR | wx.CENTRE)
  233. elif data['command']:
  234. eval(handler)(event = None, cmd = data['command'].split())
  235. else:
  236. eval(handler)(None)
  237. def OnShowItem(self, event):
  238. """!Show selected item"""
  239. self.tree.OnShowItem(event)
  240. if self.tree.GetSelected():
  241. self.btnRun.Enable()
  242. else:
  243. self.btnRun.Enable(False)
  244. def OnItemActivated(self, event):
  245. """!Item activated (double-click)"""
  246. item = event.GetItem()
  247. if not item or not item.IsOk():
  248. return
  249. data = self.tree.GetPyData(item)
  250. if not data or 'command' not in data:
  251. return
  252. self.tree.itemSelected = item
  253. self.OnRun(None)
  254. def OnItemSelected(self, event):
  255. """!Item selected"""
  256. item = event.GetItem()
  257. if not item or not item.IsOk():
  258. return
  259. data = self.tree.GetPyData(item)
  260. if not data or 'command' not in data:
  261. return
  262. if data['command']:
  263. label = data['command'] + ' -- ' + data['description']
  264. else:
  265. label = data['description']
  266. self.parent.SetStatusText(label, 0)
  267. def OnUpdateStatusBar(self, event):
  268. """!Update statusbar text"""
  269. element = self.search.GetSelection()
  270. self.tree.SearchItems(element = element,
  271. value = event.GetString())
  272. nItems = len(self.tree.itemsMarked)
  273. if event.GetString():
  274. self.parent.SetStatusText(_("%d modules match") % nItems, 0)
  275. else:
  276. self.parent.SetStatusText("", 0)
  277. event.Skip()
  278. class ItemTree(CT.CustomTreeCtrl):
  279. def __init__(self, parent, id = wx.ID_ANY,
  280. ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
  281. CT.TR_LINES_AT_ROOT | CT.TR_SINGLE, **kwargs):
  282. if globalvar.hasAgw:
  283. super(ItemTree, self).__init__(parent, id, agwStyle = ctstyle, **kwargs)
  284. else:
  285. super(ItemTree, self).__init__(parent, id, style = ctstyle, **kwargs)
  286. self.root = self.AddRoot(_("Menu tree"))
  287. self.itemsMarked = [] # list of marked items
  288. self.itemSelected = None
  289. def SearchItems(self, element, value):
  290. """!Search item
  291. @param element element index (see self.searchBy)
  292. @param value
  293. @return list of found tree items
  294. """
  295. items = list()
  296. if not value:
  297. return items
  298. item = self.GetFirstChild(self.root)[0]
  299. self._processItem(item, element, value, items)
  300. self.itemsMarked = items
  301. self.itemSelected = None
  302. return items
  303. def _processItem(self, item, element, value, listOfItems):
  304. """!Search items (used by SearchItems)
  305. @param item reference item
  306. @param listOfItems list of found items
  307. """
  308. while item and item.IsOk():
  309. subItem = self.GetFirstChild(item)[0]
  310. if subItem:
  311. self._processItem(subItem, element, value, listOfItems)
  312. data = self.GetPyData(item)
  313. if data and element in data and \
  314. value.lower() in data[element].lower():
  315. listOfItems.append(item)
  316. item = self.GetNextSibling(item)
  317. def GetSelected(self):
  318. """!Get selected item"""
  319. return self.itemSelected
  320. def OnShowItem(self, event):
  321. """!Highlight first found item in menu tree"""
  322. if len(self.itemsMarked) > 0:
  323. if self.GetSelected():
  324. self.ToggleItemSelection(self.GetSelected())
  325. idx = self.itemsMarked.index(self.GetSelected()) + 1
  326. else:
  327. idx = 0
  328. try:
  329. self.ToggleItemSelection(self.itemsMarked[idx])
  330. self.itemSelected = self.itemsMarked[idx]
  331. self.EnsureVisible(self.itemsMarked[idx])
  332. except IndexError:
  333. self.ToggleItemSelection(self.itemsMarked[0]) # reselect first item
  334. self.EnsureVisible(self.itemsMarked[0])
  335. self.itemSelected = self.itemsMarked[0]
  336. else:
  337. for item in self.root.GetChildren():
  338. self.Collapse(item)
  339. itemSelected = self.GetSelection()
  340. if itemSelected:
  341. self.ToggleItemSelection(itemSelected)
  342. self.itemSelected = None
  343. class MenuTree(ItemTree):
  344. """!Menu tree class"""
  345. def __init__(self, parent, data, **kwargs):
  346. self.parent = parent
  347. self.menudata = data
  348. super(MenuTree, self).__init__(parent, **kwargs)
  349. def Load(self, data = None):
  350. """!Load menu data tree
  351. @param data menu data (None to use self.menudata)
  352. """
  353. if not data:
  354. data = self.menudata
  355. self.itemsMarked = [] # list of marked items
  356. for eachMenuData in data.GetMenu():
  357. for label, items in eachMenuData:
  358. item = self.AppendItem(parentId = self.root,
  359. text = label.replace('&', ''))
  360. self.__AppendItems(item, items)
  361. def __AppendItems(self, item, data):
  362. """!Append items into tree (used by Load()
  363. @param item tree item (parent)
  364. @parent data menu data"""
  365. for eachItem in data:
  366. if len(eachItem) == 2:
  367. if eachItem[0]:
  368. itemSub = self.AppendItem(parentId = item,
  369. text = eachItem[0])
  370. self.__AppendItems(itemSub, eachItem[1])
  371. else:
  372. if eachItem[0]:
  373. itemNew = self.AppendItem(parentId = item,
  374. text = eachItem[0])
  375. data = { 'item' : eachItem[0],
  376. 'description' : eachItem[1],
  377. 'handler' : eachItem[2],
  378. 'command' : eachItem[3],
  379. 'keywords' : eachItem[4] }
  380. self.SetPyData(itemNew, data)
  381. class AboutWindow(wx.Frame):
  382. """!Create custom About Window
  383. @todo improve styling
  384. """
  385. def __init__(self, parent, size = (750, 400),
  386. title = _('About GRASS GIS'), **kwargs):
  387. wx.Frame.__init__(self, parent = parent, id = wx.ID_ANY, size = size, **kwargs)
  388. panel = wx.Panel(parent = self, id = wx.ID_ANY)
  389. # icon
  390. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  391. # get version and web site
  392. vInfo = grass.version()
  393. infoTxt = wx.Panel(parent = panel, id = wx.ID_ANY)
  394. infoSizer = wx.BoxSizer(wx.VERTICAL)
  395. infoGridSizer = wx.GridBagSizer(vgap = 5, hgap = 5)
  396. infoGridSizer.AddGrowableCol(0)
  397. infoGridSizer.AddGrowableCol(1)
  398. logo = os.path.join(globalvar.ETCDIR, "gui", "icons", "grass.ico")
  399. logoBitmap = wx.StaticBitmap(parent = infoTxt, id = wx.ID_ANY,
  400. bitmap = wx.Bitmap(name = logo,
  401. type = wx.BITMAP_TYPE_ICO))
  402. infoSizer.Add(item = logoBitmap, proportion = 0,
  403. flag = wx.ALL | wx.ALIGN_CENTER, border = 25)
  404. info = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  405. label = 'GRASS GIS ' + vInfo['version'] + '\n\n')
  406. info.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
  407. infoSizer.Add(item = info, proportion = 0,
  408. flag = wx.BOTTOM | wx.ALIGN_CENTER, border = 15)
  409. row = 0
  410. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  411. label = _('Official GRASS site:')),
  412. pos = (row, 0),
  413. flag = wx.ALIGN_RIGHT)
  414. infoGridSizer.Add(item = HyperLinkCtrl(parent = infoTxt, id = wx.ID_ANY,
  415. label = 'http://grass.osgeo.org'),
  416. pos = (row, 1),
  417. flag = wx.ALIGN_LEFT)
  418. row += 2
  419. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  420. label = _('SVN Revision:')),
  421. pos = (row, 0),
  422. flag = wx.ALIGN_RIGHT)
  423. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  424. label = vInfo['revision']),
  425. pos = (row, 1),
  426. flag = wx.ALIGN_LEFT)
  427. row += 1
  428. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  429. label = _('GIS Library Revision:')),
  430. pos = (row, 0),
  431. flag = wx.ALIGN_RIGHT)
  432. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  433. label = vInfo['libgis_revision'] + ' (' +
  434. vInfo['libgis_date'].split(' ')[0] + ')'),
  435. pos = (row, 1),
  436. flag = wx.ALIGN_LEFT)
  437. infoSizer.Add(item = infoGridSizer,
  438. proportion = 1,
  439. flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL,
  440. border = 25)
  441. # create a flat notebook for displaying information about GRASS
  442. aboutNotebook = GNotebook(panel, style = globalvar.FNPageStyle | FN.FNB_NO_X_BUTTON)
  443. aboutNotebook.SetTabAreaColour(globalvar.FNPageColor)
  444. for title, win in ((_("Info"), infoTxt),
  445. (_("Copyright"), self._pageCopyright()),
  446. (_("License"), self._pageLicense()),
  447. (_("Authors"), self._pageCredit()),
  448. (_("Contributors"), self._pageContributors()),
  449. (_("Extra contributors"), self._pageContributors(extra = True)),
  450. (_("Translators"), self._pageTranslators())):
  451. aboutNotebook.AddPage(page = win, text = title)
  452. wx.CallAfter(aboutNotebook.SetSelection, 0)
  453. # buttons
  454. btnClose = wx.Button(parent = panel, id = wx.ID_CLOSE)
  455. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  456. btnSizer.Add(item = btnClose, proportion = 0,
  457. flag = wx.ALL | wx.ALIGN_RIGHT,
  458. border = 5)
  459. # bindings
  460. btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
  461. infoTxt.SetSizer(infoSizer)
  462. infoSizer.Fit(infoTxt)
  463. sizer = wx.BoxSizer(wx.VERTICAL)
  464. sizer.Add(item = aboutNotebook, proportion = 1,
  465. flag = wx.EXPAND | wx.ALL, border = 1)
  466. sizer.Add(item = btnSizer, proportion = 0,
  467. flag = wx.ALL | wx.ALIGN_RIGHT, border = 1)
  468. panel.SetSizer(sizer)
  469. self.Layout()
  470. def _pageCopyright(self):
  471. """Copyright information"""
  472. copyfile = os.path.join(os.getenv("GISBASE"), "COPYING")
  473. if os.path.exists(copyfile):
  474. copyrightFile = open(copyfile, 'r')
  475. copytext = copyrightFile.read()
  476. copyrightFile.close()
  477. else:
  478. copytext = _('%s file missing') % 'COPYING'
  479. # put text into a scrolling panel
  480. copyrightwin = scrolled.ScrolledPanel(self, id = wx.ID_ANY,
  481. size = wx.DefaultSize,
  482. style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
  483. copyrighttxt = wx.StaticText(copyrightwin, id = wx.ID_ANY, label = copytext)
  484. copyrightwin.SetAutoLayout(True)
  485. copyrightwin.sizer = wx.BoxSizer(wx.VERTICAL)
  486. copyrightwin.sizer.Add(item = copyrighttxt, proportion = 1,
  487. flag = wx.EXPAND | wx.ALL, border = 3)
  488. copyrightwin.SetSizer(copyrightwin.sizer)
  489. copyrightwin.Layout()
  490. copyrightwin.SetupScrolling()
  491. return copyrightwin
  492. def _pageLicense(self):
  493. """Licence about"""
  494. licfile = os.path.join(os.getenv("GISBASE"), "GPL.TXT")
  495. if os.path.exists(licfile):
  496. licenceFile = open(licfile, 'r')
  497. license = ''.join(licenceFile.readlines())
  498. licenceFile.close()
  499. else:
  500. license = _('%s file missing') % 'GPL.TXT'
  501. # put text into a scrolling panel
  502. licensewin = scrolled.ScrolledPanel(self, id = wx.ID_ANY,
  503. style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
  504. licensetxt = wx.StaticText(licensewin, id = wx.ID_ANY, label = license)
  505. licensewin.SetAutoLayout(True)
  506. licensewin.sizer = wx.BoxSizer(wx.VERTICAL)
  507. licensewin.sizer.Add(item = licensetxt, proportion = 1,
  508. flag = wx.EXPAND | wx.ALL, border = 3)
  509. licensewin.SetSizer(licensewin.sizer)
  510. licensewin.Layout()
  511. licensewin.SetupScrolling()
  512. return licensewin
  513. def _pageCredit(self):
  514. """Credit about"""
  515. # credits
  516. authfile = os.path.join(os.getenv("GISBASE"), "AUTHORS")
  517. if os.path.exists(authfile):
  518. authorsFile = open(authfile, 'r')
  519. authors = unicode(''.join(authorsFile.readlines()), "utf-8")
  520. authorsFile.close()
  521. else:
  522. authors = _('%s file missing') % 'AUTHORS'
  523. authorwin = scrolled.ScrolledPanel(self, id = wx.ID_ANY,
  524. style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
  525. authortxt = wx.StaticText(authorwin, id = wx.ID_ANY, label = authors)
  526. authorwin.SetAutoLayout(1)
  527. authorwin.SetupScrolling()
  528. authorwin.sizer = wx.BoxSizer(wx.VERTICAL)
  529. authorwin.sizer.Add(item = authortxt, proportion = 1,
  530. flag = wx.EXPAND | wx.ALL, border = 3)
  531. authorwin.SetSizer(authorwin.sizer)
  532. authorwin.Layout()
  533. return authorwin
  534. def _pageContributors(self, extra = False):
  535. """Contributors info"""
  536. if extra:
  537. contribfile = os.path.join(os.getenv("GISBASE"), "contributors_extra.csv")
  538. else:
  539. contribfile = os.path.join(os.getenv("GISBASE"), "contributors.csv")
  540. if os.path.exists(contribfile):
  541. contribFile = codecs.open(contribfile, encoding = 'utf-8', mode = 'r')
  542. contribs = list()
  543. errLines = list()
  544. for line in contribFile.readlines()[1:]:
  545. line = line.rstrip('\n')
  546. try:
  547. if extra:
  548. name, email, rfc2_agreed = line.split(',')
  549. else:
  550. cvs_id, name, email, country, osgeo_id, rfc2_agreed = line.split(',')
  551. except ValueError:
  552. errLines.append(line)
  553. continue
  554. if extra:
  555. contribs.append((name, email))
  556. else:
  557. contribs.append((name, email, country, osgeo_id))
  558. contribFile.close()
  559. if errLines:
  560. GError(parent = self,
  561. message = _("Error when reading file '%s'.") % contribfile + \
  562. "\n\n" + _("Lines:") + " %s" % \
  563. os.linesep.join(map(utils.DecodeString, errLines)))
  564. else:
  565. contribs = None
  566. contribwin = scrolled.ScrolledPanel(self, id = wx.ID_ANY,
  567. style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
  568. contribwin.SetAutoLayout(True)
  569. contribwin.SetupScrolling()
  570. contribwin.sizer = wx.BoxSizer(wx.VERTICAL)
  571. if not contribs:
  572. contribtxt = wx.StaticText(contribwin, id = wx.ID_ANY,
  573. label = _('%s file missing') % contribfile)
  574. contribwin.sizer.Add(item = contribtxt, proportion = 1,
  575. flag = wx.EXPAND | wx.ALL, border = 3)
  576. else:
  577. if extra:
  578. items = (_('Name'), _('E-mail'))
  579. else:
  580. items = (_('Name'), _('E-mail'), _('Country'), _('OSGeo_ID'))
  581. contribBox = wx.FlexGridSizer(cols = len(items), vgap = 5, hgap = 5)
  582. for item in items:
  583. contribBox.Add(item = wx.StaticText(parent = contribwin, id = wx.ID_ANY,
  584. label = item))
  585. for vals in sorted(contribs, key = lambda x: x[0]):
  586. for item in vals:
  587. contribBox.Add(item = wx.StaticText(parent = contribwin, id = wx.ID_ANY,
  588. label = item))
  589. contribwin.sizer.Add(item = contribBox, proportion = 1,
  590. flag = wx.EXPAND | wx.ALL, border = 3)
  591. contribwin.SetSizer(contribwin.sizer)
  592. contribwin.Layout()
  593. return contribwin
  594. def _pageTranslators(self):
  595. """Translators info"""
  596. translatorsfile = os.path.join(os.getenv("GISBASE"), "translators.csv")
  597. if os.path.exists(translatorsfile):
  598. translatorsFile = open(translatorsfile, 'r')
  599. translators = dict()
  600. errLines = list()
  601. for line in translatorsFile.readlines()[1:]:
  602. line = line.rstrip('\n')
  603. try:
  604. name, email, languages = line.split(',')
  605. except ValueError:
  606. errLines.append(line)
  607. continue
  608. for language in languages.split(' '):
  609. if language not in translators:
  610. translators[language] = list()
  611. translators[language].append((name, email))
  612. translatorsFile.close()
  613. if errLines:
  614. GError(parent = self,
  615. message = _("Error when reading file '%s'.") % translatorsfile + \
  616. "\n\n" + _("Lines:") + " %s" % \
  617. os.linesep.join(map(utils.DecodeString, errLines)))
  618. else:
  619. translators = None
  620. translatorswin = scrolled.ScrolledPanel(self, id = wx.ID_ANY,
  621. style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
  622. translatorswin.SetAutoLayout(1)
  623. translatorswin.SetupScrolling()
  624. translatorswin.sizer = wx.BoxSizer(wx.VERTICAL)
  625. if not translators:
  626. translatorstxt = wx.StaticText(translatorswin, id = wx.ID_ANY,
  627. label = _('%s file missing') % 'translators.csv')
  628. translatorswin.sizer.Add(item = translatorstxt, proportion = 1,
  629. flag = wx.EXPAND | wx.ALL, border = 3)
  630. else:
  631. translatorsBox = wx.FlexGridSizer(cols = 3, vgap = 5, hgap = 5)
  632. languages = translators.keys()
  633. languages.sort()
  634. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  635. label = _('Name')))
  636. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  637. label = _('E-mail')))
  638. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  639. label = _('Language')))
  640. for lang in languages:
  641. for translator in translators[lang]:
  642. name, email = translator
  643. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  644. label = unicode(name, "utf-8")))
  645. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  646. label = email))
  647. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  648. label = lang))
  649. translatorswin.sizer.Add(item = translatorsBox, proportion = 1,
  650. flag = wx.EXPAND | wx.ALL, border = 3)
  651. translatorswin.SetSizer(translatorswin.sizer)
  652. translatorswin.Layout()
  653. return translatorswin
  654. def OnCloseWindow(self, event):
  655. """!Close window"""
  656. self.Close()
  657. class InstallExtensionWindow(wx.Frame):
  658. def __init__(self, parent, id = wx.ID_ANY,
  659. title = _("Fetch & install extension from GRASS Addons"), **kwargs):
  660. self.parent = parent
  661. self.options = dict() # list of options
  662. wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
  663. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  664. self.repoBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  665. label = " %s " % _("Repository"))
  666. self.treeBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  667. label = " %s " % _("List of extensions"))
  668. self.repo = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY)
  669. self.fullDesc = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
  670. label = _("Fetch full info including description and keywords (takes time)"))
  671. self.fullDesc.SetValue(True)
  672. self.search = SearchModuleWindow(parent = self.panel)
  673. self.search.SetSelection(0)
  674. self.tree = ExtensionTree(parent = self.panel, log = parent.GetLogWindow())
  675. self.optionBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  676. label = " %s " % _("Options"))
  677. task = gtask.parse_interface('g.extension')
  678. for f in task.get_options()['flags']:
  679. name = f.get('name', '')
  680. desc = f.get('label', '')
  681. if not desc:
  682. desc = f.get('description', '')
  683. if not name and not desc:
  684. continue
  685. if name in ('l', 'c', 'g', 'quiet', 'verbose'):
  686. continue
  687. self.options[name] = wx.CheckBox(parent = self.panel, id = wx.ID_ANY,
  688. label = desc)
  689. self.repo.SetValue(task.get_param(value = 'svnurl').get('default',
  690. 'http://svn.osgeo.org/grass/grass-addons/grass7'))
  691. self.statusbar = self.CreateStatusBar(number = 1)
  692. self.btnFetch = wx.Button(parent = self.panel, id = wx.ID_ANY,
  693. label = _("&Fetch"))
  694. self.btnFetch.SetToolTipString(_("Fetch list of available modules from GRASS Addons SVN repository"))
  695. self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
  696. self.btnInstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
  697. label = _("&Install"))
  698. self.btnInstall.SetToolTipString(_("Install selected add-ons GRASS module"))
  699. self.btnInstall.Enable(False)
  700. self.btnCmd = wx.Button(parent = self.panel, id = wx.ID_ANY,
  701. label = _("Command dialog"))
  702. self.btnCmd.SetToolTipString(_('Open %s dialog') % 'g.extension')
  703. self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
  704. self.btnFetch.Bind(wx.EVT_BUTTON, self.OnFetch)
  705. self.btnInstall.Bind(wx.EVT_BUTTON, self.OnInstall)
  706. self.btnCmd.Bind(wx.EVT_BUTTON, self.OnCmdDialog)
  707. self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
  708. self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnItemSelected)
  709. self.search.Bind(wx.EVT_TEXT_ENTER, self.OnShowItem)
  710. self.search.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
  711. self._layout()
  712. def _layout(self):
  713. """!Do layout"""
  714. sizer = wx.BoxSizer(wx.VERTICAL)
  715. repoSizer = wx.StaticBoxSizer(self.repoBox, wx.VERTICAL)
  716. repo1Sizer = wx.BoxSizer(wx.HORIZONTAL)
  717. repo1Sizer.Add(item = self.repo, proportion = 1,
  718. flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
  719. repo1Sizer.Add(item = self.btnFetch, proportion = 0,
  720. flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
  721. repoSizer.Add(item = repo1Sizer,
  722. flag = wx.EXPAND)
  723. repoSizer.Add(item = self.fullDesc)
  724. findSizer = wx.BoxSizer(wx.HORIZONTAL)
  725. findSizer.Add(item = self.search, proportion = 1)
  726. treeSizer = wx.StaticBoxSizer(self.treeBox, wx.HORIZONTAL)
  727. treeSizer.Add(item = self.tree, proportion = 1,
  728. flag = wx.ALL | wx.EXPAND, border = 1)
  729. # options
  730. optionSizer = wx.StaticBoxSizer(self.optionBox, wx.VERTICAL)
  731. for key in self.options.keys():
  732. optionSizer.Add(item = self.options[key], proportion = 0)
  733. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  734. btnSizer.Add(item = self.btnCmd, proportion = 0,
  735. flag = wx.RIGHT, border = 5)
  736. btnSizer.AddSpacer(10)
  737. btnSizer.Add(item = self.btnClose, proportion = 0,
  738. flag = wx.RIGHT, border = 5)
  739. btnSizer.Add(item = self.btnInstall, proportion = 0)
  740. sizer.Add(item = repoSizer, proportion = 0,
  741. flag = wx.ALL | wx.EXPAND, border = 3)
  742. sizer.Add(item = findSizer, proportion = 0,
  743. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  744. sizer.Add(item = treeSizer, proportion = 1,
  745. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  746. sizer.Add(item = optionSizer, proportion = 0,
  747. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  748. sizer.Add(item = btnSizer, proportion = 0,
  749. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  750. self.panel.SetSizer(sizer)
  751. sizer.Fit(self.panel)
  752. self.Layout()
  753. def _getCmd(self):
  754. item = self.tree.GetSelected()
  755. if not item or not item.IsOk():
  756. return ['g.extension']
  757. name = self.tree.GetItemText(item)
  758. if not name:
  759. GError(_("Extension not defined"), parent = self)
  760. return
  761. flags = list()
  762. for key in self.options.keys():
  763. if self.options[key].IsChecked():
  764. flags.append('-%s' % key)
  765. return ['g.extension'] + flags + ['extension=' + name,
  766. 'svnurl=' + self.repo.GetValue().strip()]
  767. def OnUpdateStatusBar(self, event):
  768. """!Update statusbar text"""
  769. element = self.search.GetSelection()
  770. if not self.tree.IsLoaded():
  771. self.SetStatusText(_("Fetch list of available extensions by clicking on 'Fetch' button"), 0)
  772. return
  773. self.tree.SearchItems(element = element,
  774. value = event.GetString())
  775. nItems = len(self.tree.itemsMarked)
  776. if event.GetString():
  777. self.SetStatusText(_("%d items match") % nItems, 0)
  778. else:
  779. self.SetStatusText("", 0)
  780. event.Skip()
  781. def OnCloseWindow(self, event):
  782. """!Close window"""
  783. self.Destroy()
  784. def OnFetch(self, event):
  785. """!Fetch list of available extensions"""
  786. wx.BeginBusyCursor()
  787. self.SetStatusText(_("Fetching list of modules from GRASS-Addons SVN (be patient)..."), 0)
  788. self.tree.Load(url = self.repo.GetValue().strip(), full = self.fullDesc.IsChecked())
  789. self.SetStatusText("", 0)
  790. wx.EndBusyCursor()
  791. def OnItemActivated(self, event):
  792. item = event.GetItem()
  793. data = self.tree.GetPyData(item)
  794. if data and 'command' in data:
  795. self.OnInstall(event = None)
  796. def OnInstall(self, event):
  797. """!Install selected extension"""
  798. log = self.parent.GetLogWindow()
  799. log.RunCmd(self._getCmd(), onDone = self.OnDone)
  800. def OnDone(self, cmd, returncode):
  801. item = self.tree.GetSelected()
  802. if not item or not item.IsOk() or \
  803. returncode != 0 or \
  804. not os.getenv('GRASS_ADDON_PATH'):
  805. return
  806. name = self.tree.GetItemText(item)
  807. globalvar.grassCmd['all'].append(name)
  808. def OnItemSelected(self, event):
  809. """!Item selected"""
  810. item = event.GetItem()
  811. self.tree.itemSelected = item
  812. data = self.tree.GetPyData(item)
  813. if not data:
  814. self.SetStatusText('', 0)
  815. self.btnInstall.Enable(False)
  816. else:
  817. self.SetStatusText(data.get('description', ''), 0)
  818. self.btnInstall.Enable(True)
  819. def OnShowItem(self, event):
  820. """!Show selected item"""
  821. self.tree.OnShowItem(event)
  822. if self.tree.GetSelected():
  823. self.btnInstall.Enable()
  824. else:
  825. self.btnInstall.Enable(False)
  826. def OnCmdDialog(self, event):
  827. """!Shows command dialog"""
  828. GUI(parent = self).ParseCommand(cmd = self._getCmd())
  829. class ExtensionTree(ItemTree):
  830. """!List of available extensions"""
  831. def __init__(self, parent, log, id = wx.ID_ANY,
  832. ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
  833. CT.TR_LINES_AT_ROOT | CT.TR_SINGLE,
  834. **kwargs):
  835. self.parent = parent # GMFrame
  836. self.log = log
  837. super(ExtensionTree, self).__init__(parent, id, ctstyle = ctstyle, **kwargs)
  838. self._initTree()
  839. def _initTree(self):
  840. for prefix in ('display', 'database',
  841. 'general', 'imagery',
  842. 'misc', 'postscript', 'paint',
  843. 'raster', 'raster3D', 'sites', 'vector', 'wxGUI', 'other'):
  844. self.AppendItem(parentId = self.root,
  845. text = prefix)
  846. self._loaded = False
  847. def _expandPrefix(self, c):
  848. name = { 'd' : 'display',
  849. 'db' : 'database',
  850. 'g' : 'general',
  851. 'i' : 'imagery',
  852. 'm' : 'misc',
  853. 'ps' : 'postscript',
  854. 'p' : 'paint',
  855. 'r' : 'raster',
  856. 'r3' : 'raster3D',
  857. 's' : 'sites',
  858. 'v' : 'vector',
  859. 'wx' : 'wxGUI',
  860. 'u' : 'other' }
  861. if c in name:
  862. return name[c]
  863. return c
  864. def _findItem(self, text):
  865. """!Find item"""
  866. item = self.GetFirstChild(self.root)[0]
  867. while item and item.IsOk():
  868. if text == self.GetItemText(item):
  869. return item
  870. item = self.GetNextSibling(item)
  871. return None
  872. def Load(self, url, full = False):
  873. """!Load list of extensions"""
  874. self.DeleteAllItems()
  875. self.root = self.AddRoot(_("Menu tree"))
  876. self._initTree()
  877. if full:
  878. flags = 'g'
  879. else:
  880. flags = 'l'
  881. ret = RunCommand('g.extension', read = True,
  882. svnurl = url,
  883. flags = flags, quiet = True)
  884. if not ret:
  885. return
  886. mdict = dict()
  887. for line in ret.splitlines():
  888. if full:
  889. key, value = line.split('=', 1)
  890. if key == 'name':
  891. try:
  892. prefix, name = value.split('.', 1)
  893. except ValueError:
  894. prefix = 'u'
  895. name = value
  896. if prefix not in mdict:
  897. mdict[prefix] = dict()
  898. mdict[prefix][name] = dict()
  899. else:
  900. mdict[prefix][name][key] = value
  901. else:
  902. try:
  903. prefix, name = line.strip().split('.', 1)
  904. except:
  905. prefix = 'unknown'
  906. name = line.strip()
  907. if self._expandPrefix(prefix) == prefix:
  908. prefix = 'unknown'
  909. if prefix not in mdict:
  910. mdict[prefix] = dict()
  911. mdict[prefix][name] = { 'command' : prefix + '.' + name }
  912. for prefix in mdict.keys():
  913. prefixName = self._expandPrefix(prefix)
  914. item = self._findItem(prefixName)
  915. names = mdict[prefix].keys()
  916. names.sort()
  917. for name in names:
  918. new = self.AppendItem(parentId = item,
  919. text = prefix + '.' + name)
  920. data = dict()
  921. for key in mdict[prefix][name].keys():
  922. data[key] = mdict[prefix][name][key]
  923. self.SetPyData(new, data)
  924. self._loaded = True
  925. def IsLoaded(self):
  926. """Check if items are loaded"""
  927. return self._loaded
  928. class HelpWindow(wx.html.HtmlWindow):
  929. """!This panel holds the text from GRASS docs.
  930. GISBASE must be set in the environment to find the html docs dir.
  931. The SYNOPSIS section is skipped, since this Panel is supposed to
  932. be integrated into the cmdPanel and options are obvious there.
  933. """
  934. def __init__(self, parent, grass_command, text, skip_description,
  935. **kwargs):
  936. """!If grass_command is given, the corresponding HTML help
  937. file will be presented, with all links pointing to absolute
  938. paths of local files.
  939. If 'skip_description' is True, the HTML corresponding to
  940. SYNOPSIS will be skipped, thus only presenting the help file
  941. from the DESCRIPTION section onwards.
  942. If 'text' is given, it must be the HTML text to be presented
  943. in the Panel.
  944. """
  945. self.parent = parent
  946. wx.InitAllImageHandlers()
  947. wx.html.HtmlWindow.__init__(self, parent = parent, **kwargs)
  948. gisbase = os.getenv("GISBASE")
  949. self.loaded = False
  950. self.history = list()
  951. self.historyIdx = 0
  952. self.fspath = os.path.join(gisbase, "docs", "html")
  953. self.SetStandardFonts (size = 10)
  954. self.SetBorders(10)
  955. if text is None:
  956. if skip_description:
  957. url = os.path.join(self.fspath, grass_command + ".html")
  958. self.fillContentsFromFile(url,
  959. skip_description = skip_description)
  960. self.history.append(url)
  961. self.loaded = True
  962. else:
  963. ### FIXME: calling LoadPage() is strangely time-consuming (only first call)
  964. # self.LoadPage(self.fspath + grass_command + ".html")
  965. self.loaded = False
  966. else:
  967. self.SetPage(text)
  968. self.loaded = True
  969. def OnLinkClicked(self, linkinfo):
  970. url = linkinfo.GetHref()
  971. if url[:4] != 'http':
  972. url = os.path.join(self.fspath, url)
  973. self.history.append(url)
  974. self.historyIdx += 1
  975. self.parent.OnHistory()
  976. super(HelpWindow, self).OnLinkClicked(linkinfo)
  977. def fillContentsFromFile(self, htmlFile, skip_description = True):
  978. """!Load content from file"""
  979. aLink = re.compile(r'(<a href="?)(.+\.html?["\s]*>)', re.IGNORECASE)
  980. imgLink = re.compile(r'(<img src="?)(.+\.[png|gif])', re.IGNORECASE)
  981. try:
  982. contents = []
  983. skip = False
  984. for l in file(htmlFile, "rb").readlines():
  985. if "DESCRIPTION" in l:
  986. skip = False
  987. if not skip:
  988. # do skip the options description if requested
  989. if "SYNOPSIS" in l:
  990. skip = skip_description
  991. else:
  992. # FIXME: find only first item
  993. findALink = aLink.search(l)
  994. if findALink is not None:
  995. contents.append(aLink.sub(findALink.group(1)+
  996. self.fspath+findALink.group(2),l))
  997. findImgLink = imgLink.search(l)
  998. if findImgLink is not None:
  999. contents.append(imgLink.sub(findImgLink.group(1)+
  1000. self.fspath+findImgLink.group(2),l))
  1001. if findALink is None and findImgLink is None:
  1002. contents.append(l)
  1003. self.SetPage("".join(contents))
  1004. self.loaded = True
  1005. except: # The Manual file was not found
  1006. self.loaded = False
  1007. class HelpPanel(wx.Panel):
  1008. def __init__(self, parent, grass_command = "index", text = None,
  1009. skip_description = False, **kwargs):
  1010. self.grass_command = grass_command
  1011. wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY)
  1012. self.content = HelpWindow(self, grass_command, text,
  1013. skip_description)
  1014. self.btnNext = wx.Button(parent = self, id = wx.ID_ANY,
  1015. label = _("&Next"))
  1016. self.btnNext.Enable(False)
  1017. self.btnPrev = wx.Button(parent = self, id = wx.ID_ANY,
  1018. label = _("&Previous"))
  1019. self.btnPrev.Enable(False)
  1020. self.btnNext.Bind(wx.EVT_BUTTON, self.OnNext)
  1021. self.btnPrev.Bind(wx.EVT_BUTTON, self.OnPrev)
  1022. self._layout()
  1023. def _layout(self):
  1024. """!Do layout"""
  1025. sizer = wx.BoxSizer(wx.VERTICAL)
  1026. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  1027. btnSizer.Add(item = self.btnPrev, proportion = 0,
  1028. flag = wx.ALL, border = 5)
  1029. btnSizer.Add(item = wx.Size(1, 1), proportion = 1)
  1030. btnSizer.Add(item = self.btnNext, proportion = 0,
  1031. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  1032. sizer.Add(item = self.content, proportion = 1,
  1033. flag = wx.EXPAND)
  1034. sizer.Add(item = btnSizer, proportion = 0,
  1035. flag = wx.EXPAND)
  1036. self.SetSizer(sizer)
  1037. sizer.Fit(self)
  1038. def LoadPage(self, path = None):
  1039. """!Load page"""
  1040. if not path:
  1041. path = os.path.join(self.content.fspath, self.grass_command + ".html")
  1042. self.content.history.append(path)
  1043. self.content.LoadPage(path)
  1044. def IsFile(self):
  1045. """!Check if file exists"""
  1046. return os.path.isfile(os.path.join(self.content.fspath, self.grass_command + ".html"))
  1047. def IsLoaded(self):
  1048. return self.content.loaded
  1049. def OnHistory(self):
  1050. """!Update buttons"""
  1051. nH = len(self.content.history)
  1052. iH = self.content.historyIdx
  1053. if iH == nH - 1:
  1054. self.btnNext.Enable(False)
  1055. elif iH > -1:
  1056. self.btnNext.Enable(True)
  1057. if iH < 1:
  1058. self.btnPrev.Enable(False)
  1059. else:
  1060. self.btnPrev.Enable(True)
  1061. def OnNext(self, event):
  1062. """Load next page"""
  1063. self.content.historyIdx += 1
  1064. idx = self.content.historyIdx
  1065. path = self.content.history[idx]
  1066. self.content.LoadPage(path)
  1067. self.OnHistory()
  1068. event.Skip()
  1069. def OnPrev(self, event):
  1070. """Load previous page"""
  1071. self.content.historyIdx -= 1
  1072. idx = self.content.historyIdx
  1073. path = self.content.history[idx]
  1074. self.content.LoadPage(path)
  1075. self.OnHistory()
  1076. event.Skip()