dialogs.py 36 KB

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