33 from __future__
import division
34 from __future__
import print_function
41 from python_qt_binding
import loadUi
42 from python_qt_binding.QtCore
import QFile, QIODevice, Qt, Signal, Slot, QAbstractListModel
43 from python_qt_binding.QtGui
import QIcon, QImage, QPainter
44 from python_qt_binding.QtWidgets
import QFileDialog, QGraphicsScene, QWidget, QCompleter
45 from python_qt_binding.QtSvg
import QSvgGenerator
50 from .dotcode_pack
import RosPackageGraphDotcodeGenerator
62 """A completer that completes multiple times from a list""" 65 path = QCompleter.pathFromIndex(self, index)
66 lst = str(self.widget().text()).split(
',')
68 path =
'%s, %s' % (
','.join(lst[:-1]), path)
72 path = str(path.split(
',')[-1]).lstrip(
' ')
78 """Ros package and stacknames""" 80 def __init__(self, linewidget, rospack, rosstack):
81 super(StackageCompletionModel, self).
__init__(linewidget)
82 self.
allnames = sorted(list(set(rospack.list() + rosstack.list())))
88 def data(self, index, role):
90 if index.isValid()
and (role == Qt.DisplayRole
or role == Qt.EditRole):
97 _deferred_fit_in_view = Signal()
100 super(RosPackGraph, self).
__init__(context)
109 self.setObjectName(
'RosPackGraph')
111 rospack = rospkg.RosPack()
112 rosstack = rospkg.RosStack()
123 rp = rospkg.RosPack()
124 ui_file = os.path.join(rp.get_path(
'rqt_dep'),
'resource',
'RosPackGraph.ui')
125 loadUi(ui_file, self.
_widget, {
'InteractiveGraphicsView': InteractiveGraphicsView})
126 self._widget.setObjectName(
'RosPackGraphUi')
127 if context.serial_number() > 1:
128 self._widget.setWindowTitle(
129 self._widget.windowTitle() + (
' (%d)' % context.serial_number()))
132 self._scene.setBackgroundBrush(Qt.white)
133 self._widget.graphics_view.setScene(self.
_scene)
135 self._widget.depth_combo_box.insertItem(0, self.tr(
'infinite'), -1)
136 self._widget.depth_combo_box.insertItem(1, self.tr(
'1'), 2)
137 self._widget.depth_combo_box.insertItem(2, self.tr(
'2'), 3)
138 self._widget.depth_combo_box.insertItem(3, self.tr(
'3'), 4)
139 self._widget.depth_combo_box.insertItem(4, self.tr(
'4'), 5)
142 self._widget.directions_combo_box.insertItem(0, self.tr(
'depends'), 0)
143 self._widget.directions_combo_box.insertItem(1, self.tr(
'depends_on'), 1)
144 self._widget.directions_combo_box.insertItem(2, self.tr(
'both'), 2)
147 self._widget.package_type_combo_box.insertItem(0, self.tr(
'wet & dry'), 3)
148 self._widget.package_type_combo_box.insertItem(1, self.tr(
'wet only'), 2)
149 self._widget.package_type_combo_box.insertItem(2, self.tr(
'dry only'), 1)
154 completer.setCompletionMode(QCompleter.PopupCompletion)
155 completer.setWrapAround(
True)
157 completer.setCaseSensitivity(Qt.CaseInsensitive)
159 self._widget.filter_line_edit.setCompleter(completer)
160 self._widget.filter_line_edit.selectionChanged.connect(self.
_clear_filter)
168 self._widget.refresh_graph_push_button.setIcon(QIcon.fromTheme(
'view-refresh'))
173 self._widget.fit_in_view_push_button.setIcon(QIcon.fromTheme(
'zoom-original'))
174 self._widget.fit_in_view_push_button.pressed.connect(self.
_fit_in_view)
176 self._widget.load_dot_push_button.setIcon(QIcon.fromTheme(
'document-open'))
177 self._widget.load_dot_push_button.pressed.connect(self.
_load_dot)
178 self._widget.save_dot_push_button.setIcon(QIcon.fromTheme(
'document-save-as'))
179 self._widget.save_dot_push_button.pressed.connect(self.
_save_dot)
180 self._widget.save_as_svg_push_button.setIcon(QIcon.fromTheme(
'document-save-as'))
181 self._widget.save_as_svg_push_button.pressed.connect(self.
_save_svg)
182 self._widget.save_as_image_push_button.setIcon(QIcon.fromTheme(
'image'))
183 self._widget.save_as_image_push_button.pressed.connect(self.
_save_image)
185 self._deferred_fit_in_view.connect(self.
_fit_in_view, Qt.QueuedConnection)
186 self._deferred_fit_in_view.emit()
188 context.add_widget(self.
_widget)
196 self._update_thread.kill()
199 instance_settings.set_value(
200 'depth_combo_box_index', self._widget.depth_combo_box.currentIndex())
201 instance_settings.set_value(
202 'directions_combo_box_index', self._widget.directions_combo_box.currentIndex())
203 instance_settings.set_value(
204 'package_type_combo_box', self._widget.package_type_combo_box.currentIndex())
205 instance_settings.set_value(
'filter_line_edit_text', self._widget.filter_line_edit.text())
206 instance_settings.set_value(
207 'with_stacks_state', self._widget.with_stacks_check_box.isChecked())
208 instance_settings.set_value(
209 'hide_transitives_state', self._widget.hide_transitives_check_box.isChecked())
210 instance_settings.set_value(
211 'show_system_state', self._widget.show_system_check_box.isChecked())
212 instance_settings.set_value(
'mark_state', self._widget.mark_check_box.isChecked())
213 instance_settings.set_value(
'colorize_state', self._widget.colorize_check_box.isChecked())
214 instance_settings.set_value(
215 'auto_fit_graph_check_box_state', self._widget.auto_fit_graph_check_box.isChecked())
216 instance_settings.set_value(
'highlight_connections_check_box_state',
217 self._widget.highlight_connections_check_box.isChecked())
220 _str_filter = instance_settings.value(
'filter_line_edit_text',
'')
221 if (_str_filter ==
None or _str_filter ==
'')
and \
223 _str_filter =
'(Separate pkgs by comma)' 227 self._widget.depth_combo_box.setCurrentIndex(
228 int(instance_settings.value(
'depth_combo_box_index', 0)))
229 self._widget.directions_combo_box.setCurrentIndex(
230 int(instance_settings.value(
'directions_combo_box_index', 0)))
231 self._widget.package_type_combo_box.setCurrentIndex(
232 int(instance_settings.value(
'package_type_combo_box', 0)))
233 self._widget.filter_line_edit.setText(_str_filter)
234 self._widget.with_stacks_check_box.setChecked(
235 instance_settings.value(
'with_stacks_state',
True)
in [
True,
'true'])
236 self._widget.mark_check_box.setChecked(
237 instance_settings.value(
'mark_state',
True)
in [
True,
'true'])
238 self._widget.colorize_check_box.setChecked(
239 instance_settings.value(
'colorize_state',
False)
in [
True,
'true'])
240 self._widget.hide_transitives_check_box.setChecked(
241 instance_settings.value(
'hide_transitives_state',
False)
in [
True,
'true'])
242 self._widget.show_system_check_box.setChecked(
243 instance_settings.value(
'show_system_state',
False)
in [
True,
'true'])
244 self._widget.auto_fit_graph_check_box.setChecked(instance_settings.value(
245 'auto_fit_graph_check_box_state',
True)
in [
True,
'true'])
246 self._widget.highlight_connections_check_box.setChecked(instance_settings.value(
247 'highlight_connections_check_box_state',
True)
in [
True,
'true'])
253 self._widget.depth_combo_box.setEnabled(
True)
254 self._widget.directions_combo_box.setEnabled(
True)
255 self._widget.package_type_combo_box.setEnabled(
True)
256 self._widget.filter_line_edit.setEnabled(
True)
257 self._widget.with_stacks_check_box.setEnabled(
True)
258 self._widget.mark_check_box.setEnabled(
True)
259 self._widget.colorize_check_box.setEnabled(
True)
260 self._widget.hide_transitives_check_box.setEnabled(
True)
261 self._widget.show_system_check_box.setEnabled(
True)
266 self.
_options[
'depth'] = self._widget.depth_combo_box.itemData(
267 self._widget.depth_combo_box.currentIndex())
268 self.
_options[
'directions'] = self._widget.directions_combo_box.itemData(
269 self._widget.directions_combo_box.currentIndex())
270 self.
_options[
'package_types'] = self._widget.package_type_combo_box.itemData(
271 self._widget.package_type_combo_box.currentIndex())
272 self.
_options[
'with_stacks'] = self._widget.with_stacks_check_box.isChecked()
273 self.
_options[
'mark_selected'] = self._widget.mark_check_box.isChecked()
274 self.
_options[
'hide_transitives'] = self._widget.hide_transitives_check_box.isChecked()
275 self.
_options[
'show_system'] = self._widget.show_system_check_box.isChecked()
277 self.
_options[
'colortheme'] =
True if self._widget.colorize_check_box.isChecked()
else None 278 self.
_options[
'names'] = self._widget.filter_line_edit.text().split(
',')
279 if self.
_options[
'names'] == [
u'None']:
281 self.
_options[
'highlight_level'] = \
282 3
if self._widget.highlight_connections_check_box.isChecked()
else 1
283 self.
_options[
'auto_fit'] = self._widget.auto_fit_graph_check_box.isChecked()
289 self._update_thread.kill()
294 new_options_serialized = pickle.dumps(self.
_options)
299 self._scene.setBackgroundBrush(Qt.lightGray)
301 self._update_thread.start()
307 except Exception
as e:
308 print(str(type(e)), str(e), file=sys.stderr)
314 self._scene.setBackgroundBrush(Qt.white)
322 if name.strip().startswith(
'-'):
323 excludes.append(name.strip()[1:])
325 includes.append(name.strip())
329 if self.
_options[
'directions'] == 1:
331 if self.
_options[
'directions'] == 0:
333 return self.dotcode_generator.generate_dotcode(
335 selected_names=includes,
338 with_stacks=self.
_options[
'with_stacks'],
339 descendants=descendants,
341 mark_selected=self.
_options[
'mark_selected'],
342 colortheme=self.
_options[
'colortheme'],
343 hide_transitives=self.
_options[
'hide_transitives'],
344 show_system=self.
_options[
'show_system'],
345 hide_wet=self.
_options[
'package_types'] == 1,
346 hide_dry=self.
_options[
'package_types'] == 2)
351 self.
_nodes, self.
_edges = self.dot_to_qt.dotcode_to_qt_items(
355 if url
is not None and ':' in url:
356 item_type, item_path = url.split(
':', 1)
357 if item_type ==
'node':
358 tool_tip =
'Node:\n %s' % (item_path)
359 service_names = rosservice.get_service_list(node=item_path)
361 tool_tip +=
'\nServices:' 362 for service_name
in service_names:
364 service_type = rosservice.get_service_type(service_name)
365 tool_tip +=
'\n %s [%s]' % (service_name, service_type)
366 except rosservice.ROSServiceIOException
as e:
367 tool_tip +=
'\n %s' % (e)
369 elif item_type ==
'topic':
370 topic_type, topic_name, _ = rostopic.get_topic_type(item_path)
371 return 'Topic:\n %s\nType:\n %s' % (topic_name, topic_type)
376 for item
in self._scene.items():
377 self._scene.removeItem(item)
379 for node_item
in self._nodes.values():
380 self._scene.addItem(node_item)
381 for edge_items
in self._edges.values():
382 for edge_item
in edge_items:
383 edge_item.add_to_scene(self.
_scene)
385 self._scene.setSceneRect(self._scene.itemsBoundingRect())
390 if file_name
is None:
391 file_name, _ = QFileDialog.getOpenFileName(
392 self.
_widget, self.tr(
'Open graph from file'),
None, self.tr(
'DOT graph (*.dot)'))
393 if file_name
is None or file_name ==
'':
397 fh = open(file_name,
'rb')
404 self._widget.depth_combo_box.setEnabled(
False)
405 self._widget.directions_combo_box.setEnabled(
False)
406 self._widget.package_type_combo_box.setEnabled(
False)
407 self._widget.filter_line_edit.setEnabled(
False)
408 self._widget.with_stacks_check_box.setEnabled(
False)
409 self._widget.mark_check_box.setEnabled(
False)
410 self._widget.colorize_check_box.setEnabled(
False)
411 self._widget.hide_transitives_check_box.setEnabled(
False)
412 self._widget.show_system_check_box.setEnabled(
False)
419 self._widget.graphics_view.fitInView(self._scene.itemsBoundingRect(), Qt.KeepAspectRatio)
422 file_name, _ = QFileDialog.getSaveFileName(
423 self.
_widget, self.tr(
'Save as DOT'),
'rospackgraph.dot', self.tr(
'DOT graph (*.dot)'))
424 if file_name
is None or file_name ==
'':
427 handle = QFile(file_name)
428 if not handle.open(QIODevice.WriteOnly | QIODevice.Text):
435 file_name, _ = QFileDialog.getSaveFileName(
437 self.tr(
'Save as SVG'),
439 self.tr(
'Scalable Vector Graphic (*.svg)'))
440 if file_name
is None or file_name ==
'':
443 generator = QSvgGenerator()
444 generator.setFileName(file_name)
445 generator.setSize((self._scene.sceneRect().size() * 2.0).toSize())
447 painter = QPainter(generator)
448 painter.setRenderHint(QPainter.Antialiasing)
449 self._scene.render(painter)
453 file_name, _ = QFileDialog.getSaveFileName(
455 self.tr(
'Save as image'),
457 self.tr(
'Image (*.bmp *.jpg *.png *.tiff)'))
458 if file_name
is None or file_name ==
'':
462 (self._scene.sceneRect().size() * 2.0).toSize(),
463 QImage.Format_ARGB32_Premultiplied)
464 painter = QPainter(img)
465 painter.setRenderHint(QPainter.Antialiasing)
466 self._scene.render(painter)
472 self._widget.filter_line_edit.setText(
'')
def shutdown_plugin(self)
def save_settings(self, plugin_settings, instance_settings)
def __init__(self, linewidget, rospack, rosstack)
def _update_rospackgraph(self)
def pathFromIndex(self, index)
def _refresh_rospackgraph(self, force_update=False)
def _generate_tool_tip(self, url)
def _update_options(self)
def splitPath(self, path)
def data(self, index, role)
def __init__(self, context)
def _load_dot(self, file_name=None)
def rowCount(self, parent)
def _update_finished(self)
def _generate_dotcode(self)
def restore_settings(self, plugin_settings, instance_settings)
def _redraw_graph_scene(self)
def _update_thread_run(self)
def _update_graph(self, dotcode)