query.py 9.2 KB

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