query.py 9.2 KB

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