def show_reconf(self, param_client_widget): """ Callback when user chooses a node. @param param_client_widget: """ node_grn = param_client_widget.get_node_grn() logging.debug('ParameditWidget.show' ' str(node_grn)={}'.format(str(node_grn))) if node_grn not in self._param_client_widgets.keys(): # Add param widget if there isn't already one. # Client gets renewed every time different node_grn was clicked. self._param_client_widgets.__setitem__(node_grn, param_client_widget) self.vlayout.addWidget(param_client_widget) param_client_widget.sig_node_disabled_selected.connect( self._node_disabled) else: # If there has one already existed, remove it. self._remove_node(node_grn) # LayoutUtil.clear_layout(self.vlayout) # Re-add the rest of existing items to layout. # for k, v in self._param_client_widgets.items(): # logging.info('added to layout k={} v={}'.format(k, v)) # self.vlayout.addWidget(v) # Add color to alternate the rim of the widget. LayoutUtil.alternate_color(self._param_client_widgets.values(), [ self.palette().window().color().lighter(125), self.palette().window().color().darker(125) ])
def show(self, param_client_widget): # Callback when user chooses a node. node_grn = param_client_widget.get_node_grn() logging.debug('ParameditWidget.show' ' str(node_grn)={}'.format(str(node_grn))) if node_grn not in self._param_client_widgets: self._param_client_widgets[node_grn] = param_client_widget self._vlayout.addWidget(param_client_widget) param_client_widget.sig_node_disabled_selected.connect( self._node_disabled) else: # If there has one already existed, remove it. self._remove_node(node_grn) # LayoutUtil.clear_layout(self.vlayout) """" Re-add the rest of existing items to layout. for k, v in self._param_client_widgets.items(): logging.info('added to layout k={} v={}'.format(k, v)) self.vlayout.addWidget(v) """ # Add color to alternate the rim of the widget. LayoutUtil.alternate_color(self._param_client_widgets.values(), [ self.palette().window().color().lighter(125), self.palette().window().color().darker(125) ])
def update_editor_widgets(self, parameters): for parameter in parameters: if parameter.name not in self._editor_widgets: continue logging.debug('Updating editor widget for {}'.format( parameter.name)) self._editor_widgets[parameter.name].update_local(parameter.value)
def __init__(self, reconf, node_name): """ :type reconf: dynamic_reconfigure.client :type node_name: str """ group_desc = reconf.get_group_descriptions() logging.debug('ParamClientWidget.group_desc={}'.format(group_desc)) super(ParamClientWidget, self).__init__(ParamUpdater(reconf), group_desc, node_name) # Save and load buttons self.button_widget = QWidget(self) self.button_header = QHBoxLayout(self.button_widget) self.button_header.setContentsMargins(QMargins(0, 0, 0, 0)) self.load_button = QPushButton() self.save_button = QPushButton() self.load_button.setIcon(QIcon.fromTheme('document-open')) self.save_button.setIcon(QIcon.fromTheme('document-save')) self.load_button.clicked[bool].connect(self._handle_load_clicked) self.save_button.clicked[bool].connect(self._handle_save_clicked) self.button_header.addWidget(self.save_button) self.button_header.addWidget(self.load_button) self.setMinimumWidth(150) self.reconf = reconf self.updater.start() self.reconf.config_callback = self.config_callback self._node_grn = node_name
def _test_sel_index(self, selected, deselected): # Method for Debug only. # index_current = self.selectionModel.currentIndex() src_model = self._item_model index_current = None index_deselected = None index_parent = None curr_qstd_item = None if selected.indexes(): index_current = selected.indexes()[0] index_parent = index_current.parent() curr_qstd_item = src_model.itemFromIndex(index_current) elif deselected.indexes(): index_deselected = deselected.indexes()[0] index_parent = index_deselected.parent() curr_qstd_item = src_model.itemFromIndex(index_deselected) if selected.indexes() > 0: logging.debug('sel={} par={} desel={} ' 'sel.d={} par.d={} desel.d={} cur.item={}'.format( index_current, index_parent, index_deselected, index_current.data(Qt.DisplayRole), index_parent.data(Qt.DisplayRole), None, # index_deselected.data(Qt.DisplayRole) curr_qstd_item)) elif deselected.indexes(): logging.debug('sel={} par={} desel={} ' 'sel.d={} par.d={} desel.d={} cur.item={}'.format( index_current, index_parent, index_deselected, None, index_parent.data(Qt.DisplayRole), index_deselected.data(Qt.DisplayRole), curr_qstd_item))
def __init__(self, updater, config, nodename): """ :param config: :type config: Dictionary? defined in dynamic_reconfigure.client.Client :type nodename: str """ super(GroupWidget, self).__init__() self.state = config['state'] self.param_name = config['name'] self._toplevel_treenode_name = nodename # TODO: .ui file needs to be back into usage in later phase. # ui_file = os.path.join(rp.get_path('rqt_reconfigure'), # 'resource', 'singlenode_parameditor.ui') # loadUi(ui_file, self) verticalLayout = QVBoxLayout(self) verticalLayout.setContentsMargins(QMargins(0, 0, 0, 0)) _widget_nodeheader = QWidget() _h_layout_nodeheader = QHBoxLayout(_widget_nodeheader) _h_layout_nodeheader.setContentsMargins(QMargins(0, 0, 0, 0)) self.nodename_qlabel = QLabel(self) font = QFont('Trebuchet MS, Bold') font.setUnderline(True) font.setBold(True) # Button to close a node. _icon_disable_node = QIcon.fromTheme('window-close') _bt_disable_node = QPushButton(_icon_disable_node, '', self) _bt_disable_node.setToolTip('Hide this node') _bt_disable_node_size = QSize(36, 24) _bt_disable_node.setFixedSize(_bt_disable_node_size) _bt_disable_node.pressed.connect(self._node_disable_bt_clicked) _h_layout_nodeheader.addWidget(self.nodename_qlabel) _h_layout_nodeheader.addWidget(_bt_disable_node) self.nodename_qlabel.setAlignment(Qt.AlignCenter) font.setPointSize(10) self.nodename_qlabel.setFont(font) grid_widget = QWidget(self) self.grid = QFormLayout(grid_widget) verticalLayout.addWidget(_widget_nodeheader) verticalLayout.addWidget(grid_widget, 1) # Again, these UI operation above needs to happen in .ui file. self.tab_bar = None # Every group can have one tab bar self.tab_bar_shown = False self.updater = updater self.editor_widgets = [] self._param_names = [] self._create_node_widgets(config) logging.debug('Groups node name={}'.format(nodename)) self.nodename_qlabel.setText(nodename)
def set_param_client(self, param_client): """ @param param_client: dynamic_reconfigure.client.Client """ self._param_client = param_client logging.debug('Qitem set param_client={} param={}'.format( self._param_client, self._raw_param_name ))
def set_item_from_index(self, grn, qpindex): """ :type grn: str :type qpindex: QPersistentModelIndex """ logging.debug('set_item_from_index grn={} qpindex={}'.format( grn, qpindex)) self._indexes[grn] = qpindex
def _selection_changed_slot(self, selected, deselected): """ Send "open ROS Node box" signal. ONLY IF the selected treenode is the terminal treenode. Receives args from signal QItemSelectionModel.selectionChanged. :param selected: All indexs where selected (could be multiple) :type selected: QItemSelection :type deselected: QItemSelection """ # Getting the index where user just selected. Should be single. if not selected.indexes() and not deselected.indexes(): logging.error('Nothing selected? Not ideal to reach here') return index_current = None if selected.indexes(): index_current = selected.indexes()[0] elif len(deselected.indexes()) == 1: # Setting length criteria as 1 is only a workaround, to avoid # Node boxes on right-hand side disappears when filter key doesn't # match them. # Indeed this workaround leaves another issue. Question for # permanent solution is asked here http://goo.gl/V4DT1 index_current = deselected.indexes()[0] logging.debug(' - - - index_current={}'.format(index_current)) rosnode_name_selected = RqtRosGraph.get_upper_grn(index_current, '') # If retrieved node name isn't in the list of all nodes. if rosnode_name_selected not in self._nodeitems.keys(): # De-select the selected item. self.selectionModel.select(index_current, QItemSelectionModel.Deselect) return if selected.indexes(): try: self._selection_selected(index_current, rosnode_name_selected) except Exception as e: # TODO: print to sysmsg pane err_msg = 'Connection to node={} failed:\n{}'.format( rosnode_name_selected, e) import traceback traceback.print_exc() self._signal_msg.emit(err_msg) logging.error(err_msg) elif deselected.indexes(): try: self._selection_deselected(index_current, rosnode_name_selected) except Exception as e: self._signal_msg.emit(e) logging.error(e)
def remove_editor_widgets(self, parameters): for parameter in parameters: if parameter.name not in self._editor_widgets: continue logging.debug('Removing editor widget for {}'.format( parameter.name)) self._editor_widgets[parameter.name].hide(self.grid) self._editor_widgets[parameter.name].close() del self._editor_widgets[parameter.name]
def _update_nodetree_pernode(self): """ """ # TODO(Isaac): 11/25/2012 dynamic_reconfigure only returns params that # are associated with nodes. In order to handle independent # params, different approach needs taken. try: nodes = dyn_reconf.find_reconfigure_services() except rosservice.ROSServiceIOException as e: logging.error('Reconfigure GUI cannot connect to master.') raise e # TODO Make sure 'raise' here returns or finalizes func. if not nodes == self._nodes_previous: i_node_curr = 1 num_nodes = len(nodes) elapsedtime_overall = 0.0 for node_name_grn in nodes: # Skip this grn if we already have it if node_name_grn in self._nodeitems: i_node_curr += 1 continue time_siglenode_loop = time.time() # (Begin) For DEBUG ONLY; skip some dynreconf creation # if i_node_curr % 2 != 0: # i_node_curr += 1 # continue # (End) For DEBUG ONLY. #### # Instantiate QStandardItem. Inside, dyn_reconf client will # be generated too. treenodeitem_toplevel = TreenodeQstdItem( node_name_grn, TreenodeQstdItem.NODE_FULLPATH) _treenode_names = treenodeitem_toplevel.get_treenode_names() # Using OrderedDict here is a workaround for StdItemModel # not returning corresponding item to index. self._nodeitems[node_name_grn] = treenodeitem_toplevel self._add_children_treenode(treenodeitem_toplevel, self._rootitem, _treenode_names) time_siglenode_loop = time.time() - time_siglenode_loop elapsedtime_overall += time_siglenode_loop _str_progress = 'reconf ' + \ 'loading #{}/{} {} / {}sec node={}'.format( i_node_curr, num_nodes, round(time_siglenode_loop, 2), round(elapsedtime_overall, 2), node_name_grn ) # NOT a debug print - please DO NOT remove. This print works # as progress notification when loading takes long time. logging.debug(_str_progress) i_node_curr += 1
def get_index_from_grn(self, grn): """ :type grn: str :rtype: QPersistentModelIndex. None if the corresponding item isn't found. """ logging.debug('get_index_from_grn all item={}'.format(self._indexes)) return self._indexes.get(grn)
def _node_disabled(self, node_grn): logging.debug('paramedit_w _node_disabled grn={}'.format(node_grn)) # Signal to notify other GUI components (eg. nodes tree pane) that # a node widget is disabled. self.sig_node_disabled_selected.emit(node_grn) # Remove the selected node widget from the internal list of nodes. self._remove_node(node_grn)
def add_editor_widgets(self, parameters, descriptors): for parameter, descriptor in zip(parameters, descriptors): if parameter.type_ not in EDITOR_TYPES: continue logging.debug('Adding editor widget for {}'.format(parameter.name)) editor_widget = EDITOR_TYPES[parameter.type_]( self._param_client, parameter, descriptor ) self._editor_widgets[parameter.name] = editor_widget editor_widget.display(self.grid)
def filterAcceptsRow(self, src_row, src_parent_qmindex): """ Overridden. Terminology: "Treenode" is deliberately used to avoid confusion with "Node" in ROS. :type src_row: int :type src_parent_qmindex: QModelIndex """ logging.debug('filerAcceptRow 1') return self._filter_row_recur(src_row, src_parent_qmindex)
def filterAcceptsColumn(self, source_column, source_parent): """ Overridden. Doing nothing really since columns are not in use. :type source_column: int :type source_parent: QModelIndex """ logging.debug('FCModel.filterAcceptsCol source_col={} '.format( source_column) + 'parent col={} row={} data={}'.format( source_parent.column(), source_parent.row(), source_parent.data())) return True
def _create_node_widgets(self, config): """ :type config: Dict? """ i_debug = 0 for param in config['parameters']: begin = time.time() * 1000 editor_type = '(none)' if param['edit_method']: widget = EnumEditor(self.updater, param) elif param['type'] in EDITOR_TYPES: logging.debug('GroupWidget i_debug={} param type ={}'.format( i_debug, param['type'])) editor_type = EDITOR_TYPES[param['type']] widget = eval(editor_type)(self.updater, param) self.editor_widgets.append(widget) self._param_names.append(param['name']) logging.debug( 'groups._create_node_widgets num editors={}'.format(i_debug)) end = time.time() * 1000 time_elap = end - begin logging.debug('ParamG editor={} loop=#{} Time={}msec'.format( editor_type, i_debug, time_elap)) i_debug += 1 for name, group in sorted(config['groups'].items()): if group['type'] == 'tab': widget = TabGroup(self, self.updater, group, self._toplevel_treenode_name) elif group['type'] in _GROUP_TYPES.keys(): widget = eval(_GROUP_TYPES[group['type']])( self.updater, group, self._toplevel_treenode_name) else: widget = eval(_GROUP_TYPES[''])(self.updater, group, self._toplevel_treenode_name) self.editor_widgets.append(widget) logging.debug('groups._create_node_widgets name={}'.format(name)) for i, ed in enumerate(self.editor_widgets): ed.display(self.grid) logging.debug('GroupWdgt._create_node_widgets' ' len(editor_widgets)={}'.format(len( self.editor_widgets)))
def node_selected(self, grn): """ Select the index that corresponds to the given GRN. :type grn: str """ # Obtain the corresponding index. qindex_tobe_selected = self._item_model.get_index_from_grn(grn) logging.debug('NodeSelWidt node_selected qindex={} data={}'.format( qindex_tobe_selected, qindex_tobe_selected.data(Qt.DisplayRole))) # Select the index. if qindex_tobe_selected: self.selectionModel.select(qindex_tobe_selected, QItemSelectionModel.Select)
def run(self): param_client = None try: param_client = dynamic_reconfigure.client.Client( str(self._raw_param_name), timeout=5.0) logging.debug( 'ParamserverConnectThread param_client={}'.format( param_client )) self._parent.set_param_client(param_client) except ROSException as e: raise type(e)( e.message + "TreenodeQstdItem. Couldn't connect to {}".format( self._raw_param_name ))
def node_deselected(self, grn): """ Deselect the index that corresponds to the given GRN. :type grn: str """ # Obtain all indices currently selected. indexes_selected = self.selectionModel.selectedIndexes() for index in indexes_selected: grn_from_selectedindex = RqtRosGraph.get_upper_grn(index, '') logging.debug(' Compare given grn={} from selected={}'.format( grn, grn_from_selectedindex)) # If GRN retrieved from selected index matches the given one. if grn == grn_from_selectedindex: # Deselect the index. self.selectionModel.select(index, QItemSelectionModel.Deselect)
def _prune_nodetree_pernode(self): try: nodes = find_nodes_with_params(self._context.node) except Exception as e: logging.error('Reconfigure GUI cannot connect to master.') raise e # TODO Make sure 'raise' here returns or finalizes func. for i in reversed(range(0, self._rootitem.rowCount())): candidate_for_removal = \ self._rootitem.child(i).get_raw_param_name() if candidate_for_removal not in nodes: logging.debug( 'Removing {} because the server is no longer available.'. format(candidate_for_removal)) self._rootitem.removeRow(i) self._nodeitems.pop(candidate_for_removal).reset()
def enable_param_items(self): """ Create QStdItem per parameter and addColumn them to myself. :rtype: None if _param_client is not initiated. """ if not self._param_client_widget: return None param_names = self._param_client_widget.get_treenode_names() param_names_items = [] brush = QBrush(Qt.lightGray) for param_name in param_names: item = ReadonlyItem(param_name) item.setBackground(brush) param_names_items.append(item) logging.debug('enable_param_items len of param_names={}'.format( len(param_names_items) )) self.appendColumn(param_names_items)
def node_selected(self, grn, scroll_to=False): """ Select the index that corresponds to the given GRN. :type grn: str """ # Iterate over all of the indexes for index in self._enumerate_indexes(): grn_from_index = RqtRosGraph.get_upper_grn(index, '') logging.debug(' Compare given grn={} from selected={}'.format( grn, grn_from_index)) # If GRN retrieved from selected index matches the given one. if grn == grn_from_index: # Select the index. self.selectionModel.select(index, QItemSelectionModel.Select) if scroll_to: self._node_selector_view.scrollTo(index) break
def _set_param_name(self, param_name): """ :param param_name: A string formatted as GRN (Graph Resource Names, see http://www.ros.org/wiki/Names). Example: /paramname/subpara/subsubpara/... """ logging.debug('_set_param_name param_name={} '.format(param_name)) # separate param_name by forward slash self._list_treenode_names = param_name.split('/') # Deleting the 1st elem which is zero-length str. del self._list_treenode_names[0] self._toplevel_treenode_name = self._list_treenode_names[0] logging.debug('param_name={} node_name={} _list_params[-1]={}'.format( param_name, self._toplevel_treenode_name, self._list_treenode_names[-1] ))
def config_callback(self, config): # TODO: Think about replacing callback architecture with signals. if config: # TODO: should use config.keys but this method doesnt exist names = [name for name, v in config.items()] # v isn't used but necessary to get key and put it into dict. # logging.debug('config_callback name={} v={}'.format(name, v)) for widget in self.editor_widgets: if isinstance(widget, EditorWidget): if widget.param_name in names: logging.debug('EDITOR widget.param_name={}'.format( widget.param_name)) widget.update_value(config[widget.param_name]) elif isinstance(widget, GroupWidget): cfg = find_cfg(config, widget.param_name) logging.debug('GROUP widget.param_name={}'.format( widget.param_name)) widget.update_group(cfg)
def _remove_node(self, node_grn): try: i = list(self._param_client_widgets.keys()).index(node_grn) except ValueError: # ValueError occurring here means that the specified key is not # found, most likely already removed, which is possible in the # following situation/sequence: # # Node widget on ParameditWidget removed by clicking disable button # --> Node deselected on tree widget gets updated # --> Tree widget detects deselection # --> Tree widget emits deselection signal, which is captured by # ParameditWidget's slot. Thus reaches this method again. return item = self.vlayout.itemAt(i) if isinstance(item, QWidgetItem): item.widget().close() w = self._param_client_widgets.pop(node_grn) logging.debug('popped={} Len of left clients={}'.format( w, len(self._param_client_widgets)))
def node_deselected(self, grn): """ Deselect the index that corresponds to the given GRN. :type grn: str """ # Obtain the corresponding index. qindex_tobe_deselected = self._item_model.get_index_from_grn(grn) logging.debug('NodeSelWidt node_deselected qindex={} data={}'.format( qindex_tobe_deselected, qindex_tobe_deselected.data(Qt.DisplayRole))) # Obtain all indices currently selected. indexes_selected = self.selectionModel.selectedIndexes() for index in indexes_selected: grn_from_selectedindex = RqtRosGraph.get_upper_grn(index, '') logging.debug(' Compare given grn={} grn from selected={}'.format( grn, grn_from_selectedindex)) # If GRN retrieved from selected index matches the given one. if grn == grn_from_selectedindex: # Deselect the index. self.selectionModel.select(index, QItemSelectionModel.Deselect)
def get_param_client_widget(self): """ Get the param_client_widget. @rtype: ParamClientWidget (QWidget) @return: None if param_client is not yet generated. @raise ROSException: """ if not self._param_client_widget: logging.debug('In get_param_client_widget 4') self._param_client_widget = ParamClientWidget( self._context, self._raw_param_name ) """ Creating the ParamClientWidget transfers ownership of the _param_client to it. If it is destroyed from Qt, we need to clear our reference to it and stop the param server thread we had. """ self._param_client_widget.destroyed.connect(self.reset) logging.debug('In get_param_client_widget 5') return self._param_client_widget
def _selection_selected(self, index_current, rosnode_name_selected): """Intended to be called from _selection_changed_slot.""" logging.debug('_selection_changed_slot row={} col={} data={}'.format( index_current.row(), index_current.column(), index_current.data(Qt.DisplayRole))) # Determine if it's terminal treenode. found_node = False for nodeitem in self._nodeitems.values(): name_nodeitem = nodeitem.data(Qt.DisplayRole) name_rosnode_leaf = rosnode_name_selected[ rosnode_name_selected.rfind(RqtRosGraph.DELIM_GRN) + 1:] # If name of the leaf in the given name & the name taken from # nodeitem list matches. if ((name_nodeitem == rosnode_name_selected) and (name_nodeitem[name_nodeitem.rfind(RqtRosGraph.DELIM_GRN) + 1:] == name_rosnode_leaf)): logging.debug('terminal str {} MATCH {}'.format( name_nodeitem, name_rosnode_leaf)) found_node = True break if not found_node: # Only when it's NOT a terminal we deselect it. self.selectionModel.select(index_current, QItemSelectionModel.Deselect) return # Only when it's a terminal we move forward. item_child = self._nodeitems[rosnode_name_selected] item_widget = None try: item_widget = item_child.get_param_client_widget() except ROSException as e: raise e logging.debug('item_selected={} child={} widget={}'.format( index_current, item_child, item_widget)) self.sig_node_selected.emit(item_widget)
def _node_disable_bt_clicked(self): logging.debug('param_gs _node_disable_bt_clicked') self.sig_node_disabled_selected.emit(self._toplevel_treenode_name)