37 from python_qt_binding
import loadUi
38 from python_qt_binding.QtCore
import Qt, QTimer, qWarning, Slot
39 from python_qt_binding.QtGui
import QIcon
40 from python_qt_binding.QtWidgets
import QAction, QMenu, QWidget
45 from rqt_py_common
import topic_helpers
47 from . rosplot
import ROSData, RosPlotException
51 topic_type, real_topic, _ = topic_helpers.get_topic_type(topic_name)
52 if topic_type
is None:
53 message =
"topic %s does not exist" % (topic_name)
55 field_name = topic_name[len(real_topic) + 1:]
57 slot_type, is_array, array_size = roslib.msgs.parse_type(topic_type)
58 field_class = roslib.message.get_message_class(slot_type)
59 if field_class
is None:
60 message =
"type of topic %s is unknown" % (topic_name)
63 fields = [f
for f
in field_name.split(
'/')
if f]
68 field, _, field_index = roslib.msgs.parse_type(field)
69 except roslib.msgs.MsgSpecException:
70 message =
"invalid field %s in topic %s" % (field, real_topic)
73 if field
not in getattr(field_class,
'__slots__', []):
74 message =
"no field %s in topic %s" % (field_name, real_topic)
76 slot_type = field_class._slot_types[field_class.__slots__.index(field)]
77 slot_type, slot_is_array, array_size = roslib.msgs.parse_type(slot_type)
78 is_array = slot_is_array
and field_index
is None 80 field_class = topic_helpers.get_type_class(slot_type)
82 if field_class
in (int, float, bool):
83 topic_kind =
'boolean' if field_class == bool
else 'numeric' 85 if array_size
is not None:
86 message =
"topic %s is fixed-size %s array" % (topic_name, topic_kind)
87 return [
"%s[%d]" % (topic_name, i)
for i
in range(array_size)], message
89 message =
"topic %s is variable-size %s array" % (topic_name, topic_kind)
92 message =
"topic %s is %s" % (topic_name, topic_kind)
93 return [topic_name], message
95 if not roslib.msgs.is_valid_constant_type(slot_type):
97 for i, slot
in enumerate(field_class.__slots__):
98 slot_type = field_class._slot_types[i]
99 slot_type, is_array, array_size = roslib.msgs.parse_type(slot_type)
100 slot_class = topic_helpers.get_type_class(slot_type)
101 if slot_class
in (int, float)
and not is_array:
102 numeric_fields.append(slot)
104 if len(numeric_fields) > 0:
105 message =
"%d plottable fields in %s" % (len(numeric_fields), topic_name)
107 message =
"No plottable fields in %s" % (topic_name)
108 return [
"%s/%s" % (topic_name, f)
for f
in numeric_fields], message
110 message =
"Topic %s is not numeric" % (topic_name)
116 return len(fields) > 0, message
120 _redraw_interval = 40
122 def __init__(self, initial_topics=None, start_paused=False):
124 self.setObjectName(
'PlotWidget')
128 rp = rospkg.RosPack()
129 ui_file = os.path.join(rp.get_path(
'rqt_plot'),
'resource',
'plot.ui')
130 loadUi(ui_file, self)
131 self.subscribe_topic_button.setIcon(QIcon.fromTheme(
'list-add'))
132 self.remove_topic_button.setIcon(QIcon.fromTheme(
'list-remove'))
133 self.pause_button.setIcon(QIcon.fromTheme(
'media-playback-pause'))
134 self.clear_button.setIcon(QIcon.fromTheme(
'edit-clear'))
137 self.subscribe_topic_button.setEnabled(
False)
139 self.pause_button.setChecked(
True)
142 self._topic_completer.update_topics()
151 self._update_plot_timer.timeout.connect(self.
update_plot)
156 self.data_plot_layout.removeWidget(self.
data_plot)
158 self.data_plot.close()
161 self.data_plot_layout.addWidget(self.
data_plot)
162 self.data_plot.autoscroll(self.autoscroll_checkbox.isChecked())
165 self.data_plot.dropEvent = self.
dropEvent 173 for topic_name, rosdata
in self._rosdata.items():
174 data_x, data_y = rosdata.next()
175 self.data_plot.add_curve(topic_name, topic_name, data_x, data_y)
179 @Slot(
'QDragEnterEvent*')
182 if not event.mimeData().hasText():
183 if not hasattr(event.source(),
'selectedItems')
or \
184 len(event.source().selectedItems()) == 0:
186 'Plot.dragEnterEvent(): not hasattr(event.source(), selectedItems) or ' 187 'len(event.source().selectedItems()) == 0')
189 item = event.source().selectedItems()[0]
190 topic_name = item.data(0, Qt.UserRole)
191 if topic_name ==
None:
192 qWarning(
'Plot.dragEnterEvent(): not hasattr(item, ros_topic_name_)')
195 topic_name = str(event.mimeData().text())
200 event.acceptProposedAction()
202 qWarning(
'Plot.dragEnterEvent(): rejecting: "%s"' % (message))
206 if event.mimeData().hasText():
207 topic_name = str(event.mimeData().text())
209 droped_item = event.source().selectedItems()[0]
210 topic_name = str(droped_item.data(0, Qt.UserRole))
216 if topic_name
in (
'',
'/'):
217 self._topic_completer.update_topics()
220 self.subscribe_topic_button.setEnabled(plottable)
221 self.subscribe_topic_button.setToolTip(message)
225 if self.subscribe_topic_button.isEnabled():
226 self.
add_topic(str(self.topic_edit.text()))
230 self.
add_topic(str(self.topic_edit.text()))
238 self.data_plot.autoscroll(checked)
240 self.data_plot.redraw()
249 for topic_name, rosdata
in self._rosdata.items():
251 data_x, data_y = rosdata.next()
253 self.data_plot.update_values(topic_name, data_x, data_y)
255 except RosPlotException
as e:
256 qWarning(
'PlotWidget.update_plot(): error in rosplot: %s' % e)
258 self.data_plot.redraw()
262 if not self.pause_button.isChecked():
265 self.data_plot.redraw()
268 def make_remove_topic_function(x):
271 self._remove_topic_menu.clear()
272 for topic_name
in sorted(self._rosdata.keys()):
274 action.triggered.connect(make_remove_topic_function(topic_name))
275 self._remove_topic_menu.addAction(action)
280 self._remove_topic_menu.addAction(all_action)
285 topics_changed =
False 288 qWarning(
'PlotWidget.add_topic(): topic already subscribed: %s' % topic_name)
291 if self.
_rosdata[topic_name].error
is not None:
292 qWarning(str(self.
_rosdata[topic_name].error))
295 data_x, data_y = self.
_rosdata[topic_name].next()
296 self.data_plot.add_curve(topic_name, topic_name, data_x, data_y)
297 topics_changed =
True 305 self.data_plot.remove_curve(topic_name)
310 for topic_name, _
in self._rosdata.items():
311 self.data_plot.clear_values(topic_name)
312 self.data_plot.redraw()
315 for topic_name, rosdata
in self._rosdata.items():
317 self.data_plot.remove_curve(topic_name)
326 self._update_plot_timer.stop()