From 20636899567f3fe607138cd3f75605e16ebdcb16 Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Mon, 10 Nov 2014 02:14:26 -0800 Subject: [PATCH 1/4] rqt_reconfigure: Restore support for parameter groups --- .../dynreconf_client_widget.py | 4 +- .../src/rqt_reconfigure/param_groups.py | 104 ++++++++++-------- 2 files changed, 62 insertions(+), 46 deletions(-) diff --git a/rqt_reconfigure/src/rqt_reconfigure/dynreconf_client_widget.py b/rqt_reconfigure/src/rqt_reconfigure/dynreconf_client_widget.py index 2ff50f7f..e6bc13e5 100644 --- a/rqt_reconfigure/src/rqt_reconfigure/dynreconf_client_widget.py +++ b/rqt_reconfigure/src/rqt_reconfigure/dynreconf_client_widget.py @@ -35,7 +35,7 @@ import rospy from .param_editors import EditorWidget -from .param_groups import GroupWidget +from .param_groups import GroupWidget, find_cfg from .param_updater import ParamUpdater @@ -85,7 +85,7 @@ def config_callback(self, config): widget.param_name) widget.update_value(config[widget.param_name]) elif isinstance(widget, GroupWidget): - cfg = self.find_cfg(config, widget.param_name) + cfg = find_cfg(config, widget.param_name) rospy.logdebug('GROUP widget.param_name=%s', widget.param_name) widget.update_group(cfg) diff --git a/rqt_reconfigure/src/rqt_reconfigure/param_groups.py b/rqt_reconfigure/src/rqt_reconfigure/param_groups.py index 7e92a359..b05bf241 100644 --- a/rqt_reconfigure/src/rqt_reconfigure/param_groups.py +++ b/rqt_reconfigure/src/rqt_reconfigure/param_groups.py @@ -89,6 +89,7 @@ class GroupWidget(QWidget): # public signal sig_node_disabled_selected = Signal(str) + sig_node_state_change = Signal(bool) def __init__(self, updater, config, nodename): ''' @@ -97,13 +98,9 @@ def __init__(self, updater, config, nodename): :type nodename: str ''' - #TODO figure out what data type 'config' is. It is afterall returned - # from dynamic_reconfigure.client.get_parameter_descriptions() - # ros.org/doc/api/dynamic_reconfigure/html/dynamic_reconfigure.client-pysrc.html#Client - super(GroupWidget, self).__init__() self.state = config['state'] - self.name = config['name'] + self.param_name = config['name'] self._toplevel_treenode_name = nodename # TODO: .ui file needs to be back into usage in later phase. @@ -176,8 +173,8 @@ def _create_node_widgets(self, config): widget = EnumEditor(self.updater, param) elif param['type'] in EDITOR_TYPES: rospy.logdebug('GroupWidget i_debug=%d param type =%s', - i_debug, - param['type']) + i_debug, + param['type']) editor_type = EDITOR_TYPES[param['type']] widget = eval(editor_type)(self.updater, param) @@ -195,38 +192,41 @@ def _create_node_widgets(self, config): for name, group in config['groups'].items(): if group['type'] == 'tab': - widget = TabGroup(self, self.updater, group) + 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) + 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) rospy.logdebug('groups._create_node_widgets ' + - #'num groups=%d' + - 'name=%s', - name) + 'name=%s', + name) for i, ed in enumerate(self.editor_widgets): ed.display(self.grid) rospy.logdebug('GroupWdgt._create_node_widgets len(editor_widgets)=%d', - len(self.editor_widgets)) + len(self.editor_widgets)) - def display(self, grid, row): - # groups span across all columns - grid.addWidget(self, row, 0, 1, -1) + def display(self, grid): + grid.addRow(self) def update_group(self, config): - self.state = config['state'] + if 'state' in config: + old_state = self.state + self.state = config['state'] + if self.state != old_state: + self.sig_node_state_change.emit(self.state) - # TODO: should use config.keys but this method doesnt exist - names = [name for name in config.items()] + names = [name for name in config.keys()] for widget in self.editor_widgets: if isinstance(widget, EditorWidget): - if widget.name in names: - widget.update_value(config[widget.name]) + if widget.param_name in names: + widget.update_value(config[widget.param_name]) elif isinstance(widget, GroupWidget): - cfg = find_cfg(config, widget.name) + cfg = find_cfg(config, widget.param_name) widget.update_group(cfg) def close(self): @@ -245,41 +245,56 @@ def _node_disable_bt_clicked(self): class BoxGroup(GroupWidget): - def __init__(self, updater, config): - super(BoxGroup, self).__init__(updater, config) + def __init__(self, updater, config, nodename): + super(BoxGroup, self).__init__(updater, config, nodename) - self.box = QGroupBox(self.name) + self.box = QGroupBox(self.param_name) self.box.setLayout(self.grid) - def display(self, grid, row): - grid.addWidget(self.box, row, 0, 1, -1) + def display(self, grid): + grid.addRow(self.box) class CollapseGroup(BoxGroup): - def __init__(self, updater, config): - super(CollapseGroup, self).__init__(updater, config) + def __init__(self, updater, config, nodename): + super(CollapseGroup, self).__init__(updater, config, nodename) self.box.setCheckable(True) + self.box.clicked.connect(self.click_cb) + self.sig_node_state_change.connect(self.box.setChecked) + + for child in self.box.children(): + if child.isWidgetType(): + self.box.toggled.connect(child.setShown) + + self.box.setChecked(self.state) + + def click_cb(self, on): + self.updater.update({'groups': {self.param_name: on}}) class HideGroup(BoxGroup): - def update_group(self, config): - super(HideGroup, self).update_group(config) - self.box.setVisible(self.state) + def __init__(self, updater, config, nodename): + super(HideGroup, self).__init__(updater, config, nodename) + self.box.setShown(self.state) + self.sig_node_state_change.connect(self.box.setShown) class TabGroup(GroupWidget): - def __init__(self, parent, updater, config): - super(TabGroup, self).__init__(updater, config) + def __init__(self, parent, updater, config, nodename): + super(TabGroup, self).__init__(updater, config, nodename) self.parent = parent if not self.parent.tab_bar: self.parent.tab_bar = QTabWidget() - parent.tab_bar.addTab(self, self.name) + self.wid = QWidget() + self.wid.setLayout(self.grid) + + parent.tab_bar.addTab(self.wid, self.param_name) - def display(self, grid, row): + def display(self, grid): if not self.parent.tab_bar_shown: - grid.addWidget(self.parent.tab_bar, row, 0, 1, -1) + grid.addRow(self.parent.tab_bar) self.parent.tab_bar_shown = True def close(self): @@ -290,24 +305,25 @@ def close(self): class ApplyGroup(BoxGroup): class ApplyUpdater: - def __init__(self, updater): + def __init__(self, updater, loopback): self.updater = updater + self.loopback = loopback self._configs_pending = {} def update(self, config): for name, value in config.items(): self._configs_pending[name] = value + self.loopback(config) def apply_update(self): self.updater.update(self._configs_pending) self._configs_pending = {} - def __init__(self, updater, config): - self.updater = ApplyGroup.ApplyUpdater(updater) - super(ApplyGroup, self).__init__(self.updater, config) + def __init__(self, updater, config, nodename): + self.updater = ApplyGroup.ApplyUpdater(updater, self.update_group) + super(ApplyGroup, self).__init__(self.updater, config, nodename) - self.button = QPushButton("Apply %s" % self.name) + self.button = QPushButton('Apply %s' % self.param_name) self.button.clicked.connect(self.updater.apply_update) - rows = self.grid.rowCount() - self.grid.addWidget(self.button, rows + 1, 1, Qt.AlignRight) + self.grid.addRow(self.button) From 95f015504328802f01a4ed4372c3e1e1c135452f Mon Sep 17 00:00:00 2001 From: Michael Jae-Yoon Chung Date: Wed, 12 Nov 2014 08:56:54 -0800 Subject: [PATCH 2/4] fix image shrinking problem #291 --- rqt_image_view/src/rqt_image_view/ratio_layouted_frame.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rqt_image_view/src/rqt_image_view/ratio_layouted_frame.cpp b/rqt_image_view/src/rqt_image_view/ratio_layouted_frame.cpp index 2aa39070..16160b2d 100644 --- a/rqt_image_view/src/rqt_image_view/ratio_layouted_frame.cpp +++ b/rqt_image_view/src/rqt_image_view/ratio_layouted_frame.cpp @@ -72,13 +72,13 @@ void RatioLayoutedFrame::resizeToFitAspectRatio() { // too large width width = height * aspect_ratio_.width() / aspect_ratio_.height(); - rect.setWidth(int(width)); + rect.setWidth(int(width + 0.5)); } else { // too large height height = width * aspect_ratio_.height() / aspect_ratio_.width(); - rect.setHeight(int(height)); + rect.setHeight(int(height + 0.5)); } // resize taking the border line into account From 7eecff4af857bd28a166adde025917fcdfc94c53 Mon Sep 17 00:00:00 2001 From: Christian-Eike Framing Date: Mon, 15 Dec 2014 09:42:42 +0100 Subject: [PATCH 3/4] Remove dead nodes from memory and rebuild uri cache when nodes are restarted --- rqt_top/src/rqt_top/node_info.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/rqt_top/src/rqt_top/node_info.py b/rqt_top/src/rqt_top/node_info.py index 12d66bb7..0dbe77d6 100644 --- a/rqt_top/src/rqt_top/node_info.py +++ b/rqt_top/src/rqt_top/node_info.py @@ -34,8 +34,9 @@ class NodeInfo(object): nodes = dict() - def get_node_info(self, node_name): - node_api = rosnode.get_api_uri(rospy.get_master(), node_name) + + def get_node_info(self, node_name, skip_cache=False): + node_api = rosnode.get_api_uri(rospy.get_master(), node_name, skip_cache=skip_cache) try: code, msg, pid = xmlrpclib.ServerProxy(node_api[2]).getPid(ID) if node_name in self.nodes: @@ -48,11 +49,14 @@ def get_node_info(self, node_name): except: return False except xmlrpclib.socket.error: - return False - + if not skip_cache: + return self.get_node_info(node_name, skip_cache=True) + else: + return False def get_all_node_info(self): infos = [] + self.remove_dead_nodes() for node_name in rosnode.get_node_names(): info = self.get_node_info(node_name) if info is not False: infos.append((node_name, info)) @@ -65,6 +69,12 @@ def get_all_node_fields(self, fields): infos.append(self.as_dict(p, fields + ['cmdline', 'get_memory_info'])) infos[-1]['node_name'] = name return infos + + def remove_dead_nodes(self): + running_nodes = rosnode.get_node_names() + dead_nodes = [node_name for node_name in self.nodes if node_name not in running_nodes] + for node_name in dead_nodes: + self.nodes.pop(node_name, None) def kill_node(self, node_name): success, fail = rosnode.kill_nodes([node_name]) From d1ba2165f834db6eb35929d1e2aaaa2a4a4c6ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl?= Date: Sun, 11 Jan 2015 20:14:35 +0100 Subject: [PATCH 4/4] Fix version clash when PyQt5 is installed When PyQt5 is installed, matplotlib uses the Qt5 backend by default, so we need to force the usage of the Qt4 backend. --- rqt_plot/src/rqt_plot/data_plot/mat_data_plot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rqt_plot/src/rqt_plot/data_plot/mat_data_plot.py b/rqt_plot/src/rqt_plot/data_plot/mat_data_plot.py index 0a783002..91a26031 100644 --- a/rqt_plot/src/rqt_plot/data_plot/mat_data_plot.py +++ b/rqt_plot/src/rqt_plot/data_plot/mat_data_plot.py @@ -51,6 +51,7 @@ def parse_version(s): raise ImportError('A newer matplotlib is required (at least 1.1.0)') try: + matplotlib.use('Qt4Agg') from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas except ImportError: # work around bug in dateutil