ghelp.py 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015
  1. """!
  2. @package help.py
  3. @brief Help window
  4. Classes:
  5. - HelpWindow
  6. - SearchModuleWindow
  7. - ItemTree
  8. - MenuTreeWindow
  9. - MenuTree
  10. - AboutWindow
  11. - InstallExtensionWindow
  12. - ExtensionTree
  13. (C) 2008-2010 by the GRASS Development Team
  14. This program is free software under the GNU General Public License
  15. (>=v2). Read the file COPYING that comes with GRASS for details.
  16. @author Martin Landa <landa.martin gmail.com>
  17. """
  18. import os
  19. import wx
  20. try:
  21. import wx.lib.agw.customtreectrl as CT
  22. # import wx.lib.agw.hyperlink as hl
  23. except ImportError:
  24. import wx.lib.customtreectrl as CT
  25. # import wx.lib.hyperlink as hl
  26. import wx.lib.flatnotebook as FN
  27. import wx.lib.scrolledpanel as scrolled
  28. import menudata
  29. import gcmd
  30. import globalvar
  31. import menuform
  32. class HelpWindow(wx.Frame):
  33. """!GRASS Quickstart help window"""
  34. def __init__(self, parent, id, title, size, file):
  35. wx.Frame.__init__(self, parent=parent, id=id, title=title, size=size)
  36. sizer = wx.BoxSizer(wx.VERTICAL)
  37. # text
  38. helpFrame = wx.html.HtmlWindow(parent=self, id=wx.ID_ANY)
  39. helpFrame.SetStandardFonts (size = 10)
  40. helpFrame.SetBorders(10)
  41. wx.InitAllImageHandlers()
  42. helpFrame.LoadFile(file)
  43. self.Ok = True
  44. sizer.Add(item=helpFrame, proportion=1, flag=wx.EXPAND)
  45. self.SetAutoLayout(True)
  46. self.SetSizer(sizer)
  47. # sizer.Fit(self)
  48. # sizer.SetSizeHints(self)
  49. self.Layout()
  50. class SearchModuleWindow(wx.Panel):
  51. """!Search module window (used in MenuTreeWindow)"""
  52. def __init__(self, parent, id = wx.ID_ANY, cmdPrompt = None,
  53. showChoice = True, showTip = False, **kwargs):
  54. self.showTip = showTip
  55. self.showChoice = showChoice
  56. self.cmdPrompt = cmdPrompt
  57. wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
  58. self._searchDict = { _('description') : 'description',
  59. _('command') : 'command',
  60. _('keywords') : 'keywords' }
  61. self.box = wx.StaticBox(parent = self, id = wx.ID_ANY,
  62. label=" %s " % _("Find module(s)"))
  63. self.searchBy = wx.Choice(parent = self, id = wx.ID_ANY,
  64. choices = [_('description'),
  65. _('keywords'),
  66. _('command')])
  67. self.searchBy.SetSelection(0)
  68. self.search = wx.TextCtrl(parent = self, id = wx.ID_ANY,
  69. value = "", size = (-1, 25),
  70. style = wx.TE_PROCESS_ENTER)
  71. self.search.Bind(wx.EVT_TEXT, self.OnSearchModule)
  72. if self.showTip:
  73. self.searchTip = menuform.StaticWrapText(parent = self, id = wx.ID_ANY,
  74. size = (-1, 35))
  75. if self.showChoice:
  76. self.searchChoice = wx.Choice(parent = self, id = wx.ID_ANY)
  77. if self.cmdPrompt:
  78. self.searchChoice.SetItems(self.cmdPrompt.GetCommandItems())
  79. self.searchChoice.Bind(wx.EVT_CHOICE, self.OnSelectModule)
  80. self._layout()
  81. def _layout(self):
  82. """!Do layout"""
  83. sizer = wx.StaticBoxSizer(self.box, wx.HORIZONTAL)
  84. gridSizer = wx.GridBagSizer(hgap = 3, vgap = 3)
  85. gridSizer.AddGrowableCol(1)
  86. gridSizer.Add(item = self.searchBy,
  87. flag=wx.ALIGN_CENTER_VERTICAL, pos = (0, 0))
  88. gridSizer.Add(item = self.search,
  89. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (0, 1))
  90. row = 1
  91. if self.showTip:
  92. gridSizer.Add(item = self.searchTip,
  93. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
  94. row += 1
  95. if self.showChoice:
  96. gridSizer.Add(item = self.searchChoice,
  97. flag=wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, pos = (row, 0), span = (1, 2))
  98. sizer.Add(item = gridSizer, proportion = 1)
  99. self.SetSizer(sizer)
  100. sizer.Fit(self)
  101. def GetSelection(self):
  102. """!Get selected element"""
  103. selection = self.searchBy.GetStringSelection()
  104. return self._searchDict[selection]
  105. def SetSelection(self, i):
  106. """!Set selection element"""
  107. self.searchBy.SetSelection(i)
  108. def OnSearchModule(self, event):
  109. """!Search module by keywords or description"""
  110. if not self.cmdPrompt:
  111. event.Skip()
  112. return
  113. text = event.GetString()
  114. if not text:
  115. self.cmdPrompt.SetFilter(None)
  116. mList = self.cmdPrompt.GetCommandItems()
  117. self.searchChoice.SetItems(mList)
  118. if self.showTip:
  119. self.searchTip.SetLabel(_("%d modules found") % len(mList))
  120. event.Skip()
  121. return
  122. modules = dict()
  123. iFound = 0
  124. for module, data in self.cmdPrompt.moduleDesc.iteritems():
  125. found = False
  126. sel = self.searchBy.GetSelection()
  127. if sel == 0: # -> description
  128. if text in data['desc']:
  129. found = True
  130. elif sel == 1: # keywords
  131. if self.cmdPrompt.CheckKey(text, data['keywords']):
  132. found = True
  133. else: # command
  134. if text in module:
  135. found = True
  136. if found:
  137. iFound += 1
  138. try:
  139. group, name = module.split('.')
  140. except ValueError:
  141. continue # TODO
  142. if not modules.has_key(group):
  143. modules[group] = list()
  144. modules[group].append(name)
  145. self.cmdPrompt.SetFilter(modules)
  146. self.searchChoice.SetItems(self.cmdPrompt.GetCommandItems())
  147. if self.showTip:
  148. self.searchTip.SetLabel(_("%d modules found") % iFound)
  149. event.Skip()
  150. def OnSelectModule(self, event):
  151. """!Module selected from choice, update command prompt"""
  152. cmd = event.GetString().split(' ', 1)[0]
  153. text = cmd + ' '
  154. pos = len(text)
  155. if self.cmdPrompt:
  156. self.cmdPrompt.SetText(text)
  157. self.cmdPrompt.SetSelectionStart(pos)
  158. self.cmdPrompt.SetCurrentPos(pos)
  159. self.cmdPrompt.SetFocus()
  160. desc = self.cmdPrompt.GetCommandDesc(cmd)
  161. if self.showTip:
  162. self.searchTip.SetLabel(desc)
  163. class MenuTreeWindow(wx.Panel):
  164. """!Show menu tree"""
  165. def __init__(self, parent, id = wx.ID_ANY, **kwargs):
  166. self.parent = parent # LayerManager
  167. wx.Panel.__init__(self, parent = parent, id = id, **kwargs)
  168. self.dataBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
  169. label=" %s " % _("Menu tree (double-click to run command)"))
  170. # tree
  171. self.tree = MenuTree(parent = self, data = menudata.ManagerData())
  172. self.tree.Load()
  173. # search widget
  174. self.search = SearchModuleWindow(parent = self, showChoice = False)
  175. # buttons
  176. self.btnRun = wx.Button(self, id = wx.ID_OK, label = _("&Run"))
  177. self.btnRun.SetToolTipString(_("Run selected command"))
  178. self.btnRun.Enable(False)
  179. # bindings
  180. self.btnRun.Bind(wx.EVT_BUTTON, self.OnRun)
  181. self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
  182. self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnItemSelected)
  183. self.search.Bind(wx.EVT_TEXT_ENTER, self.OnShowItem)
  184. self.search.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
  185. self._layout()
  186. self.search.SetFocus()
  187. def _layout(self):
  188. """!Do dialog layout"""
  189. sizer = wx.BoxSizer(wx.VERTICAL)
  190. # body
  191. dataSizer = wx.StaticBoxSizer(self.dataBox, wx.HORIZONTAL)
  192. dataSizer.Add(item = self.tree, proportion =1,
  193. flag = wx.EXPAND)
  194. # buttons
  195. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  196. btnSizer.Add(item = self.btnRun, proportion = 0)
  197. sizer.Add(item = dataSizer, proportion = 1,
  198. flag = wx.EXPAND | wx.ALL, border = 5)
  199. sizer.Add(item = self.search, proportion=0,
  200. flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border = 5)
  201. sizer.Add(item = btnSizer, proportion=0,
  202. flag = wx.ALIGN_RIGHT | wx.BOTTOM | wx.RIGHT, border = 5)
  203. sizer.Fit(self)
  204. sizer.SetSizeHints(self)
  205. self.SetSizer(sizer)
  206. self.Fit()
  207. self.SetAutoLayout(True)
  208. self.Layout()
  209. def OnCloseWindow(self, event):
  210. """!Close window"""
  211. self.Destroy()
  212. def OnRun(self, event):
  213. """!Run selected command"""
  214. if not self.tree.GetSelected():
  215. return # should not happen
  216. data = self.tree.GetPyData(self.tree.GetSelected())
  217. if not data:
  218. return
  219. handler = 'self.parent.' + data['handler'].lstrip('self.')
  220. if data['handler'] == 'self.OnXTerm':
  221. wx.MessageBox(parent = self,
  222. message = _('You must run this command from the menu or command line',
  223. 'This command require an XTerm'),
  224. caption = _('Message'), style=wx.OK | wx.ICON_ERROR | wx.CENTRE)
  225. elif data['command']:
  226. eval(handler)(event = None, cmd = data['command'].split())
  227. else:
  228. eval(handler)(None)
  229. def OnShowItem(self, event):
  230. """!Show selected item"""
  231. self.tree.OnShowItem(event)
  232. if self.tree.GetSelected():
  233. self.btnRun.Enable()
  234. else:
  235. self.btnRun.Enable(False)
  236. def OnItemActivated(self, event):
  237. """!Item activated (double-click)"""
  238. item = event.GetItem()
  239. if not item or not item.IsOk():
  240. return
  241. data = self.tree.GetPyData(item)
  242. if not data or not data.has_key('command'):
  243. return
  244. self.tree.itemSelected = item
  245. self.OnRun(None)
  246. def OnItemSelected(self, event):
  247. """!Item selected"""
  248. item = event.GetItem()
  249. if not item or not item.IsOk():
  250. return
  251. data = self.tree.GetPyData(item)
  252. if not data or not data.has_key('command'):
  253. return
  254. if data['command']:
  255. label = data['command'] + ' -- ' + data['description']
  256. else:
  257. label = data['description']
  258. self.parent.SetStatusText(label, 0)
  259. def OnUpdateStatusBar(self, event):
  260. """!Update statusbar text"""
  261. element = self.search.GetSelection()
  262. self.tree.SearchItems(element = element,
  263. value = event.GetString())
  264. nItems = len(self.tree.itemsMarked)
  265. if event.GetString():
  266. self.parent.SetStatusText(_("%d modules match") % nItems, 0)
  267. else:
  268. self.parent.SetStatusText("", 0)
  269. event.Skip()
  270. class ItemTree(CT.CustomTreeCtrl):
  271. def __init__(self, parent, id = wx.ID_ANY,
  272. ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
  273. CT.TR_LINES_AT_ROOT | CT.TR_SINGLE, **kwargs):
  274. super(ItemTree, self).__init__(parent, id, ctstyle = ctstyle, **kwargs)
  275. self.root = self.AddRoot(_("Menu tree"))
  276. self.itemsMarked = [] # list of marked items
  277. self.itemSelected = None
  278. def SearchItems(self, element, value):
  279. """!Search item
  280. @param element element index (see self.searchBy)
  281. @param value
  282. @return list of found tree items
  283. """
  284. items = list()
  285. if not value:
  286. return items
  287. item = self.GetFirstChild(self.root)[0]
  288. self._processItem(item, element, value, items)
  289. self.itemsMarked = items
  290. self.itemSelected = None
  291. return items
  292. def _processItem(self, item, element, value, listOfItems):
  293. """!Search items (used by SearchItems)
  294. @param item reference item
  295. @param listOfItems list of found items
  296. """
  297. while item and item.IsOk():
  298. subItem = self.GetFirstChild(item)[0]
  299. if subItem:
  300. self._processItem(subItem, element, value, listOfItems)
  301. data = self.GetPyData(item)
  302. if data and data.has_key(element) and \
  303. value.lower() in data[element].lower():
  304. listOfItems.append(item)
  305. item = self.GetNextSibling(item)
  306. def GetSelected(self):
  307. """!Get selected item"""
  308. return self.itemSelected
  309. def OnShowItem(self, event):
  310. """!Highlight first found item in menu tree"""
  311. if len(self.itemsMarked) > 0:
  312. if self.GetSelected():
  313. self.ToggleItemSelection(self.GetSelected())
  314. idx = self.itemsMarked.index(self.GetSelected()) + 1
  315. else:
  316. idx = 0
  317. try:
  318. self.ToggleItemSelection(self.itemsMarked[idx])
  319. self.itemSelected = self.itemsMarked[idx]
  320. self.EnsureVisible(self.itemsMarked[idx])
  321. except IndexError:
  322. self.ToggleItemSelection(self.itemsMarked[0]) # reselect first item
  323. self.EnsureVisible(self.itemsMarked[0])
  324. self.itemSelected = self.itemsMarked[0]
  325. else:
  326. for item in self.root.GetChildren():
  327. self.Collapse(item)
  328. itemSelected = self.GetSelection()
  329. if itemSelected:
  330. self.ToggleItemSelection(itemSelected)
  331. self.itemSelected = None
  332. class MenuTree(ItemTree):
  333. """!Menu tree class"""
  334. def __init__(self, parent, data, **kwargs):
  335. self.parent = parent
  336. self.menudata = data
  337. super(MenuTree, self).__init__(parent, **kwargs)
  338. def Load(self, data = None):
  339. """!Load menu data tree
  340. @param data menu data (None to use self.menudata)
  341. """
  342. if not data:
  343. data = self.menudata
  344. self.itemsMarked = [] # list of marked items
  345. for eachMenuData in data.GetMenu():
  346. for label, items in eachMenuData:
  347. item = self.AppendItem(parentId = self.root,
  348. text = label.replace('&', ''))
  349. self.__AppendItems(item, items)
  350. def __AppendItems(self, item, data):
  351. """!Append items into tree (used by Load()
  352. @param item tree item (parent)
  353. @parent data menu data"""
  354. for eachItem in data:
  355. if len(eachItem) == 2:
  356. if eachItem[0]:
  357. itemSub = self.AppendItem(parentId = item,
  358. text = eachItem[0])
  359. self.__AppendItems(itemSub, eachItem[1])
  360. else:
  361. if eachItem[0]:
  362. itemNew = self.AppendItem(parentId = item,
  363. text = eachItem[0])
  364. data = { 'item' : eachItem[0],
  365. 'description' : eachItem[1],
  366. 'handler' : eachItem[2],
  367. 'command' : eachItem[3],
  368. 'keywords' : eachItem[4] }
  369. self.SetPyData(itemNew, data)
  370. class AboutWindow(wx.Frame):
  371. def __init__(self, parent):
  372. """!Create custom About Window
  373. @todo improve styling
  374. """
  375. wx.Frame.__init__(self, parent=parent, id=wx.ID_ANY, size=(550,400),
  376. title=_('About GRASS GIS'))
  377. panel = wx.Panel(parent = self, id = wx.ID_ANY)
  378. # icon
  379. self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  380. # get version and web site
  381. version, svn_gis_h_rev, svn_gis_h_date = gcmd.RunCommand('g.version',
  382. flags = 'r',
  383. read = True).splitlines()
  384. infoTxt = wx.Panel(parent = panel, id = wx.ID_ANY)
  385. infoSizer = wx.BoxSizer(wx.VERTICAL)
  386. infoGridSizer = wx.GridBagSizer(vgap=5, hgap=5)
  387. infoGridSizer.AddGrowableCol(0)
  388. infoGridSizer.AddGrowableCol(1)
  389. logo = os.path.join(globalvar.ETCDIR, "gui", "icons", "grass.ico")
  390. logoBitmap = wx.StaticBitmap(parent = infoTxt, id = wx.ID_ANY,
  391. bitmap = wx.Bitmap(name = logo,
  392. type = wx.BITMAP_TYPE_ICO))
  393. infoSizer.Add(item = logoBitmap, proportion = 0,
  394. flag = wx.ALL | wx.ALIGN_CENTER, border = 25)
  395. info = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  396. label = version.replace('GRASS', 'GRASS GIS').strip() + '\n\n')
  397. info.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
  398. infoSizer.Add(item = info, proportion = 0,
  399. flag = wx.BOTTOM | wx.ALIGN_CENTER, border = 15)
  400. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  401. label = _('Official GRASS site:')),
  402. pos = (0, 0),
  403. flag = wx.ALIGN_RIGHT)
  404. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  405. label = 'http://grass.osgeo.org'),
  406. pos = (0, 1),
  407. flag = wx.ALIGN_LEFT)
  408. # infoGridSizer.Add(item = hl.HyperLinkCtrl(parent = self, id = wx.ID_ANY,
  409. # label = 'http://grass.osgeo.org',
  410. # URL = 'http://grass.osgeo.org'),
  411. # pos = (0, 1),
  412. # flag = wx.LEFT)
  413. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  414. label = _('GIS Library Revision:')),
  415. pos = (2, 0),
  416. flag = wx.ALIGN_RIGHT)
  417. infoGridSizer.Add(item = wx.StaticText(parent = infoTxt, id = wx.ID_ANY,
  418. label = svn_gis_h_rev.split(' ')[1] + ' (' +
  419. svn_gis_h_date.split(' ')[1] + ')'),
  420. pos = (2, 1),
  421. flag = wx.ALIGN_LEFT)
  422. infoSizer.Add(item = infoGridSizer,
  423. proportion = 1,
  424. flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL,
  425. border = 25)
  426. #
  427. # create pages
  428. #
  429. copyrightwin = self.PageCopyright()
  430. licensewin = self.PageLicense()
  431. authorwin = self.PageCredit()
  432. contribwin = self.PageContributors()
  433. transwin = self.PageTranslators()
  434. # create a flat notebook for displaying information about GRASS
  435. nbstyle = FN.FNB_VC8 | \
  436. FN.FNB_BACKGROUND_GRADIENT | \
  437. FN.FNB_TABS_BORDER_SIMPLE | \
  438. FN.FNB_NO_X_BUTTON
  439. aboutNotebook = FN.FlatNotebook(panel, id=wx.ID_ANY, style=nbstyle)
  440. aboutNotebook.SetTabAreaColour(globalvar.FNPageColor)
  441. # make pages for About GRASS notebook
  442. pg1 = aboutNotebook.AddPage(infoTxt, text=_("Info"))
  443. pg2 = aboutNotebook.AddPage(copyrightwin, text=_("Copyright"))
  444. pg3 = aboutNotebook.AddPage(licensewin, text=_("License"))
  445. pg4 = aboutNotebook.AddPage(authorwin, text=_("Authors"))
  446. pg5 = aboutNotebook.AddPage(contribwin, text=_("Contributors"))
  447. pg5 = aboutNotebook.AddPage(transwin, text=_("Translators"))
  448. wx.CallAfter(aboutNotebook.SetSelection, 0)
  449. # buttons
  450. btnClose = wx.Button(parent = panel, id = wx.ID_CLOSE)
  451. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  452. btnSizer.Add(item = btnClose, proportion = 0,
  453. flag = wx.ALL | wx.ALIGN_RIGHT,
  454. border = 5)
  455. # bindings
  456. # self.aboutNotebook.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnAGPageChanged)
  457. btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
  458. infoTxt.SetSizer(infoSizer)
  459. infoSizer.Fit(infoTxt)
  460. sizer = wx.BoxSizer(wx.VERTICAL)
  461. sizer.Add(item=aboutNotebook, proportion=1,
  462. flag=wx.EXPAND | wx.ALL, border=1)
  463. sizer.Add(item=btnSizer, proportion=0,
  464. flag=wx.ALL | wx.ALIGN_RIGHT, border=1)
  465. panel.SetSizer(sizer)
  466. self.Layout()
  467. def PageCopyright(self):
  468. """Copyright information"""
  469. copyfile = os.path.join(os.getenv("GISBASE"), "COPYING")
  470. if os.path.exists(copyfile):
  471. copyrightFile = open(copyfile, 'r')
  472. copytext = copyrightFile.read()
  473. copyrightFile.close()
  474. else:
  475. copytext = _('%s file missing') % 'COPYING'
  476. # put text into a scrolling panel
  477. copyrightwin = scrolled.ScrolledPanel(self, id=wx.ID_ANY,
  478. size=wx.DefaultSize,
  479. style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
  480. copyrighttxt = wx.StaticText(copyrightwin, id=wx.ID_ANY, label=copytext)
  481. copyrightwin.SetAutoLayout(True)
  482. copyrightwin.sizer = wx.BoxSizer(wx.VERTICAL)
  483. copyrightwin.sizer.Add(item=copyrighttxt, proportion=1,
  484. flag=wx.EXPAND | wx.ALL, border=3)
  485. copyrightwin.SetSizer(copyrightwin.sizer)
  486. copyrightwin.Layout()
  487. copyrightwin.SetupScrolling()
  488. return copyrightwin
  489. def PageLicense(self):
  490. """Licence about"""
  491. licfile = os.path.join(os.getenv("GISBASE"), "GPL.TXT")
  492. if os.path.exists(licfile):
  493. licenceFile = open(licfile, 'r')
  494. license = ''.join(licenceFile.readlines())
  495. licenceFile.close()
  496. else:
  497. license = _('%s file missing') % 'GPL.TXT'
  498. # put text into a scrolling panel
  499. licensewin = scrolled.ScrolledPanel(self, id=wx.ID_ANY,
  500. style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
  501. licensetxt = wx.StaticText(licensewin, id=wx.ID_ANY, label=license)
  502. licensewin.SetAutoLayout(True)
  503. licensewin.sizer = wx.BoxSizer(wx.VERTICAL)
  504. licensewin.sizer.Add(item=licensetxt, proportion=1,
  505. flag=wx.EXPAND | wx.ALL, border=3)
  506. licensewin.SetSizer(licensewin.sizer)
  507. licensewin.Layout()
  508. licensewin.SetupScrolling()
  509. return licensewin
  510. def PageCredit(self):
  511. """Credit about"""
  512. # credits
  513. authfile = os.path.join(os.getenv("GISBASE"), "AUTHORS")
  514. if os.path.exists(authfile):
  515. authorsFile = open(authfile, 'r')
  516. authors = unicode(''.join(authorsFile.readlines()), "utf-8")
  517. authorsFile.close()
  518. else:
  519. authors = _('%s file missing') % 'AUTHORS'
  520. authorwin = scrolled.ScrolledPanel(self, id=wx.ID_ANY,
  521. style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
  522. authortxt = wx.StaticText(authorwin, id=wx.ID_ANY, label=authors)
  523. authorwin.SetAutoLayout(1)
  524. authorwin.SetupScrolling()
  525. authorwin.sizer = wx.BoxSizer(wx.VERTICAL)
  526. authorwin.sizer.Add(item=authortxt, proportion=1,
  527. flag=wx.EXPAND | wx.ALL, border=3)
  528. authorwin.SetSizer(authorwin.sizer)
  529. authorwin.Layout()
  530. return authorwin
  531. def PageContributors(self):
  532. """Contributors info"""
  533. contribfile = os.path.join(os.getenv("GISBASE"), "contributors.csv")
  534. if os.path.exists(contribfile):
  535. contribFile = open(contribfile, 'r')
  536. contribs = list()
  537. for line in contribFile.readlines():
  538. cvs_id, name, email, country, osgeo_id, rfc2_agreed = line.split(',')
  539. contribs.append((name, email, country, osgeo_id))
  540. contribs[0] = (_('Name'), _('E-mail'), _('Country'), _('OSGeo_ID'))
  541. contribFile.close()
  542. else:
  543. contribs = None
  544. contribwin = scrolled.ScrolledPanel(self, id=wx.ID_ANY,
  545. style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
  546. contribwin.SetAutoLayout(1)
  547. contribwin.SetupScrolling()
  548. contribwin.sizer = wx.BoxSizer(wx.VERTICAL)
  549. if not contribs:
  550. contribtxt = wx.StaticText(contribwin, id=wx.ID_ANY,
  551. label=_('%s file missing') % 'contibutors.csv')
  552. contribwin.sizer.Add(item=contribtxt, proportion=1,
  553. flag=wx.EXPAND | wx.ALL, border=3)
  554. else:
  555. contribBox = wx.FlexGridSizer(cols=4, vgap=5, hgap=5)
  556. for developer in contribs:
  557. for item in developer:
  558. contribBox.Add(item = wx.StaticText(parent = contribwin, id = wx.ID_ANY,
  559. label = item))
  560. contribwin.sizer.Add(item=contribBox, proportion=1,
  561. flag=wx.EXPAND | wx.ALL, border=3)
  562. contribwin.SetSizer(contribwin.sizer)
  563. contribwin.Layout()
  564. return contribwin
  565. def PageTranslators(self):
  566. """Translators info"""
  567. translatorsfile = os.path.join(os.getenv("GISBASE"), "translators.csv")
  568. if os.path.exists(translatorsfile):
  569. translatorsFile = open(translatorsfile, 'r')
  570. translators = dict()
  571. for line in translatorsFile.readlines()[1:]:
  572. name, email, languages = line.rstrip('\n').split(',')
  573. for language in languages.split(' '):
  574. if not translators.has_key(language):
  575. translators[language] = list()
  576. translators[language].append((name, email))
  577. translatorsFile.close()
  578. else:
  579. translators = None
  580. translatorswin = scrolled.ScrolledPanel(self, id=wx.ID_ANY,
  581. style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER)
  582. translatorswin.SetAutoLayout(1)
  583. translatorswin.SetupScrolling()
  584. translatorswin.sizer = wx.BoxSizer(wx.VERTICAL)
  585. if not translators:
  586. translatorstxt = wx.StaticText(translatorswin, id=wx.ID_ANY,
  587. label=_('%s file missing') % 'translators.csv')
  588. translatorswin.sizer.Add(item=translatorstxt, proportion=1,
  589. flag=wx.EXPAND | wx.ALL, border=3)
  590. else:
  591. translatorsBox = wx.FlexGridSizer(cols=3, vgap=5, hgap=5)
  592. languages = translators.keys()
  593. languages.sort()
  594. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  595. label = _('Name')))
  596. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  597. label = _('E-mail')))
  598. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  599. label = _('Language')))
  600. for lang in languages:
  601. for translator in translators[lang]:
  602. name, email = translator
  603. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  604. label = unicode(name, "utf-8")))
  605. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  606. label = email))
  607. translatorsBox.Add(item = wx.StaticText(parent = translatorswin, id = wx.ID_ANY,
  608. label = lang))
  609. translatorswin.sizer.Add(item=translatorsBox, proportion=1,
  610. flag=wx.EXPAND | wx.ALL, border=3)
  611. translatorswin.SetSizer(translatorswin.sizer)
  612. translatorswin.Layout()
  613. return translatorswin
  614. def OnCloseWindow(self, event):
  615. """!Close window"""
  616. self.Close()
  617. class InstallExtensionWindow(wx.Frame):
  618. def __init__(self, parent, id = wx.ID_ANY,
  619. title = _("Fetch & install new extension from GRASS Addons"), **kwargs):
  620. self.parent = parent
  621. wx.Frame.__init__(self, parent = parent, id = id, title = title, **kwargs)
  622. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  623. self.repoBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  624. label=" %s " % _("Repository"))
  625. self.findBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  626. label=" %s " % _("Find extension by"))
  627. self.treeBox = wx.StaticBox(parent = self.panel, id = wx.ID_ANY,
  628. label=" %s " % _("List of extensions"))
  629. self.repo = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY,
  630. value = 'https://svn.osgeo.org/grass/grass-addons')
  631. self.fullDesc = wx.CheckBox(parent = self.panel, id=wx.ID_ANY,
  632. label = _("Fetch full info including description and keywords (takes time)"))
  633. self.fullDesc.SetValue(False)
  634. self.search = SearchModuleWindow(parent = self.panel)
  635. self.search.SetSelection(2)
  636. self.tree = ExtensionTree(parent = self.panel, log = parent.GetLogWindow())
  637. self.statusbar = self.CreateStatusBar(0)
  638. self.btnFetch = wx.Button(parent = self.panel, id = wx.ID_ANY,
  639. label = _("&Fetch"))
  640. self.btnFetch.SetToolTipString(_("Fetch list of available modules from GRASS Addons SVN repository"))
  641. self.btnClose = wx.Button(parent = self.panel, id = wx.ID_CLOSE)
  642. self.btnInstall = wx.Button(parent = self.panel, id = wx.ID_ANY,
  643. label = _("&Install"))
  644. self.btnInstall.SetToolTipString(_("Install selected add-ons GRASS module"))
  645. self.btnInstall.Enable(False)
  646. self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow)
  647. self.btnFetch.Bind(wx.EVT_BUTTON, self.OnFetch)
  648. self.btnInstall.Bind(wx.EVT_BUTTON, self.OnInstall)
  649. self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
  650. self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnItemSelected)
  651. self.search.Bind(wx.EVT_TEXT_ENTER, self.OnShowItem)
  652. self.search.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
  653. self._layout()
  654. def _layout(self):
  655. """!Do layout"""
  656. sizer = wx.BoxSizer(wx.VERTICAL)
  657. repoSizer = wx.StaticBoxSizer(self.repoBox, wx.VERTICAL)
  658. repo1Sizer = wx.BoxSizer(wx.HORIZONTAL)
  659. repo1Sizer.Add(item = self.repo, proportion = 1,
  660. flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
  661. repo1Sizer.Add(item = self.btnFetch, proportion = 0,
  662. flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
  663. repoSizer.Add(item = repo1Sizer,
  664. flag = wx.EXPAND)
  665. repoSizer.Add(item = self.fullDesc)
  666. findSizer = wx.StaticBoxSizer(self.findBox, wx.HORIZONTAL)
  667. findSizer.Add(item = self.search, proportion = 1)
  668. treeSizer = wx.StaticBoxSizer(self.treeBox, wx.HORIZONTAL)
  669. treeSizer.Add(item = self.tree, proportion = 1,
  670. flag = wx.ALL | wx.EXPAND, border = 1)
  671. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  672. btnSizer.Add(item = self.btnClose, proportion = 0,
  673. flag = wx.RIGHT, border = 5)
  674. btnSizer.Add(item = self.btnInstall, proportion = 0)
  675. sizer.Add(item = repoSizer, proportion = 0,
  676. flag = wx.ALL | wx.EXPAND, border = 3)
  677. sizer.Add(item = findSizer, proportion = 0,
  678. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  679. sizer.Add(item = treeSizer, proportion = 1,
  680. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 3)
  681. sizer.Add(item = btnSizer, proportion=0,
  682. flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
  683. self.panel.SetSizer(sizer)
  684. sizer.Fit(self.panel)
  685. self.Layout()
  686. def _install(self, name):
  687. if not name:
  688. return
  689. log = self.parent.GetLogWindow()
  690. log.RunCmd(['g.extension', 'extension=' + name,
  691. 'svnurl=' + self.repo.GetValue().strip()])
  692. self.OnCloseWindow(None)
  693. def OnUpdateStatusBar(self, event):
  694. """!Update statusbar text"""
  695. element = self.search.GetSelection()
  696. if not self.tree.IsLoaded():
  697. self.SetStatusText(_("Fetch list of available extensions by clicking on 'Fetch' button"), 0)
  698. return
  699. self.tree.SearchItems(element = element,
  700. value = event.GetString())
  701. nItems = len(self.tree.itemsMarked)
  702. if event.GetString():
  703. self.SetStatusText(_("%d items match") % nItems, 0)
  704. else:
  705. self.SetStatusText("", 0)
  706. event.Skip()
  707. def OnCloseWindow(self, event):
  708. """!Close window"""
  709. self.Destroy()
  710. def OnFetch(self, event):
  711. """!Fetch list of available extensions"""
  712. self.SetStatusText(_("Fetching list of modules from GRASS-Addons SVN (be patient)..."), 0)
  713. self.tree.Load(url = self.repo.GetValue().strip(), full = self.fullDesc.IsChecked())
  714. self.SetStatusText("", 0)
  715. def OnItemActivated(self, event):
  716. item = event.GetItem()
  717. data = self.tree.GetPyData(item)
  718. if data and data.has_key('command'):
  719. self._install(data['command'])
  720. def OnInstall(self, event):
  721. """!Install selected extension"""
  722. item = self.tree.GetSelected()
  723. if not item.IsOk():
  724. return
  725. self._install(self.tree.GetItemText(item))
  726. def OnItemSelected(self, event):
  727. """!Item selected"""
  728. item = event.GetItem()
  729. self.tree.itemSelected = item
  730. data = self.tree.GetPyData(item)
  731. if not data:
  732. self.SetStatusText('', 0)
  733. self.btnInstall.Enable(False)
  734. else:
  735. self.SetStatusText(data.get('description', ''), 0)
  736. self.btnInstall.Enable(True)
  737. def OnShowItem(self, event):
  738. """!Show selected item"""
  739. self.tree.OnShowItem(event)
  740. if self.tree.GetSelected():
  741. self.btnInstall.Enable()
  742. else:
  743. self.btnInstall.Enable(False)
  744. class ExtensionTree(ItemTree):
  745. """!List of available extensions"""
  746. def __init__(self, parent, log, id = wx.ID_ANY,
  747. ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
  748. CT.TR_LINES_AT_ROOT | CT.TR_SINGLE,
  749. **kwargs):
  750. self.parent = parent # GMFrame
  751. self.log = log
  752. super(ExtensionTree, self).__init__(parent, id, ctstyle = ctstyle, **kwargs)
  753. self._initTree()
  754. def _initTree(self):
  755. for prefix in ('display', 'database',
  756. 'general', 'imagery',
  757. 'misc', 'postscript', 'paint',
  758. 'raster', 'raster3D', 'sites', 'vector'):
  759. self.AppendItem(parentId = self.root,
  760. text = prefix)
  761. self._loaded = False
  762. def _expandPrefix(self, c):
  763. name = { 'd' : 'display',
  764. 'db' : 'database',
  765. 'g' : 'general',
  766. 'i' : 'imagery',
  767. 'm' : 'misc',
  768. 'ps' : 'postscript',
  769. 'p' : 'paint',
  770. 'r' : 'raster',
  771. 'r3' : 'raster3D',
  772. 's' : 'sites',
  773. 'v' : 'vector' }
  774. if name.has_key(c):
  775. return name[c]
  776. return c
  777. def _findItem(self, text):
  778. """!Find item"""
  779. item = self.GetFirstChild(self.root)[0]
  780. while item and item.IsOk():
  781. if text == self.GetItemText(item):
  782. return item
  783. item = self.GetNextSibling(item)
  784. return None
  785. def Load(self, url, full = False):
  786. """!Load list of extensions"""
  787. self.DeleteAllItems()
  788. self.root = self.AddRoot(_("Menu tree"))
  789. self._initTree()
  790. if full:
  791. flags = 'g'
  792. else:
  793. flags = 'l'
  794. ret = gcmd.RunCommand('g.extension', read = True,
  795. svnurl = url,
  796. flags = flags, quiet = True)
  797. if not ret:
  798. return
  799. mdict = dict()
  800. for line in ret.splitlines():
  801. if full:
  802. key, value = line.split('=', 1)
  803. if key == 'name':
  804. prefix, name = value.split('.', 1)
  805. if not mdict.has_key(prefix):
  806. mdict[prefix] = dict()
  807. mdict[prefix][name] = dict()
  808. else:
  809. mdict[prefix][name][key] = value
  810. else:
  811. prefix, name = line.strip().split('.', 1)
  812. if not mdict.has_key(prefix):
  813. mdict[prefix] = dict()
  814. mdict[prefix][name] = { 'command' : prefix + '.' + name }
  815. for prefix in mdict.keys():
  816. prefixName = self._expandPrefix(prefix)
  817. item = self._findItem(prefixName)
  818. names = mdict[prefix].keys()
  819. names.sort()
  820. for name in names:
  821. new = self.AppendItem(parentId = item,
  822. text = prefix + '.' + name)
  823. data = dict()
  824. for key in mdict[prefix][name].keys():
  825. data[key] = mdict[prefix][name][key]
  826. self.SetPyData(new, data)
  827. self._loaded = True
  828. def IsLoaded(self):
  829. """Check if items are loaded"""
  830. return self._loaded