00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035 from __future__ import division
00036
00037 from collections import OrderedDict
00038 import os
00039 import time
00040
00041 import dynamic_reconfigure as dyn_reconf
00042 from python_qt_binding import loadUi
00043 from python_qt_binding.QtCore import Qt, Signal
00044 try:
00045 from python_qt_binding.QtCore import QItemSelectionModel
00046 except ImportError:
00047 from python_qt_binding.QtGui import QItemSelectionModel
00048 from python_qt_binding.QtWidgets import QHeaderView, QWidget
00049 import rospy
00050 from rospy.exceptions import ROSException
00051 import rosservice
00052
00053 from rqt_py_common.rqt_ros_graph import RqtRosGraph
00054 from rqt_reconfigure.filter_children_model import FilterChildrenModel
00055 from rqt_reconfigure.treenode_qstditem import TreenodeQstdItem
00056 from rqt_reconfigure.treenode_item_model import TreenodeItemModel
00057
00058 from rqt_reconfigure.dynreconf_client_widget import DynreconfClientWidget
00059
00060
00061 class NodeSelectorWidget(QWidget):
00062 _COL_NAMES = ['Node']
00063
00064
00065 sig_node_selected = Signal(DynreconfClientWidget)
00066
00067 def __init__(self, parent, rospack, signal_msg=None):
00068 """
00069 @param signal_msg: Signal to carries a system msg that is shown on GUI.
00070 @type signal_msg: QtCore.Signal
00071 """
00072 super(NodeSelectorWidget, self).__init__()
00073 self._parent = parent
00074 self.stretch = None
00075 self._signal_msg = signal_msg
00076
00077 ui_file = os.path.join(rospack.get_path('rqt_reconfigure'), 'resource',
00078 'node_selector.ui')
00079 loadUi(ui_file, self)
00080
00081
00082
00083
00084 self._nodeitems = OrderedDict()
00085
00086
00087
00088
00089
00090 self._item_model = TreenodeItemModel()
00091 self._rootitem = self._item_model.invisibleRootItem()
00092
00093 self._nodes_previous = None
00094
00095
00096
00097 self._update_nodetree_pernode()
00098
00099
00100
00101
00102
00103
00104 self._collapse_button.pressed.connect(
00105 self._node_selector_view.collapseAll)
00106 self._expand_button.pressed.connect(self._node_selector_view.expandAll)
00107 self._refresh_button.pressed.connect(self._refresh_nodes)
00108
00109
00110 self._proxy_model = FilterChildrenModel(self)
00111 self._proxy_model.setDynamicSortFilter(True)
00112 self._proxy_model.setSourceModel(self._item_model)
00113 self._node_selector_view.setModel(self._proxy_model)
00114 self._filterkey_prev = ''
00115
00116
00117
00118
00119 try:
00120 self._node_selector_view.header().setResizeMode(
00121 0, QHeaderView.ResizeToContents)
00122 except AttributeError:
00123
00124
00125 pass
00126
00127
00128 self.selectionModel = self._node_selector_view.selectionModel()
00129
00130
00131 self.selectionModel.selectionChanged.connect(
00132 self._selection_changed_slot)
00133
00134 def node_deselected(self, grn):
00135 """
00136 Deselect the index that corresponds to the given GRN.
00137
00138 :type grn: str
00139 """
00140
00141
00142 qindex_tobe_deselected = self._item_model.get_index_from_grn(grn)
00143 rospy.logdebug('NodeSelWidt node_deselected qindex={} data={}'.format(
00144 qindex_tobe_deselected,
00145 qindex_tobe_deselected.data(Qt.DisplayRole)))
00146
00147
00148 indexes_selected = self.selectionModel.selectedIndexes()
00149 for index in indexes_selected:
00150 grn_from_selectedindex = RqtRosGraph.get_upper_grn(index, '')
00151 rospy.logdebug(' Compare given grn={} grn from selected={}'.format(
00152 grn, grn_from_selectedindex))
00153
00154 if grn == grn_from_selectedindex:
00155
00156 self.selectionModel.select(index, QItemSelectionModel.Deselect)
00157
00158 def node_selected(self, grn):
00159 """
00160 Select the index that corresponds to the given GRN.
00161
00162 :type grn: str
00163 """
00164
00165
00166 qindex_tobe_selected = self._item_model.get_index_from_grn(grn)
00167 rospy.logdebug('NodeSelWidt node_selected qindex={} data={}'.format(
00168 qindex_tobe_selected,
00169 qindex_tobe_selected.data(Qt.DisplayRole)))
00170
00171
00172
00173 if qindex_tobe_selected:
00174 self.selectionModel.select(qindex_tobe_selected, QItemSelectionModel.Select)
00175
00176 def _selection_deselected(self, index_current, rosnode_name_selected):
00177 """
00178 Intended to be called from _selection_changed_slot.
00179 """
00180 self.selectionModel.select(index_current, QItemSelectionModel.Deselect)
00181
00182 try:
00183 reconf_widget = self._nodeitems[
00184 rosnode_name_selected].get_dynreconf_widget()
00185 except ROSException as e:
00186 raise e
00187
00188
00189 self.sig_node_selected.emit(reconf_widget)
00190
00191
00192 def _selection_selected(self, index_current, rosnode_name_selected):
00193 """Intended to be called from _selection_changed_slot."""
00194 rospy.logdebug('_selection_changed_slot row={} col={} data={}'.format(
00195 index_current.row(), index_current.column(),
00196 index_current.data(Qt.DisplayRole)))
00197
00198
00199 found_node = False
00200 for nodeitem in self._nodeitems.values():
00201 name_nodeitem = nodeitem.data(Qt.DisplayRole)
00202 name_rosnode_leaf = rosnode_name_selected[
00203 rosnode_name_selected.rfind(RqtRosGraph.DELIM_GRN) + 1:]
00204
00205
00206
00207 if ((name_nodeitem == rosnode_name_selected) and
00208 (name_nodeitem[name_nodeitem.rfind(RqtRosGraph.DELIM_GRN) + 1:]
00209 == name_rosnode_leaf)):
00210
00211 rospy.logdebug('terminal str {} MATCH {}'.format(
00212 name_nodeitem, name_rosnode_leaf))
00213 found_node = True
00214 break
00215 if not found_node:
00216 self.selectionModel.select(index_current,
00217 QItemSelectionModel.Deselect)
00218 return
00219
00220
00221
00222 item_child = self._nodeitems[rosnode_name_selected]
00223 item_widget = None
00224 try:
00225 item_widget = item_child.get_dynreconf_widget()
00226 except ROSException as e:
00227 raise e
00228 rospy.logdebug('item_selected={} child={} widget={}'.format(
00229 index_current, item_child, item_widget))
00230 self.sig_node_selected.emit(item_widget)
00231
00232
00233
00234
00235 def _selection_changed_slot(self, selected, deselected):
00236 """
00237 Sends "open ROS Node box" signal ONLY IF the selected treenode is the
00238 terminal treenode.
00239 Receives args from signal QItemSelectionModel.selectionChanged.
00240
00241 :param selected: All indexs where selected (could be multiple)
00242 :type selected: QItemSelection
00243 :type deselected: QItemSelection
00244 """
00245
00246
00247 if not selected.indexes() and not deselected.indexes():
00248 rospy.logerr('Nothing selected? Not ideal to reach here')
00249 return
00250
00251 index_current = None
00252 if selected.indexes():
00253 index_current = selected.indexes()[0]
00254 elif len(deselected.indexes()) == 1:
00255
00256
00257
00258
00259
00260 index_current = deselected.indexes()[0]
00261
00262 rospy.logdebug(' - - - index_current={}'.format(index_current))
00263
00264 rosnode_name_selected = RqtRosGraph.get_upper_grn(index_current, '')
00265
00266
00267 if not rosnode_name_selected in self._nodeitems.keys():
00268
00269 self.selectionModel.select(index_current,
00270 QItemSelectionModel.Deselect)
00271 return
00272
00273 if selected.indexes():
00274 try:
00275 self._selection_selected(index_current, rosnode_name_selected)
00276 except ROSException as e:
00277
00278 err_msg = e.message + '. Connection to node=' + \
00279 format(rosnode_name_selected) + ' failed'
00280 self._signal_msg.emit(err_msg)
00281 rospy.logerr(err_msg)
00282
00283 elif deselected.indexes():
00284 try:
00285 self._selection_deselected(index_current,
00286 rosnode_name_selected)
00287 except ROSException as e:
00288 rospy.logerr(e.message)
00289
00290
00291 def get_paramitems(self):
00292 """
00293 :rtype: OrderedDict 1st elem is node's GRN name,
00294 2nd is TreenodeQstdItem instance
00295 """
00296 return self._nodeitems
00297
00298 def _update_nodetree_pernode(self):
00299 """
00300 """
00301
00302
00303
00304
00305 try:
00306 nodes = dyn_reconf.find_reconfigure_services()
00307 except rosservice.ROSServiceIOException as e:
00308 rospy.logerr("Reconfigure GUI cannot connect to master.")
00309 raise e
00310
00311 if not nodes == self._nodes_previous:
00312 i_node_curr = 1
00313 num_nodes = len(nodes)
00314 elapsedtime_overall = 0.0
00315 for node_name_grn in nodes:
00316
00317 if node_name_grn in self._nodeitems:
00318 i_node_curr += 1
00319 continue
00320
00321 time_siglenode_loop = time.time()
00322
00323
00324
00325
00326
00327
00328
00329
00330
00331 treenodeitem_toplevel = TreenodeQstdItem(
00332 node_name_grn, TreenodeQstdItem.NODE_FULLPATH)
00333 _treenode_names = treenodeitem_toplevel.get_treenode_names()
00334
00335 try:
00336 treenodeitem_toplevel.connect_param_server()
00337 except rospy.exceptions.ROSException as e:
00338 rospy.logerr(e.message)
00339
00340 continue
00341
00342
00343
00344
00345 self._nodeitems[node_name_grn] = treenodeitem_toplevel
00346
00347 self._add_children_treenode(treenodeitem_toplevel,
00348 self._rootitem, _treenode_names)
00349
00350 time_siglenode_loop = time.time() - time_siglenode_loop
00351 elapsedtime_overall += time_siglenode_loop
00352
00353 _str_progress = 'reconf ' + \
00354 'loading #{}/{} {} / {}sec node={}'.format(
00355 i_node_curr, num_nodes, round(time_siglenode_loop, 2),
00356 round(elapsedtime_overall, 2), node_name_grn)
00357
00358
00359
00360 rospy.logdebug(_str_progress)
00361 i_node_curr += 1
00362
00363 def _add_children_treenode(self, treenodeitem_toplevel,
00364 treenodeitem_parent, child_names_left):
00365 """
00366 Evaluate current treenode and the previous treenode at the same depth.
00367 If the name of both nodes is the same, current node instance is
00368 ignored (that means children will be added to the same parent). If not,
00369 the current node gets added to the same parent node. At the end, this
00370 function gets called recursively going 1 level deeper.
00371
00372 :type treenodeitem_toplevel: TreenodeQstdItem
00373 :type treenodeitem_parent: TreenodeQstdItem.
00374 :type child_names_left: List of str
00375 :param child_names_left: List of strings that is sorted in hierarchical
00376 order of params.
00377 """
00378
00379
00380 name_currentnode = child_names_left.pop(0)
00381 grn_curr = treenodeitem_toplevel.get_raw_param_name()
00382 stditem_currentnode = TreenodeQstdItem(grn_curr,
00383 TreenodeQstdItem.NODE_FULLPATH)
00384
00385
00386 row_index_parent = treenodeitem_parent.rowCount() - 1
00387
00388
00389 name_prev = ''
00390 stditem_prev = None
00391 if treenodeitem_parent.child(row_index_parent):
00392 stditem_prev = treenodeitem_parent.child(row_index_parent)
00393 name_prev = stditem_prev.text()
00394
00395 stditem = None
00396
00397
00398 if name_prev != name_currentnode:
00399 stditem_currentnode.setText(name_currentnode)
00400
00401
00402 insert_index = 0
00403 while insert_index < treenodeitem_parent.rowCount() and treenodeitem_parent.child(insert_index).text() < name_currentnode:
00404 insert_index += 1
00405
00406 treenodeitem_parent.insertRow(insert_index, stditem_currentnode)
00407 stditem = stditem_currentnode
00408 else:
00409 stditem = stditem_prev
00410
00411 if child_names_left:
00412
00413
00414
00415 self._add_children_treenode(treenodeitem_toplevel, stditem,
00416 child_names_left)
00417 else:
00418
00419 self._item_model.set_item_from_index(grn_curr, stditem.index())
00420
00421 def _prune_nodetree_pernode(self):
00422 try:
00423 nodes = dyn_reconf.find_reconfigure_services()
00424 except rosservice.ROSServiceIOException as e:
00425 rospy.logerr("Reconfigure GUI cannot connect to master.")
00426 raise e
00427
00428 for i in reversed(range(0, self._rootitem.rowCount())):
00429 candidate_for_removal = self._rootitem.child(i).get_raw_param_name()
00430 if not candidate_for_removal in nodes:
00431 rospy.logdebug('Removing {} because the server is no longer available.'.format(
00432 candidate_for_removal))
00433 self._nodeitems[candidate_for_removal].disconnect_param_server()
00434 self._rootitem.removeRow(i)
00435 self._nodeitems.pop(candidate_for_removal)
00436
00437 def _refresh_nodes(self):
00438 self._prune_nodetree_pernode()
00439 self._update_nodetree_pernode()
00440
00441 def close_node(self):
00442 rospy.logdebug(" in close_node")
00443
00444
00445 def set_filter(self, filter_):
00446 """
00447 Pass fileter instance to the child proxymodel.
00448 :type filter_: BaseFilter
00449 """
00450 self._proxy_model.set_filter(filter_)
00451
00452 def _test_sel_index(self, selected, deselected):
00453 """
00454 Method for Debug only
00455 """
00456
00457 src_model = self._item_model
00458 index_current = None
00459 index_deselected = None
00460 index_parent = None
00461 curr_qstd_item = None
00462 if selected.indexes():
00463 index_current = selected.indexes()[0]
00464 index_parent = index_current.parent()
00465 curr_qstd_item = src_model.itemFromIndex(index_current)
00466 elif deselected.indexes():
00467 index_deselected = deselected.indexes()[0]
00468 index_parent = index_deselected.parent()
00469 curr_qstd_item = src_model.itemFromIndex(index_deselected)
00470
00471 if selected.indexes() > 0:
00472 rospy.logdebug('sel={} par={} desel={} sel.d={} par.d={}'.format(
00473 index_current, index_parent, index_deselected,
00474 index_current.data(Qt.DisplayRole),
00475 index_parent.data(Qt.DisplayRole),)
00476 + ' desel.d={} cur.item={}'.format(
00477 None,
00478 curr_qstd_item))
00479 elif deselected.indexes():
00480 rospy.logdebug('sel={} par={} desel={} sel.d={} par.d={}'.format(
00481 index_current, index_parent, index_deselected,
00482 None, index_parent.data(Qt.DisplayRole)) +
00483 ' desel.d={} cur.item={}'.format(
00484 index_deselected.data(Qt.DisplayRole),
00485 curr_qstd_item))