query.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. """
  2. @package gui_core.query
  3. @brief wxGUI query dialog
  4. Classes:
  5. - query::QueryDialog
  6. (C) 2013 by the GRASS Development Team
  7. This program is free software under the GNU General Public License
  8. (>=v2). Read the file COPYING that comes with GRASS for details.
  9. @author Anna Kratochvilova <kratochanna gmail.com>
  10. """
  11. import os
  12. import wx
  13. import six
  14. from core.gcmd import DecodeString
  15. from gui_core.treeview import TreeListView
  16. from gui_core.wrap import Button, StaticText, Menu, NewId
  17. from core.treemodel import TreeModel, DictNode
  18. from grass.pydispatch.signal import Signal
  19. class QueryDialog(wx.Dialog):
  20. def __init__(self, parent, data=None):
  21. wx.Dialog.__init__(self, parent, id=wx.ID_ANY,
  22. title=_("Query results"),
  23. size=(420, 400),
  24. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
  25. # send query output to console
  26. self.redirectOutput = Signal('QueryDialog.redirectOutput')
  27. self.data = data
  28. self.panel = wx.Panel(self, id=wx.ID_ANY)
  29. self.mainSizer = wx.BoxSizer(wx.VERTICAL)
  30. helpText = StaticText(self.panel, wx.ID_ANY, label=_(
  31. "Right click to copy selected values to clipboard."))
  32. helpText.SetForegroundColour(
  33. wx.SystemSettings.GetColour(
  34. wx.SYS_COLOUR_GRAYTEXT))
  35. self.mainSizer.Add(helpText, proportion=0, flag=wx.ALL, border=5)
  36. self._colNames = [_("Feature"), _("Value")]
  37. self._model = QueryTreeBuilder(self.data, column=self._colNames[1])
  38. self.tree = TreeListView(model=self._model, parent=self.panel,
  39. columns=self._colNames,
  40. style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT |
  41. wx.TR_FULL_ROW_HIGHLIGHT | wx.TR_MULTIPLE)
  42. self.tree.SetColumnWidth(0, 220)
  43. self.tree.SetColumnWidth(1, 1000)
  44. self.tree.ExpandAll(self._model.root)
  45. self.tree.RefreshItems()
  46. self.tree.contextMenu.connect(self.ShowContextMenu)
  47. self.mainSizer.Add(
  48. self.tree,
  49. proportion=1,
  50. flag=wx.EXPAND | wx.ALL,
  51. border=5)
  52. close = Button(self.panel, id=wx.ID_CLOSE)
  53. close.Bind(wx.EVT_BUTTON, lambda event: self.Close())
  54. copy = Button(
  55. self.panel,
  56. id=wx.ID_ANY,
  57. label=_("Copy all to clipboard"))
  58. copy.Bind(wx.EVT_BUTTON, self.Copy)
  59. self.Bind(wx.EVT_CLOSE, self.OnClose)
  60. self.redirect = wx.CheckBox(self.panel, label=_("Redirect to console"))
  61. self.redirect.SetValue(False)
  62. self.redirect.Bind(
  63. wx.EVT_CHECKBOX,
  64. lambda evt: self._onRedirect(
  65. evt.IsChecked()))
  66. hbox = wx.BoxSizer(wx.HORIZONTAL)
  67. hbox.Add(
  68. self.redirect,
  69. proportion=0,
  70. flag=wx.EXPAND | wx.RIGHT,
  71. border=5)
  72. hbox.AddStretchSpacer(1)
  73. hbox.Add(copy, proportion=0, flag=wx.EXPAND | wx.RIGHT, border=5)
  74. hbox.Add(close, proportion=0, flag=wx.EXPAND | wx.ALL, border=0)
  75. self.mainSizer.Add(
  76. hbox,
  77. proportion=0,
  78. flag=wx.EXPAND | wx.ALL,
  79. border=5)
  80. self.panel.SetSizer(self.mainSizer)
  81. self.mainSizer.Fit(self.panel)
  82. # for Windows
  83. self.SendSizeEvent()
  84. def SetData(self, data):
  85. state = self.tree.GetExpansionState()
  86. self.data = data
  87. self._model = QueryTreeBuilder(self.data, column=self._colNames[1])
  88. self.tree.SetModel(self._model)
  89. self.tree.SetExpansionState(state)
  90. if self.redirect.IsChecked():
  91. self.redirectOutput.emit(output=self._textToRedirect())
  92. def Copy(self, event):
  93. text = printResults(self._model, self._colNames[1])
  94. self._copyText(text)
  95. def ShowContextMenu(self, node):
  96. """Show context menu.
  97. Menu for copying distinguishes single and multiple selection.
  98. """
  99. nodes = self.tree.GetSelected()
  100. if not nodes:
  101. return
  102. menu = Menu()
  103. texts = []
  104. if len(nodes) > 1:
  105. values = []
  106. for node in nodes:
  107. values.append(
  108. (node.label, node.data[
  109. self._colNames[1]] if node.data else ''))
  110. col1 = '\n'.join([val[1] for val in values if val[1]])
  111. col2 = '\n'.join([val[0] for val in values if val[0]])
  112. table = '\n'.join([val[0] + ': ' + val[1] for val in values])
  113. texts.append(
  114. (_("Copy from '%s' column") %
  115. self._colNames[1], col1))
  116. texts.append(
  117. (_("Copy from '%s' column") %
  118. self._colNames[0], col2))
  119. texts.append((_("Copy selected lines"), table))
  120. else:
  121. label1 = nodes[0].label
  122. texts.append((_("Copy '%s'" % self._cutLabel(label1)), label1))
  123. if nodes[0].data and nodes[0].data[self._colNames[1]]:
  124. label2 = nodes[0].data[self._colNames[1]]
  125. texts.insert(
  126. 0, (_(
  127. "Copy '%s'" %
  128. self._cutLabel(label2)), label2))
  129. texts.append((_("Copy line"), label1 + ': ' + label2))
  130. ids = []
  131. for text in texts:
  132. id = NewId()
  133. ids.append(id)
  134. self.Bind(
  135. wx.EVT_MENU,
  136. lambda evt,
  137. t=text[1],
  138. id=id: self._copyText(t),
  139. id=id)
  140. menu.Append(id, text[0])
  141. # show the popup menu
  142. self.PopupMenu(menu)
  143. menu.Destroy()
  144. for id in ids:
  145. self.Unbind(wx.EVT_MENU, id=id)
  146. def _onRedirect(self, redirect):
  147. """Emits instructions to redirect query results.
  148. :param redirect: True to start redirecting, False to stop
  149. """
  150. if redirect:
  151. self.redirectOutput.emit(output=_("Query results:"), style='cmd')
  152. self.redirectOutput.emit(output=self._textToRedirect())
  153. else:
  154. self.redirectOutput.emit(output=_(" "), style='cmd')
  155. def _textToRedirect(self):
  156. text = printResults(self._model, self._colNames[1])
  157. text += '\n' + "-" * 50 + '\n'
  158. return text
  159. def _cutLabel(self, label):
  160. limit = 15
  161. if len(label) > limit:
  162. return label[:limit] + '...'
  163. return label
  164. def _copyText(self, text):
  165. """Helper function for copying"""
  166. if wx.TheClipboard.Open():
  167. do = wx.TextDataObject()
  168. do.SetText(text)
  169. wx.TheClipboard.SetData(do)
  170. wx.TheClipboard.Close()
  171. def OnClose(self, event):
  172. if self.redirect.IsChecked():
  173. self._onRedirect(False)
  174. self.Destroy()
  175. event.Skip()
  176. def QueryTreeBuilder(data, column):
  177. """Builds tree model from query results.
  178. :param data: query results as a dictionary
  179. :param column: column name
  180. :return: tree model
  181. """
  182. def addNode(parent, data, model):
  183. for k, v in six.iteritems(data):
  184. if isinstance(v, dict):
  185. node = model.AppendNode(parent=parent, data={"label": k})
  186. addNode(parent=node, data=v, model=model)
  187. else:
  188. if not isinstance(v, six.string_types):
  189. v = str(v)
  190. node = model.AppendNode(parent=parent,
  191. data={"label": k, column: v})
  192. model = TreeModel(DictNode)
  193. for part in data:
  194. addNode(parent=model.root, data=part, model=model)
  195. return model
  196. def printResults(model, valueCol):
  197. """Print all results to string.
  198. :param model: results tree model
  199. :param valueCol: column name with value to be printed
  200. """
  201. def printTree(node, textList, valueCol, indent=0):
  202. if node.data.get(valueCol, '') or node.children:
  203. textList.append(
  204. indent * ' ' + node.label + ': ' + node.data.get(valueCol, ''))
  205. for child in node.children:
  206. printTree(
  207. node=child,
  208. textList=textList,
  209. valueCol=valueCol,
  210. indent=indent + 2)
  211. textList = []
  212. for child in model.root.children:
  213. printTree(node=child, textList=textList, valueCol=valueCol)
  214. return '\n'.join(textList)
  215. def PrepareQueryResults(coordinates, result):
  216. """Prepare query results as a Query dialog input.
  217. Adds coordinates, improves vector results tree structure.
  218. """
  219. data = []
  220. data.append({_("east, north"): ", ".join(map(str, coordinates))})
  221. for part in result:
  222. if 'Map' in part:
  223. itemText = part['Map']
  224. if 'Mapset' in part:
  225. itemText += '@' + part['Mapset']
  226. del part['Mapset']
  227. del part['Map']
  228. if part:
  229. data.append({itemText: part})
  230. else:
  231. data.append({itemText: _("Nothing found")})
  232. else:
  233. data.append(part)
  234. return data
  235. def test():
  236. app = wx.App()
  237. from grass.script import vector as gvect
  238. from grass.script import raster as grast
  239. testdata1 = grast.raster_what(
  240. map=('elevation_shade@PERMANENT', 'landclass96'),
  241. coord=[(638509.051416, 224742.348346)],
  242. localized=True)
  243. testdata2 = gvect.vector_what(
  244. map=(
  245. 'firestations', 'bridges'), coord=(
  246. 633177.897487, 221352.921257), distance=10)
  247. testdata = testdata1 + testdata2
  248. data = PrepareQueryResults(
  249. coordinates=(
  250. 638509.051416,
  251. 224742.348346),
  252. result=testdata)
  253. frame = QueryDialog(parent=None, data=data)
  254. frame.ShowModal()
  255. frame.Destroy()
  256. app.MainLoop()
  257. if __name__ == "__main__":
  258. test()