1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 import re
34 import rospy
35 from python_qt_binding.QtCore import Qt
36 from python_qt_binding.QtGui import QIcon, QStandardItem, QStandardItemModel
37
38 from detailed_msg_box import MessageBox
39 from master_discovery_fkie.master_info import TopicInfo
40
41 from node_manager_fkie.common import lnamespace, namespace, normns, utf8
42 import node_manager_fkie as nm
46 '''
47 The topic item stored in the topic model. This class stores the topic as
48 U{master_discovery_fkie.TopicInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.TopicInfo>}.
49 The name of the topic represented in HTML.
50 '''
51
52 ITEM_TYPE = QStandardItem.UserType + 36
53 NAME_ROLE = Qt.UserRole + 1
54 NODENAMES_ROLE = Qt.UserRole + 2
55 COL_PUB = 1
56 COL_SUB = 2
57 COL_TYPE = 3
58
59 - def __init__(self, name, topic=None, parent=None):
60 '''
61 Initialize the topic item.
62 :param str name: the topic name
63 :param topic: the topic info
64 :type topic: U{master_discovery_fkie.TopicInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.TopicInfo>}
65 '''
66 QStandardItem.__init__(self, name)
67 self._parent_item = parent
68 self._publish_thread = None
69 self.topic = TopicInfo(name) if topic is None else topic
70 '''@ivar: topic as U{master_discovery_fkie.TopicInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.TopicInfo>}.'''
71 self._with_namespace = rospy.names.SEP in name
72
73
74
75
76 @property
79
80 @name.setter
81 - def name(self, new_name):
83
84 @property
86 return self.topic.type
87
88 @property
90 return self._parent_item
91
92 @parent_item.setter
94 self._parent_item = parent_item
95 if parent_item is None:
96 self.setText(self.text())
97 self._with_namespace = rospy.names.SEP in self.text()
98 else:
99 new_name = self.text().replace(parent_item.get_namespace(), '', 1)
100 self.setText(new_name)
101 self._with_namespace = rospy.names.SEP in new_name
102
103 @property
105 '''
106 Returns `True` if the topic name contains a '/' in his name
107
108 :rtype: bool
109 '''
110 return self._with_namespace
111
121
123 '''
124 Updates the representation of the column contains the publisher state.
125 '''
126 if self.parent_item is not None:
127 cfg_col = self.parent_item.child(self.row(), TopicItem.COL_PUB)
128 if cfg_col is not None and isinstance(cfg_col, QStandardItem):
129 cfg_col.setText(str(len(self.topic.publisherNodes)))
130 tooltip = ''.join(['<h4>', 'Publisher [', self.topic.name, ']:</h4><dl>'])
131 for p in self.topic.publisherNodes:
132 tooltip = ''.join([tooltip, '<dt>', p, '</dt>'])
133 tooltip = ''.join([tooltip, '</dl>'])
134 if len(self.topic.publisherNodes) > 0:
135 cfg_col.setToolTip(''.join(['<div>', tooltip, '</div>']))
136
138 '''
139 Updates the representation of the column contains the subscriber state.
140 '''
141 if self.parent_item is not None:
142 cfg_col = self.parent_item.child(self.row(), TopicItem.COL_SUB)
143 if cfg_col is not None and isinstance(cfg_col, QStandardItem):
144 cfg_col.setText(str(len(self.topic.subscriberNodes)))
145 tooltip = ''.join(['<h4>', 'Subscriber [', self.topic.name, ']:</h4><dl>'])
146 for p in self.topic.subscriberNodes:
147 tooltip = ''.join([tooltip, '<dt>', p, '</dt>'])
148 tooltip = ''.join([tooltip, '</dl>'])
149 if len(self.topic.subscriberNodes) > 0:
150 cfg_col.setToolTip(''.join(['<div>', tooltip, '</div>']))
151
153 '''
154 Updates the representation of the column contains the type of the topic.
155 '''
156 if self.parent_item is not None:
157 cfg_col = self.parent_item.child(self.row(), TopicItem.COL_TYPE)
158 if cfg_col is not None and isinstance(cfg_col, QStandardItem):
159 cfg_col.setText(self.topic.type if self.topic.type and self.topic.type != 'None' else 'unknown type')
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
189 self.updateIconView(QIcon(':/icons/state_off.png'))
190
192 self.updateIconView(QIcon(':/icons/state_part.png'))
193
195 self.updateIconView(QIcon(':/icons/state_run.png'))
196
198 self._publish_thread = None
199 self.setIcon(QIcon())
200
205
208
209 - def data(self, role):
210 if role == self.NAME_ROLE:
211 return self.topic.name
212 elif role == self.NODENAMES_ROLE:
213 return utf8(self.topic.publisherNodes) + utf8(self.topic.subscriberNodes)
214 else:
215 return QStandardItem.data(self, role)
216
217 @classmethod
219 '''
220 Creates the list of the items from topic. This list is used for the
221 visualization of topic data as a table row.
222 :param str topic: the topic name
223 :param root: The parent QStandardItem
224 :type root: U{QStandardItem<https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>}
225 :return: the list for the representation as a row
226 :rtype: C{[L{TopicItem} or U{QStandardItem<https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>}, ...]}
227 '''
228 items = []
229 item = TopicItem(topic.name, topic, parent=root)
230 items.append(item)
231 pubItem = QStandardItem()
232
233 items.append(pubItem)
234 subItem = QStandardItem()
235
236 items.append(subItem)
237 typeItem = QStandardItem()
238
239 items.append(typeItem)
240 return items
241
243 '''
244 Compares the name of topic.
245 '''
246 if isinstance(item, str) or isinstance(item, unicode):
247 return self.topic.name.lower() == item.lower()
248 elif not (item is None):
249 return self.topic.name.lower() == item.topic.name.lower()
250 return False
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266 -class TopicGroupItem(QStandardItem):
267 '''
268 The TopicGroupItem stores the information about a group of nodes.
269 '''
270 ITEM_TYPE = Qt.UserRole + 35
271
272 - def __init__(self, name, parent=None, is_group=False):
273 '''
274 Initialize the TopicGroupItem object with given values.
275
276 :param str name: the name of the group
277 :param parent: the parent item. In most cases this is the HostItem. The variable is used to determine the different columns of the NodeItem.
278 :type parent: :class:`QtGui.QStandardItem` <https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>
279 :param bool is_group: True if this is a capability group. In other case it is a namespace group.
280 '''
281 dname = name
282 if is_group:
283 dname = '{%s}' % name
284 elif name != rospy.names.SEP:
285 dname = '%s/' % name
286 else:
287 dname = 'topics@master/'
288 QStandardItem.__init__(self, dname)
289 self.parent_item = parent
290 self._name = name
291 self._is_group = is_group
292 self.is_system_group = name == 'SYSTEM'
293
294 @property
296 '''
297 The name of this group.
298
299 :rtype: str
300 '''
301 return self._name
302
303 @name.setter
304 - def name(self, new_name):
305 '''
306 Set the new name of this group and updates the displayed name of the item.
307
308 :param str new_name: The new name of the group. Used also to identify the group.
309 '''
310 self._name = new_name
311 if self._is_group:
312 self.setText('{' + self._name + '}')
313 else:
314 self.setText(self._name + '/')
315
316 @property
318 return self._is_group
319
328
330 '''
331 :retrun: Returns count of nodes inside this group.
332 :rtype: int
333 '''
334 result = 0
335 for i in range(self.rowCount()):
336 item = self.child(i)
337 if isinstance(item, TopicGroupItem):
338 result += item.count_nodes()
339 elif isinstance(item, TopicItem):
340 result += 1
341 return result
342
344 '''
345 Since the same node can be included by different groups, this method searches
346 for all nodes with given name and returns these items.
347
348 :param str topic_name: The name of the topic
349 :param bool recursive: Searches in (sub) groups
350 :return: The list with node items.
351 :rtype: list(:class:`QtGui.QStandardItem` <https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>)
352 '''
353 result = []
354 for i in range(self.rowCount()):
355 item = self.child(i)
356 if isinstance(item, TopicGroupItem):
357 if recursive:
358 result[len(result):] = item.get_topic_items_by_name(topic_name)
359 elif isinstance(item, TopicItem) and item == topic_name:
360 return [item]
361 return result
362
364 '''
365 Returns all nodes in this group and subgroups.
366
367 :param bool recursive: returns the nodes of the subgroups
368 :return: The list with node items.
369 :rtype: list(:class:`QtGui.QStandardItem` <https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>)
370 '''
371 result = []
372 for i in range(self.rowCount()):
373 item = self.child(i)
374 if isinstance(item, TopicGroupItem):
375 if recursive:
376 result[len(result):] = item.get_topic_items()
377 elif isinstance(item, TopicItem):
378 result.append(item)
379 return result
380
381 @classmethod
383 '''
384 Creates the list of the items for this group. This list is used for the
385 visualization of group data as a table row.
386
387 :param str name: the group name
388 :return: the list for the representation as a row
389 :rtype: C{[L{TopicGroupItem} or U{QStandardItem<https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>}, ...]}
390 '''
391 items = []
392 item = TopicGroupItem(name, parent, is_group)
393 items.append(item)
394 pubItem = QStandardItem()
395 items.append(pubItem)
396 subItem = QStandardItem()
397 items.append(subItem)
398 typeItem = QStandardItem()
399 items.append(typeItem)
400 return items
401
403 '''
404 Returns a TopicGroupItem with given name. If no group with this name exists, a
405 new one will be created. The given name will be split by slashes if exists
406 and subgroups are created.
407
408 :param str group_name: the name of the group
409 :param bool is_group: True if it is a capability group. False if a namespace group. (Default: True)
410 :param bool nocreate: avoid creation of new group if not exists. (Default: False)
411 :return: The group with given name of None if `nocreate` is True and group not exists.
412 :rtype: :class:`TopicGroupItem`
413 '''
414 lns, rns = group_name, ''
415 if nm.settings().group_nodes_by_namespace:
416 lns, rns = lnamespace(group_name)
417 if lns == rospy.names.SEP:
418 lns, rns = lnamespace(rns)
419 if lns == rospy.names.SEP:
420 return self
421 for i in range(self.rowCount()):
422 item = self.child(i)
423 if isinstance(item, TopicGroupItem):
424 if item == lns:
425 if rns:
426 return item.get_group_item(rns, is_group)
427 return item
428 elif item > lns and not nocreate:
429 items = TopicGroupItem.create_item_list(lns, self, is_group=(is_group and not rns))
430 self.insertRow(i, items)
431 if rns:
432 return items[0].get_group_item(rns, is_group)
433 return items[0]
434 if nocreate:
435 return None
436 items = TopicGroupItem.create_item_list(lns, self, is_group=(is_group and not rns))
437 self.appendRow(items)
438 if rns:
439 return items[0].get_group_item(rns, is_group)
440 return items[0]
441
443 '''
444 Adds a new topic with given name.
445
446 :param topic: the TopicInfo of the node to create
447 :type topic: :class:`TopicInfo`
448 '''
449 group_item = self
450 if nm.settings().group_nodes_by_namespace:
451 ns = namespace(topic.name)
452 if ns != rospy.names.SEP:
453
454 group_item = self.get_group_item(ns, False)
455
456 new_item_row = TopicItem.create_item_list(topic, self)
457 group_item._add_row(new_item_row)
458
463
464 - def clearup(self, fixed_node_names=None):
465 '''
466 Removes not running and not configured nodes.
467
468 :param list(str) fixed_node_names: If the list is not None, the node not in the list are set to not running!
469 '''
470 self._clearup(fixed_node_names)
471 self._clearup_reinsert()
472 self._clearup_riseup()
473
474 - def _clearup(self, fixed_node_names=None):
475 '''
476 Removes not running and not configured nodes.
477
478 :param list(str) fixed_node_names: If the list is not None, the node not in the list are set to not running!
479 '''
480 removed = False
481
482 for i in reversed(range(self.rowCount())):
483 item = self.child(i)
484 if isinstance(item, TopicItem):
485 pass
486 else:
487 removed = item._clearup(fixed_node_names) or removed
488 if self.rowCount() == 0 and self.parent_item is not None:
489 self.parent_item._remove_group(self.name)
490 return removed
491
493 inserted = False
494 for i in reversed(range(self.rowCount())):
495 item = self.child(i)
496 if isinstance(item, TopicItem):
497 if item.with_namespace:
498 group_item = self.get_group_item(namespace(item.name), False, nocreate=True)
499 if group_item is not None and group_item != self:
500 inserted = True
501 row = self.takeRow(i)
502 group_item._add_row(row)
503 else:
504 inserted = item._clearup_reinsert() or inserted
505 return inserted
506
524
526 for i in reversed(range(self.rowCount())):
527 item = self.child(i)
528 if type(item) == TopicGroupItem and item == name and item.rowCount() == 0:
529 self.removeRow(i)
530
547
556
558 '''
559 Returns for given topics the list of QModelIndex in this model.
560
561 :param [str] publisher: the list of publisher topics
562 :param [str] subscriber: the list of subscriber topics
563 :return: the list of QModelIndex
564 :rtype: [QtCore.QModelIndex]
565 '''
566 result = []
567 for i in range(self.rowCount()):
568 item = self.child(i)
569 if type(item) == TopicGroupItem:
570 result[len(result):] = item.index_from_names(publisher, subscriber)
571 elif type(item) == TopicItem:
572 if item.topic.name in publisher:
573 result.append(item.index())
574 result.append(self.child(i, 1).index())
575 if item.topic.name in subscriber:
576 result.append(item.index())
577 result.append(self.child(i, 2).index())
578 return result
579
582
584 '''
585 Compares the name of the group.
586 '''
587 if isinstance(item, str) or isinstance(item, unicode):
588 return self.name.lower() == item.lower()
589 elif not (item is None):
590 return self.name.lower() == item.name.lower()
591 return False
592
594 return not (self == item)
595
597 '''
598 Compares the name of the group.
599 '''
600 if isinstance(item, str) or isinstance(item, unicode):
601
602 if self.is_system_group:
603 if self.name.lower() != item.lower():
604 return True
605 elif item.lower() == 'system':
606 return False
607 return self.name.lower() > item.lower()
608 elif not (item is None):
609
610 if item.is_system_group:
611 if self.name.lower() != item.lower():
612 return True
613 elif self.is_syste_group:
614 return False
615 return self.name.lower() > item.name.lower()
616 return False
617
620 '''
621 The model to manage the list with topics in ROS network.
622 '''
623 header = [('Name', 300),
624 ('Publisher', 50),
625 ('Subscriber', 50),
626 ('Type', -1)]
627 ''':ivar: the list with columns C{[(name, width), ...]}'''
628
630 '''
631 Creates a new list model.
632 '''
633 QStandardItemModel.__init__(self)
634 self.setColumnCount(len(TopicModel.header))
635 self.setHorizontalHeaderLabels([label for label, _ in TopicModel.header])
636 topics = ['/rosout', '/rosout_agg', '/diagnostics_agg']
637 def_list = ['\A' + n.strip().replace('*', '.*') + '\Z' for n in topics]
638 self._re_cap_systopics = re.compile('|'.join(def_list), re.I)
639 self.pyqt_workaround = dict()
640 root_items = TopicGroupItem.create_item_list(rospy.names.SEP, self.invisibleRootItem(), False)
641 self.invisibleRootItem().appendRow(root_items)
642 self._pyqt_workaround_add(rospy.names.SEP, root_items[0])
643
645 '''
646 :param index: parent of the list
647 :type index: QtCore.QModelIndex<https://srinikom.github.io/pyside-docs/PySide/QtCore/QModelIndex.html>
648 :return: Flag or the requested item
649 :rtype: QtCore.Qt.ItemFlag<https://srinikom.github.io/pyside-docs/PySide/QtCore/Qt.html>
650 :see: http://www.pyside.org/docs/pyside-1.0.1/PySide/QtCore/Qt.html
651 '''
652 if not index.isValid():
653 return Qt.NoItemFlags
654 return Qt.ItemIsEnabled | Qt.ItemIsSelectable
655
657 match = False
658 try:
659 match = self._re_cap_systopics.match(topic_name)
660 except Exception:
661 pass
662 if match:
663 root = self.get_root_group()
664 for i in range(root.rowCount()):
665 item = root.child(i)
666 if type(item) == TopicGroupItem:
667 if item == 'SYSTEM' and item.is_group:
668 return item
669 items = TopicGroupItem.create_item_list('SYSTEM', root, True)
670 root.appendRow(items)
671 self.pyqt_workaround['{SYSTEM}'] = items[0]
672 return items[0]
673 return None
674
676 root = self.invisibleRootItem()
677 for i in range(root.rowCount()):
678 item = root.child(i)
679 if type(item) == TopicGroupItem:
680 if item == rospy.names.SEP:
681 return item
682 return None
683
684 - def updateModelData(self, topics, added_topics, updated_topics, removed_topics):
685 '''
686 Updates the topics model. New topic will be inserted in sorting order. Not
687 available topics removed from the model.
688
689 :param topics: The dictionary with topics
690 :type topics: {topic name : U{master_discovery_fkie.TopicInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.TopicInfo>}}
691 :param added_topics: the list of new topics in the :topics: list
692 :type added_topics: list or set
693 :param updated_topics: the list of updated topics in the :topics: list
694 :type updated_topics: list or set
695 :param removed_topics: the list of removed topics in the :topics: list
696 :type removed_topics: list or set
697 '''
698
699 parents = set()
700 for rm_topic in removed_topics:
701 new_parent = self._remove_node(rm_topic)
702 if type(new_parent) == TopicGroupItem:
703 parents.add(new_parent)
704 for parent in parents:
705 parent.clearup()
706
707 root = self.invisibleRootItem()
708 for i in reversed(range(root.rowCount())):
709 item = root.child(i)
710 if type(item) == TopicGroupItem:
711 item.update_topic_view(updated_topics, topics)
712
713
714
715
716 for topic_name in added_topics:
717 try:
718 topic = topics[topic_name]
719
720 sys_group = self.get_cap_group(topic_name)
721 if sys_group is not None:
722 sys_group.add_node(topic)
723 else:
724
725 root_group = self.get_root_group()
726 if root_group is not None:
727 root_group.add_node(topic)
728 except Exception:
729 import traceback
730 print traceback.format_exc()
731 pass
732
733
734
735
737 '''
738 Returns for given topics the list of QModelIndex in this model.
739
740 :param [str] publisher: the list of publisher topics
741 :param [str] subscriber: the list of subscriber topics
742 :return: the list of QModelIndex
743 :rtype: [QtCore.QModelIndex]
744 '''
745 result = []
746 root = self.invisibleRootItem()
747 for i in range(root.rowCount()):
748 item = root.child(i)
749 if type(item) == TopicGroupItem:
750 result[len(result):] = item.index_from_names(publisher, subscriber)
751 elif type(item) == TopicItem:
752 if item.topic.name in publisher:
753 result.append(item.index())
754 result.append(self.child(i, 1).index())
755 if item.topic.name in subscriber:
756 result.append(item.index())
757 result.append(self.child(i, 2).index())
758 return result
759
761 root = self.invisibleRootItem()
762 for i in range(root.rowCount()):
763 item = root.child(i)
764 if type(item) == TopicGroupItem:
765 parent = item.remove_node(name)
766 if parent is not None:
767 return parent
768 return None
769
771 self.pyqt_workaround[name] = item
772
774 try:
775 del self.pyqt_workaround[name]
776 except Exception:
777 pass
778