dialogs.py 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051
  1. """
  2. @package web_services.dialogs
  3. @brief Dialogs for web services.
  4. List of classes:
  5. - dialogs::WSDialogBase
  6. - dialogs::AddWSDialog
  7. - dialogs::WSPropertiesDialog
  8. - dialogs::SaveWMSLayerDialog
  9. (C) 2009-2013 by the GRASS Development Team
  10. This program is free software under the GNU General Public License
  11. (>=v2). Read the file COPYING that comes with GRASS for details.
  12. @author Martin Landa <landa.martin gmail.com>
  13. @author Stepan Turek <stepan.turek seznam.cz>
  14. """
  15. import wx
  16. import os
  17. import sys
  18. import shutil
  19. from copy import deepcopy
  20. import grass.script as grass
  21. from grass.script.task import cmdlist_to_tuple, cmdtuple_to_list
  22. from core import globalvar
  23. from core.debug import Debug
  24. from core.gcmd import GMessage, GWarning, GError, RunCommand
  25. from core.utils import GetSettingsPath, _
  26. from core.gconsole import CmdThread, GStderr, EVT_CMD_DONE, EVT_CMD_OUTPUT
  27. from gui_core.gselect import Select
  28. from web_services.widgets import WSPanel, WSManageSettingsWidget
  29. class WSDialogBase(wx.Dialog):
  30. """Base class for web service dialogs.
  31. """
  32. def __init__(self, parent, id = wx.ID_ANY,
  33. style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
  34. wx.Dialog.__init__(self, parent, id, style = style, **kwargs)
  35. self.parent = parent
  36. # contains panel for every web service on server
  37. self.ws_panels = {'WMS_1.1.1' : {'panel' : None,
  38. 'label' : 'WMS 1.1.1'},
  39. 'WMS_1.3.0' : {'panel' : None,
  40. 'label' : 'WMS 1.3.0'},
  41. 'WMTS' : {'panel' : None,
  42. 'label' : 'WMTS'},
  43. 'OnEarth' : {'panel' : None,
  44. 'label' : 'OnEarth'},
  45. }
  46. #TODO: should be in file
  47. self.default_servers = { 'OSM-WMS-EUROPE' : ['http://watzmann-geog.urz.uni-heidelberg.de/cached/osm', '', ''],
  48. 'irs.gis-lab.info (OSM)' : ['http://irs.gis-lab.info', '', ''],
  49. 'NASA OnEarth' : ['http://onearth.jpl.nasa.gov/wms.cgi', '', '']
  50. }
  51. # holds reference to web service panel which is showed
  52. self.active_ws_panel = None
  53. # buttons which are disabled when the dialog is not connected
  54. self.run_btns = []
  55. # stores error messages for GError dialog showed when all web service connections were unsuccessful
  56. self.error_msgs = ''
  57. self._createWidgets()
  58. self._doLayout()
  59. def _createWidgets(self):
  60. settingsFile = os.path.join(GetSettingsPath(), 'wxWS')
  61. self.settsManager = WSManageSettingsWidget(parent=self,
  62. settingsFile=settingsFile,
  63. default_servers=self.default_servers)
  64. self.settingsBox = wx.StaticBox(parent = self,
  65. id = wx.ID_ANY,
  66. label = _(" Server settings "))
  67. self.serverText = wx.StaticText(parent = self,
  68. id = wx.ID_ANY, label = _("Server:"))
  69. self.server = wx.TextCtrl(parent = self, id = wx.ID_ANY)
  70. self.btn_connect = wx.Button(parent = self,
  71. id = wx.ID_ANY, label = _("&Connect"))
  72. self.btn_connect.SetToolTipString(_("Connect to the server"))
  73. if not self.server.GetValue():
  74. self.btn_connect.Enable(False)
  75. self.infoCollapseLabelExp = _('Show advanced connection settings')
  76. self.infoCollapseLabelCol = _('Hide advanced connection settings')
  77. self.adv_conn = wx.CollapsiblePane(parent = self,
  78. label = self.infoCollapseLabelExp,
  79. style = wx.CP_DEFAULT_STYLE |
  80. wx.CP_NO_TLW_RESIZE | wx.EXPAND)
  81. self.MakeAdvConnPane(pane = self.adv_conn.GetPane())
  82. self.adv_conn.Collapse(True)
  83. self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnAdvConnPaneChanged, self.adv_conn)
  84. self.reqDataPanel = wx.Panel(parent = self, id = wx.ID_ANY)
  85. self.layerNameBox = wx.StaticBox(parent = self.reqDataPanel,
  86. id = wx.ID_ANY,
  87. label = _(" Layer Manager Settings "))
  88. self.layerNameText = wx.StaticText(parent = self.reqDataPanel, id = wx.ID_ANY,
  89. label = _("Output layer name:"))
  90. self.layerName = wx.TextCtrl(parent = self.reqDataPanel, id = wx.ID_ANY)
  91. for ws in self.ws_panels.iterkeys():
  92. # set class WSPanel argument layerNameTxtCtrl
  93. self.ws_panels[ws]['panel'] = WSPanel(parent = self.reqDataPanel,
  94. web_service = ws)
  95. self.ws_panels[ws]['panel'].capParsed.connect(self.OnPanelCapParsed)
  96. self.ws_panels[ws]['panel'].layerSelected.connect(self.OnLayerSelected)
  97. # buttons
  98. self.btn_close = wx.Button(parent = self, id = wx.ID_CLOSE)
  99. self.btn_close.SetToolTipString(_("Close dialog"))
  100. # statusbar
  101. self.statusbar = wx.StatusBar(parent = self, id = wx.ID_ANY)
  102. # bindings
  103. self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
  104. self.Bind(wx.EVT_CLOSE, self.OnClose)
  105. self.btn_connect.Bind(wx.EVT_BUTTON, self.OnConnect)
  106. self.server.Bind(wx.EVT_TEXT, self.OnServer)
  107. self.layerName.Bind(wx.EVT_TEXT, self.OnOutputLayerName)
  108. self.settsManager.settingsChanged.connect(self.OnSettingsChanged)
  109. self.settsManager.settingsSaving.connect(self.OnSettingsSaving)
  110. def OnLayerSelected(self, title):
  111. if not self.layerName.GetValue().strip():
  112. self.layerName.SetValue(title)
  113. def _doLayout(self):
  114. dialogSizer = wx.BoxSizer(wx.VERTICAL)
  115. dialogSizer.Add(item = self.settsManager, proportion = 0,
  116. flag = wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border = 5)
  117. # connectin settings
  118. settingsSizer = wx.StaticBoxSizer(self.settingsBox, wx.VERTICAL)
  119. serverSizer = wx.FlexGridSizer(cols = 3, vgap = 5, hgap = 5)
  120. serverSizer.Add(item = self.serverText,
  121. flag = wx.ALIGN_CENTER_VERTICAL)
  122. serverSizer.AddGrowableCol(1)
  123. serverSizer.Add(item = self.server,
  124. flag = wx.EXPAND | wx.ALL)
  125. serverSizer.Add(item = self.btn_connect)
  126. settingsSizer.Add(item = serverSizer, proportion = 0,
  127. flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 5)
  128. settingsSizer.Add(item = self.adv_conn,
  129. flag = wx.ALL | wx.EXPAND, border = 5)
  130. dialogSizer.Add(item = settingsSizer, proportion = 0,
  131. flag = wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border = 5)
  132. # layer name, parsed capabilites
  133. reqDataSizer = wx.BoxSizer(wx.VERTICAL)
  134. layerNameSizer = wx.StaticBoxSizer(self.layerNameBox, wx.HORIZONTAL)
  135. layerNameSizer.Add(item = self.layerNameText,
  136. flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL, border = 5)
  137. layerNameSizer.Add(item = self.layerName,
  138. flag = wx.EXPAND, proportion = 1)
  139. reqDataSizer.Add(item = layerNameSizer,
  140. flag = wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND, border = 5)
  141. self.ch_ws_sizer = wx.BoxSizer(wx.VERTICAL)
  142. reqDataSizer.Add(item = self.ch_ws_sizer, proportion = 0,
  143. flag = wx.TOP | wx.EXPAND, border = 5)
  144. for ws in self.ws_panels.iterkeys():
  145. reqDataSizer.Add(item = self.ws_panels[ws]['panel'], proportion = 1,
  146. flag = wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND, border = 5)
  147. self.ws_panels[ws]['panel'].Hide()
  148. dialogSizer.Add(item = self.reqDataPanel, proportion = 1,
  149. flag = wx.EXPAND)
  150. self.reqDataPanel.SetSizer(reqDataSizer)
  151. self.reqDataPanel.Hide()
  152. # buttons
  153. self.btnsizer = wx.BoxSizer(orient = wx.HORIZONTAL)
  154. self.btnsizer.Add(item = self.btn_close, proportion = 0,
  155. flag = wx.ALL | wx.ALIGN_CENTER,
  156. border = 10)
  157. dialogSizer.Add(item = self.btnsizer, proportion = 0,
  158. flag = wx.ALIGN_CENTER)
  159. # expand wxWidget wx.StatusBar
  160. statusbarSizer = wx.BoxSizer(wx.HORIZONTAL)
  161. statusbarSizer.Add(item=self.statusbar, proportion=1, flag=wx.EXPAND)
  162. dialogSizer.Add(item=statusbarSizer,
  163. proportion=0,
  164. flag=wx.EXPAND)
  165. self.SetSizer(dialogSizer)
  166. self.Layout()
  167. self.SetMinSize((550, -1))
  168. self.SetMaxSize((-1, self.GetBestSize()[1]))
  169. self.Fit()
  170. def MakeAdvConnPane(self, pane):
  171. """Create advanced connection settings pane
  172. """
  173. self.usernameText = wx.StaticText(parent = pane,
  174. id = wx.ID_ANY, label = _("Username:"))
  175. self.username = wx.TextCtrl(parent = pane, id = wx.ID_ANY)
  176. self.passwText = wx.StaticText(parent = pane,
  177. id = wx.ID_ANY, label = _("Password:"))
  178. self.password = wx.TextCtrl(parent = pane, id = wx.ID_ANY,
  179. style = wx.TE_PASSWORD)
  180. # pane layout
  181. adv_conn_sizer = wx.BoxSizer(wx.VERTICAL)
  182. usernameSizer = wx.BoxSizer(wx.HORIZONTAL)
  183. usernameSizer.Add(item = self.usernameText,
  184. flag = wx.ALIGN_CENTER_VERTICAL, border = 5)
  185. usernameSizer.Add(item = self.username, proportion = 1,
  186. flag = wx.EXPAND, border = 5)
  187. adv_conn_sizer.Add(item = usernameSizer,
  188. flag = wx.ALL | wx.EXPAND, border = 5)
  189. passwSizer = wx.BoxSizer(wx.HORIZONTAL)
  190. passwSizer.Add(item = self.passwText,
  191. flag = wx.ALIGN_CENTER_VERTICAL, border = 5)
  192. passwSizer.Add(item = self.password, proportion = 1,
  193. flag = wx.EXPAND, border = 5)
  194. adv_conn_sizer.Add(item = passwSizer,
  195. flag = wx.ALL | wx.EXPAND, border = 5)
  196. pane.SetSizer(adv_conn_sizer)
  197. adv_conn_sizer.Fit(pane)
  198. pane.SetSizer(adv_conn_sizer)
  199. adv_conn_sizer.Fit(pane)
  200. def OnSettingsSaving(self, name):
  201. """Check if required data are filled before setting save is performed.
  202. """
  203. server = self.server.GetValue().strip()
  204. if not server:
  205. GMessage(parent = self,
  206. message = _("No data source defined, settings are not saved."))
  207. return
  208. self.settsManager.SetDataToSave((server,
  209. self.username.GetValue(),
  210. self.password.GetValue()))
  211. self.settsManager.SaveSettings(name)
  212. def OnSettingsChanged(self, data):
  213. """Update widgets according to chosen settings"""
  214. # data list: [server, username, password]
  215. if len < 3:
  216. return
  217. self.server.SetValue(data[0])
  218. self.username.SetValue(data[1])
  219. self.password.SetValue(data[2])
  220. if data[1] or data[2]:
  221. self.adv_conn.Expand()
  222. else:
  223. self.adv_conn.Collapse(True)
  224. # clear content of the wxWidget wx.TextCtrl (Output layer
  225. # name:), based on changing default server selection in the
  226. # wxWidget wx.Choice
  227. if len(self.layerName.GetValue()) > 0:
  228. self.layerName.Clear()
  229. def OnClose(self, event):
  230. """Close the dialog
  231. """
  232. """Close dialog"""
  233. if not self.IsModal():
  234. self.Destroy()
  235. event.Skip()
  236. def _getCapFiles(self):
  237. ws_cap_files = {}
  238. for v in self.ws_panels.itervalues():
  239. ws_cap_files[v['panel'].GetWebService()] = v['panel'].GetCapFile()
  240. return ws_cap_files
  241. def OnServer(self, event):
  242. """Server settings edited
  243. """
  244. value = event.GetString()
  245. if value:
  246. self.btn_connect.Enable(True)
  247. else:
  248. self.btn_connect.Enable(False)
  249. # clear content of the wxWidget wx.TextCtrl (Output Layer
  250. # name:), based on changing content of the wxWidget
  251. # wx.TextCtrl (Server:)
  252. self.layerName.Clear()
  253. def OnOutputLayerName(self, event):
  254. """Update layer name to web service panel
  255. """
  256. lname = event.GetString()
  257. lname = lname.encode('ascii', 'replace')
  258. for v in self.ws_panels.itervalues():
  259. v['panel'].SetOutputLayerName(lname.strip())
  260. def OnConnect(self, event):
  261. """Connect to the server
  262. """
  263. server = self.server.GetValue().strip()
  264. self.ch_ws_sizer.Clear(deleteWindows = True)
  265. if self.active_ws_panel is not None:
  266. self.reqDataPanel.Hide()
  267. for btn in self.run_btns:
  268. btn.Enable(False)
  269. self.active_ws_panel = None
  270. self.Layout()
  271. self.Fit()
  272. self.statusbar.SetStatusText(_("Connecting to <%s>..." % self.server.GetValue().strip()))
  273. # number of panels already connected
  274. self.finished_panels_num = 0
  275. for ws in self.ws_panels.iterkeys():
  276. self.ws_panels[ws]['panel'].ConnectToServer(url = server,
  277. username = self.username.GetValue(),
  278. password = self.password.GetValue())
  279. self.ws_panels[ws]['panel'].Hide()
  280. def OnPanelCapParsed(self, error_msg):
  281. """Called when panel has downloaded and parsed capabilities file.
  282. """
  283. # how many web service panels are finished
  284. self.finished_panels_num += 1
  285. if error_msg:
  286. self.error_msgs += '\n' + error_msg
  287. # if all are finished, show panels, which succeeded in connection
  288. if self.finished_panels_num == len(self.ws_panels):
  289. self.UpdateDialogAfterConnection()
  290. # show error dialog only if connections to all web services were unsuccessful
  291. if not self._getConnectedWS() and self.error_msgs:
  292. GError(self.error_msgs, parent = self)
  293. self.error_msgs = ''
  294. self.Layout()
  295. self.Fit()
  296. def _getConnectedWS(self):
  297. """
  298. :return: list of found web services on server (identified as keys in self.ws_panels)
  299. """
  300. conn_ws = []
  301. for ws, data in self.ws_panels.iteritems():
  302. if data['panel'].IsConnected():
  303. conn_ws.append(ws)
  304. return conn_ws
  305. def UpdateDialogAfterConnection(self):
  306. """Update dialog after all web service panels downloaded and parsed capabilities data.
  307. """
  308. avail_ws = {}
  309. conn_ws = self._getConnectedWS()
  310. for ws in conn_ws:
  311. avail_ws[ws] = self.ws_panels[ws]
  312. self.web_service_sel = []
  313. self.rb_choices = []
  314. # at least one web service found on server
  315. if len(avail_ws) > 0:
  316. self.reqDataPanel.Show()
  317. self.rb_order = ['WMS_1.1.1', 'WMS_1.3.0', 'WMTS', 'OnEarth']
  318. for ws in self.rb_order:
  319. if ws in avail_ws:
  320. self.web_service_sel.append(ws)
  321. self.rb_choices.append(avail_ws[ws]['label'])
  322. self.choose_ws_rb = wx.RadioBox(parent = self.reqDataPanel, id = wx.ID_ANY,
  323. label = _("Available web services"),
  324. pos = wx.DefaultPosition, choices = self.rb_choices,
  325. majorDimension = 1, style = wx.RA_SPECIFY_ROWS)
  326. self.Bind(wx.EVT_RADIOBOX, self.OnChooseWs, self.choose_ws_rb)
  327. self.ch_ws_sizer.Add(item = self.choose_ws_rb,
  328. flag = wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND, border = 5)
  329. self._showWsPanel(self.web_service_sel[self.choose_ws_rb.GetSelection()])
  330. self.statusbar.SetStatusText(_("Connected to <%s>" % self.server.GetValue().strip()))
  331. for btn in self.run_btns:
  332. btn.Enable(True)
  333. # no web service found on server
  334. else:
  335. self.statusbar.SetStatusText(_("Unable to connect to <%s>" % self.server.GetValue().strip()))
  336. for btn in self.run_btns:
  337. btn.Enable(False)
  338. self.reqDataPanel.Hide()
  339. self.active_ws_panel = None
  340. def OnChooseWs(self, event):
  341. """Show panel corresponding to selected web service.
  342. """
  343. choosen_r = event.GetInt()
  344. self._showWsPanel(self.web_service_sel[choosen_r])
  345. def _showWsPanel(self, ws):
  346. """Helper function
  347. """
  348. if self.active_ws_panel is not None:
  349. self.active_ws_panel.Hide()
  350. self.active_ws_panel = self.ws_panels[ws]['panel']
  351. self.active_ws_panel.Show()
  352. self.SetMaxSize((-1, -1))
  353. self.active_ws_panel.GetContainingSizer().Layout()
  354. def OnAdvConnPaneChanged(self, event):
  355. """Collapse search module box
  356. """
  357. if self.adv_conn.IsExpanded():
  358. self.adv_conn.SetLabel(self.infoCollapseLabelCol)
  359. else:
  360. self.adv_conn.SetLabel(self.infoCollapseLabelExp)
  361. self.Layout()
  362. self.SetMaxSize((-1, self.GetBestSize()[1]))
  363. self.SendSizeEvent()
  364. self.Fit()
  365. class AddWSDialog(WSDialogBase):
  366. """Dialog for adding web service layer."""
  367. def __init__(self, parent, giface, id = wx.ID_ANY,
  368. style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
  369. WSDialogBase.__init__(self, parent, id = wx.ID_ANY,
  370. style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs)
  371. self.SetTitle(_("Add web service layer"))
  372. self.parent = parent
  373. self.giface = giface
  374. self.btn_connect.SetDefault()
  375. def _createWidgets(self):
  376. WSDialogBase._createWidgets(self)
  377. self.btn_add = wx.Button(parent = self, id = wx.ID_ANY, label = _("&Add layer"))
  378. self.btn_add.SetToolTipString(_("Add selected web service layers as map layer into layer tree"))
  379. self.btn_add.Enable(False)
  380. self.run_btns.append(self.btn_add)
  381. def _doLayout(self):
  382. WSDialogBase._doLayout(self)
  383. self.btnsizer.Add(item = self.btn_add, proportion = 0,
  384. flag = wx.ALL | wx.ALIGN_CENTER,
  385. border = 10)
  386. # bindings
  387. self.btn_add.Bind(wx.EVT_BUTTON, self.OnAddLayer)
  388. def UpdateDialogAfterConnection(self):
  389. """Connect to the server
  390. """
  391. WSDialogBase.UpdateDialogAfterConnection(self)
  392. if self._getConnectedWS():
  393. self.btn_add.SetDefault()
  394. else:
  395. self.btn_connect.SetDefault()
  396. def OnAddLayer(self, event):
  397. """Add web service layer.
  398. """
  399. # add layer
  400. if self.active_ws_panel is None:
  401. return
  402. lcmd = self.active_ws_panel.CreateCmd()
  403. if not lcmd:
  404. return None
  405. # TODO: It is not clear how to do GetOptData in giface
  406. # knowing what GetOptData is doing might help
  407. # (maybe Get... is not the right name)
  408. # please fix giface if you know
  409. # tree -> giface
  410. # GetLayerTree -> GetLayerList
  411. # AddLayer -> AddLayer (but tree ones returns some layer,
  412. # giface ones nothing)
  413. # GetLayerInfo -> Layer object can by used instead
  414. # GetOptData -> unknown
  415. ltree = self.giface.GetLayerTree()
  416. active_ws = self.active_ws_panel.GetWebService()
  417. if 'WMS' not in active_ws:
  418. cap_file = self.active_ws_panel.GetCapFile()
  419. cmd_cap_file = grass.tempfile()
  420. shutil.copyfile(cap_file, cmd_cap_file)
  421. lcmd.append('capfile=' + cmd_cap_file)
  422. layer = ltree.AddLayer(ltype = 'wms',
  423. lname = self.active_ws_panel.GetOutputLayerName(),
  424. lchecked = True, lcmd = lcmd)
  425. ws_cap_files = self._getCapFiles()
  426. # create properties dialog
  427. cmd_list = ltree.GetLayerInfo(layer,'cmd')
  428. cmd = cmdlist_to_tuple(cmd_list)
  429. prop_win = WSPropertiesDialog(parent = self.parent,
  430. giface = self.giface,
  431. id = wx.ID_ANY,
  432. layer = layer,
  433. ws_cap_files = ws_cap_files,
  434. cmd = cmd)
  435. prop_win.Hide()
  436. ltree.GetOptData(dcmd = None, layer = layer,
  437. params = None, propwin = prop_win)
  438. class WSPropertiesDialog(WSDialogBase):
  439. """Dialog for editing web service properties."""
  440. def __init__(self, parent, giface, layer, ws_cap_files, cmd, id = wx.ID_ANY,
  441. style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
  442. """
  443. :param giface: grass interface
  444. :param layer: layer tree item
  445. :param ws_cap_files: dict web service('WMS_1.1.1', 'WMS_1.3.0',
  446. 'WMTS', 'OnEarth') : cap file path cap files, which will be parsed
  447. :param cmd: cmd to which dialog widgets will be initialized if
  448. it is possible (cmp parameters exists in parsed web service cap_file)
  449. """
  450. WSDialogBase.__init__(self, parent, id = wx.ID_ANY,
  451. style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs)
  452. self.SetTitle(_("Web service layer properties"))
  453. self.layer = layer
  454. self.giface = giface
  455. # after web service panels are connected, set dialog widgets
  456. # according to cmd in this variable (if it is not None)
  457. self.cmd_to_set = None
  458. # store data needed for reverting
  459. self.revert_ws_cap_files = {}
  460. self.revert_cmd = cmd
  461. ws_cap = self._getWSfromCmd(cmd)
  462. for ws in self.ws_panels.iterkeys():
  463. # cap file used in cmd will be deleted, thnaks to the dialogs destructor
  464. if ws == ws_cap and cmd[1].has_key('capfile'):
  465. self.revert_ws_cap_files[ws] = cmd[1]['capfile']
  466. del ws_cap_files[ws]
  467. else:
  468. self.revert_ws_cap_files[ws] = grass.tempfile()
  469. self._setRevertCapFiles(ws_cap_files)
  470. self.LoadCapFiles(ws_cap_files = self.revert_ws_cap_files, cmd = cmd)
  471. self.btn_ok.SetDefault()
  472. def __del__(self):
  473. for f in self.revert_ws_cap_files.itervalues():
  474. grass.try_remove(f)
  475. def _setRevertCapFiles(self, ws_cap_files):
  476. for ws, f in ws_cap_files.iteritems():
  477. if os.path.isfile(ws_cap_files[ws]):
  478. shutil.copyfile(f, self.revert_ws_cap_files[ws])
  479. else:
  480. # delete file content
  481. f_o = open(f, 'w')
  482. f_o.close()
  483. def _createWidgets(self):
  484. WSDialogBase._createWidgets(self)
  485. self.btn_apply = wx.Button(parent = self, id = wx.ID_ANY, label = _("&Apply"))
  486. self.btn_apply.SetToolTipString(_("Apply changes"))
  487. self.btn_apply.Enable(False)
  488. self.run_btns.append(self.btn_apply)
  489. self.btn_ok = wx.Button(parent = self, id = wx.ID_ANY, label = _("&OK"))
  490. self.btn_ok.SetToolTipString(_("Apply changes and close dialog"))
  491. self.btn_ok.Enable(False)
  492. self.run_btns.append(self.btn_ok)
  493. def _doLayout(self):
  494. WSDialogBase._doLayout(self)
  495. self.btnsizer.Add(item = self.btn_apply, proportion = 0,
  496. flag = wx.ALL | wx.ALIGN_CENTER,
  497. border = 10)
  498. self.btnsizer.Add(item = self.btn_ok, proportion = 0,
  499. flag = wx.ALL | wx.ALIGN_CENTER,
  500. border = 10)
  501. # bindings
  502. self.btn_apply.Bind(wx.EVT_BUTTON, self.OnApply)
  503. self.btn_ok.Bind(wx.EVT_BUTTON, self.OnSave)
  504. def LoadCapFiles(self, ws_cap_files, cmd):
  505. """Parse cap files and update dialog.
  506. For parameters description, see the constructor.
  507. """
  508. self.ch_ws_sizer.Clear(deleteWindows = True)
  509. self.cmd_to_set = cmd
  510. self.finished_panels_num = 0
  511. conn = self._getServerConnFromCmd(cmd)
  512. self.server.SetValue(conn['url'])
  513. self.password.SetValue(conn['password'])
  514. self.username.SetValue(conn['username'])
  515. self.layerName.SetValue(cmd[1]['map'])
  516. for ws, data in self.ws_panels.iteritems():
  517. cap_file = None
  518. if ws_cap_files.has_key(ws):
  519. cap_file = ws_cap_files[ws]
  520. data['panel'].ParseCapFile(url = conn['url'],
  521. username = conn['password'],
  522. password = conn['username'],
  523. cap_file = cap_file)
  524. def _getServerConnFromCmd(self, cmd):
  525. """Get url/server/passwod from cmd tuple
  526. """
  527. conn = { 'url' : '', 'username' : '', 'password' : ''}
  528. for k in conn.iterkeys():
  529. if cmd[1].has_key(k):
  530. conn[k] = cmd[1][k]
  531. return conn
  532. def _apply(self):
  533. """Apply chosen values from widgets to web service layer."""
  534. lcmd = self.active_ws_panel.CreateCmd()
  535. if not lcmd:
  536. return
  537. active_ws = self.active_ws_panel.GetWebService()
  538. if 'WMS' not in active_ws:
  539. lcmd.append('capfile=' + self.revert_ws_cap_files[active_ws])
  540. self.giface.GetLayerTree().GetOptData(dcmd = lcmd,
  541. layer = self.layer,
  542. params = None,
  543. propwin = self)
  544. #TODO use just list or tuple
  545. cmd = cmdlist_to_tuple(lcmd)
  546. self.revert_cmd = cmd
  547. self._setRevertCapFiles(self._getCapFiles())
  548. self.giface.updateMap.emit()
  549. def UpdateDialogAfterConnection(self):
  550. """Connect to the server
  551. """
  552. WSDialogBase.UpdateDialogAfterConnection(self)
  553. if self._getConnectedWS():
  554. self.btn_ok.SetDefault()
  555. else:
  556. self.btn_connect.SetDefault()
  557. def OnApply(self, event):
  558. self._apply()
  559. def OnSave(self, event):
  560. self._apply()
  561. self._close()
  562. def OnClose(self, event):
  563. """Close dialog"""
  564. self._close()
  565. def _close(self):
  566. """Hide dialog"""
  567. self.Hide()
  568. self.LoadCapFiles(cmd = self.revert_cmd,
  569. ws_cap_files = self.revert_ws_cap_files)
  570. def OnPanelCapParsed(self, error_msg):
  571. """Called when panel has downloaded and parsed capabilities file.
  572. """
  573. WSDialogBase.OnPanelCapParsed(self, error_msg)
  574. if self.finished_panels_num == len(self.ws_panels):
  575. if self.cmd_to_set:
  576. self._updateWsPanelWidgetsByCmd(self.cmd_to_set)
  577. self.cmd_to_set = None
  578. def _updateWsPanelWidgetsByCmd(self, cmd):
  579. """Set values of widgets according to parameters in cmd.
  580. """
  581. ws = self._getWSfromCmd(cmd)
  582. if self.ws_panels[ws]['panel'].IsConnected():
  583. self.ws_panels[ws]['panel'].UpdateWidgetsByCmd(cmd)
  584. self.choose_ws_rb.SetStringSelection(self.ws_panels[ws]['label'])
  585. self._showWsPanel(ws)
  586. def _getWSfromCmd(self, cmd):
  587. driver = cmd[1]['driver']
  588. ws = driver.split('_')[0]
  589. if ws == 'WMS':
  590. ws += '_' + cmd[1]['wms_version']
  591. return ws
  592. class SaveWMSLayerDialog(wx.Dialog):
  593. """Dialog for saving web service layer into GRASS vector/raster layer.
  594. .. todo::
  595. Implement saving data in region of map display.
  596. """
  597. def __init__(self, parent, layer, giface):
  598. wx.Dialog.__init__(self, parent = parent, title = ("Save web service layer as raster map"), id = wx.ID_ANY)
  599. self.layer = layer
  600. self._giface = giface
  601. self.cmd = self.layer.GetCmd()
  602. self.thread = CmdThread(self)
  603. self.cmdStdErr = GStderr(self)
  604. self._createWidgets()
  605. def _createWidgets(self):
  606. self.labels = {}
  607. self.params = {}
  608. self.labels['output'] = wx.StaticText(parent = self, id = wx.ID_ANY, label = _("Name for output raster map:"))
  609. self.params['output'] = Select(parent = self, type = 'raster', mapsets = [grass.gisenv()['MAPSET']],
  610. size = globalvar.DIALOG_GSELECT_SIZE)
  611. self.regionStBoxLabel = wx.StaticBox(parent = self, id = wx.ID_ANY,
  612. label = " %s " % _("Export region"))
  613. self.region_types_order = ['display', 'comp', 'named']
  614. self.region_types = {}
  615. self.region_types['display'] = wx.RadioButton(parent=self, label=_("Map display"),
  616. style=wx.RB_GROUP)
  617. self.region_types['comp'] = wx.RadioButton(parent=self, label=_("Computational region"))
  618. self.region_types['named'] = wx.RadioButton(parent=self, label=_("Named region"))
  619. self.region_types['display'].SetToolTipString(_("Extent and resolution"
  620. " are based on Map Display geometry."))
  621. self.region_types['comp'].SetToolTipString(_("Extent and resolution"
  622. " are based on computational region."))
  623. self.region_types['named'].SetToolTipString(_("Extent and resolution"
  624. " are based on named region."))
  625. self.region_types['display'].SetValue(True) # set default as map display
  626. self.overwrite = wx.CheckBox(parent = self, id = wx.ID_ANY,
  627. label = _("Overwrite existing raster map"))
  628. self.named_reg_panel = wx.Panel(parent = self, id = wx.ID_ANY)
  629. self.labels['region'] = wx.StaticText(parent = self.named_reg_panel, id = wx.ID_ANY,
  630. label = _("Choose named region:"))
  631. self.params['region'] = Select(parent = self.named_reg_panel, type = 'region',
  632. size = globalvar.DIALOG_GSELECT_SIZE)
  633. # buttons
  634. self.btn_close = wx.Button(parent = self, id = wx.ID_CLOSE)
  635. self.btn_close.SetToolTipString(_("Close dialog"))
  636. self.btn_ok = wx.Button(parent=self, id=wx.ID_OK, label=_("&Save layer"))
  637. self.btn_ok.SetToolTipString(_("Save web service layer as raster map"))
  638. # statusbar
  639. self.statusbar = wx.StatusBar(parent = self, id = wx.ID_ANY)
  640. self._layout()
  641. def _layout(self):
  642. self._border = wx.BoxSizer(wx.VERTICAL)
  643. dialogSizer = wx.BoxSizer(wx.VERTICAL)
  644. regionSizer = wx.BoxSizer(wx.HORIZONTAL)
  645. dialogSizer.Add(item = self._addSelectSizer(title = self.labels['output'],
  646. sel = self.params['output']))
  647. regionSizer = wx.StaticBoxSizer(self.regionStBoxLabel, wx.VERTICAL)
  648. regionTypeSizer = wx.BoxSizer(wx.HORIZONTAL)
  649. for r_type in self.region_types_order:
  650. regionTypeSizer.Add(item=self.region_types[r_type], flag=wx.RIGHT, border=8)
  651. regionSizer.Add(item = regionTypeSizer)
  652. self.named_reg_panel.SetSizer(self._addSelectSizer(title = self.labels['region'],
  653. sel = self.params['region']))
  654. regionSizer.Add(item = self.named_reg_panel)
  655. self.named_reg_panel.Hide()
  656. dialogSizer.Add(item = regionSizer, flag = wx.EXPAND)
  657. dialogSizer.Add(item=self.overwrite, flag=wx.TOP, border=10)
  658. # buttons
  659. self.btnsizer = wx.BoxSizer(orient = wx.HORIZONTAL)
  660. self.btnsizer.Add(item = self.btn_close, proportion = 0,
  661. flag = wx.ALL | wx.ALIGN_CENTER,
  662. border = 10)
  663. self.btnsizer.Add(item = self.btn_ok, proportion = 0,
  664. flag = wx.ALL | wx.ALIGN_CENTER,
  665. border = 10)
  666. dialogSizer.Add(item = self.btnsizer, proportion = 0,
  667. flag = wx.ALIGN_CENTER)
  668. self._border.Add(item=dialogSizer, proportion=0,
  669. flag=wx.ALL, border=5)
  670. self._border.Add(item=self.statusbar, proportion=0)
  671. self.SetSizer(self._border)
  672. self.Layout()
  673. self.Fit()
  674. # bindings
  675. self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose)
  676. self.btn_ok.Bind(wx.EVT_BUTTON, self.OnSave)
  677. self.Bind(EVT_CMD_DONE, self.OnCmdDone)
  678. self.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
  679. for r_type in self.region_types_order:
  680. self.Bind(wx.EVT_RADIOBUTTON, self.OnRegionType, self.region_types[r_type])
  681. def _addSelectSizer(self, title, sel):
  682. """Helper layout function.
  683. """
  684. selSizer = wx.BoxSizer(orient = wx.VERTICAL)
  685. selTitleSizer = wx.BoxSizer(wx.HORIZONTAL)
  686. selTitleSizer.Add(item = title, proportion = 1,
  687. flag = wx.LEFT | wx.TOP | wx.EXPAND, border = 5)
  688. selSizer.Add(item = selTitleSizer, proportion = 0,
  689. flag = wx.EXPAND)
  690. selSizer.Add(item = sel, proportion = 1,
  691. flag = wx.EXPAND | wx.ALL| wx.ALIGN_CENTER_VERTICAL,
  692. border = 5)
  693. return selSizer
  694. def OnClose(self, event):
  695. """Close dialog
  696. """
  697. if not self.IsModal():
  698. self.Destroy()
  699. event.Skip()
  700. def OnRegionType(self, event):
  701. selected = event.GetEventObject()
  702. if selected == self.region_types['named']:
  703. self.named_reg_panel.Show()
  704. else:
  705. self.named_reg_panel.Hide()
  706. self._border.Layout()
  707. self.Fit()
  708. def OnSave(self, event):
  709. """Import WMS raster data into GRASS as raster layer.
  710. """
  711. self.thread.abort(abortall = True)
  712. currmapset = grass.gisenv()['MAPSET']
  713. self.output = self.params['output'].GetValue().strip()
  714. l_spl = self.output.strip().split("@")
  715. # check output layer
  716. msg = None
  717. if not self.output:
  718. msg = _('Missing output raster.')
  719. elif len(l_spl) > 1 and \
  720. l_spl[1] != currmapset:
  721. msg = _('Output map can be added only to current mapset.')
  722. elif not self.overwrite.IsChecked() and\
  723. grass.find_file(self.output, 'cell', '.')['fullname']:
  724. msg = _('Output map <%s> already exists' % self.output)
  725. if msg:
  726. GMessage(parent = self,
  727. message = msg)
  728. return
  729. self.output = l_spl[0]
  730. # check region
  731. region = self.params['region'].GetValue().strip()
  732. reg_spl = region.strip().split("@")
  733. reg_mapset = '.'
  734. if len(reg_spl) > 1:
  735. reg_mapset = reg_spl[1]
  736. if self.region_types['named'].GetValue():
  737. if not grass.find_file(reg_spl[0], 'windows', reg_mapset)['fullname']:
  738. msg = _('Region <%s> does not exist.' % self.params['region'].GetValue())
  739. GWarning(parent=self,
  740. message=msg)
  741. return
  742. # create r.in.wms command
  743. cmd = ('r.in.wms', deepcopy(self.cmd[1]))
  744. if cmd[1].has_key('map'):
  745. del cmd[1]['map']
  746. cmd[1]['output'] = self.output
  747. if self.overwrite.IsChecked():
  748. cmd[1]['overwrite'] = True
  749. env = os.environ.copy()
  750. if self.region_types['named'].GetValue():
  751. cmd[1]['region'] = region
  752. elif self.region_types['display'].GetValue():
  753. region = self._giface.GetMapWindow().GetMap().SetRegion()
  754. env['GRASS_REGION'] = region
  755. cmdList = cmdtuple_to_list(cmd)
  756. self.currentPid = self.thread.GetId()
  757. self.thread.RunCmd(cmdList, env=env, stderr=self.cmdStdErr)
  758. self.statusbar.SetStatusText(_("Downloading data..."))
  759. def OnCmdDone(self, event):
  760. """When data are fetched.
  761. """
  762. if event.pid != self.currentPid:
  763. return
  764. self._addLayer()
  765. self.statusbar.SetStatusText("")
  766. def _addLayer(self):
  767. """Add layer into layer tree.
  768. """
  769. llist = self._giface.GetLayerList()
  770. if len(llist.GetLayersByName(self.output)) == 0:
  771. cmd = ['d.rast', 'map=' + self.output]
  772. llist.AddLayer(ltype='raster',
  773. name=self.output,
  774. cmd=cmd,
  775. checked=True)
  776. def OnCmdOutput(self, event):
  777. """Handle cmd output according to debug level.
  778. """
  779. if Debug.GetLevel() == 0:
  780. if event.type == 'error':
  781. msg = _('Unable to fetch data.\n')
  782. msg += event.text
  783. GWarning(parent = self,
  784. message = msg)
  785. else:
  786. Debug.msg(1, event.text)