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