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 from python_qt_binding.QtCore import QFile, Qt, Signal
34 from python_qt_binding.QtGui import QIcon, QImage, QStandardItem, QStandardItemModel
35 import re
36 import roslib
37 import rospy
38 import traceback
39
40 from master_discovery_fkie.common import get_hostname, subdomain
41 from master_discovery_fkie.master_info import NodeInfo
42 from node_manager_fkie.common import lnamespace, namespace, normns, utf8
43 from node_manager_fkie.name_resolution import NameResolution
44 from parameter_handler import ParameterHandler
45 import node_manager_fkie as nm
49 '''
50 Item for a cell. References to a node item.
51 '''
52 ITEM_TYPE = Qt.UserRole + 41
53
54 - def __init__(self, name, item=None, parent=None):
55 '''
56 Initialize the CellItem object with given values.
57 @param name: the name of the group
58 @type name: C{str}
59 @param parent: the parent item. In most cases this is the HostItem. The
60 variable is used to determine the different columns of the NodeItem.
61 @type parent: U{QtGui.QStandardItem<https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>}
62 '''
63 QStandardItem.__init__(self)
64 self.parent_item = parent
65 self._name = name
66 self.item = item
67
68 @property
70 '''
71 The name of this group.
72 @rtype: C{str}
73 '''
74 return self._name
75
81 '''
82 The GroupItem stores the information about a group of nodes.
83 '''
84 ITEM_TYPE = Qt.UserRole + 25
85
86 - def __init__(self, name, parent=None, has_remote_launched_nodes=False, is_group=False):
87 '''
88 Initialize the GroupItem object with given values.
89 @param name: the name of the group
90 @type name: C{str}
91 @param parent: the parent item. In most cases this is the HostItem. The
92 variable is used to determine the different columns of the NodeItem.
93 @type parent: U{QtGui.QStandardItem<https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>}
94 '''
95 dname = name
96 if dname.rfind('@') <= 0:
97 if is_group:
98 dname = '{' + dname + '}'
99 else:
100 dname = dname + '/'
101 QStandardItem.__init__(self, dname)
102 self.parent_item = parent
103 self._name = name
104 self.setIcon(QIcon(':/icons/state_off.png'))
105 self.descr_type = self.descr_name = self.descr = ''
106 self.descr_images = []
107 self._capcabilities = dict()
108 self._has_remote_launched_nodes = has_remote_launched_nodes
109 self._is_group = is_group
110 self._remote_launched_nodes_updated = False
111 self._state = NodeItem.STATE_OFF
112 self.diagnostic_array = []
113 self.is_system_group = name == 'SYSTEM'
114 '''
115 @ivar: dict(config : dict(namespace: dict(group:dict('type' : str, 'images' : [str], 'description' : str, 'nodes' : [str]))))
116 '''
117 self._re_cap_nodes = dict()
118
119 @property
121 '''
122 The name of this group.
123 @rtype: C{str}
124 '''
125 return self._name
126
127 @name.setter
128 - def name(self, new_name):
129 '''
130 Set the new name of this group and updates the displayed name of the item.
131 @param new_name: The new name of the group. Used also to identify the group.
132 @type new_name: C{str}
133 '''
134 self._name = new_name
135 if self._is_group:
136 self.setText('{' + self._name + '}')
137 else:
138 self.setText(self._name + '/')
139
140 @property
142 '''
143 The state of this group.
144 @rtype: C{int}
145 '''
146 return self._state
147
148 @property
150 return self._is_group
151
152 @property
154 lc, dc = self.get_configs()
155 lc[len(lc):] = dc
156 return lc
157
168
170 '''
171 Returns count of nodes inside this group.
172 '''
173 result = 0
174 for i in range(self.rowCount()):
175 item = self.child(i)
176 if isinstance(item, GroupItem):
177 result += item.count_nodes()
178 elif isinstance(item, NodeItem):
179 result += 1
180 return result
181
183 '''
184 Returns `True` if the group contains the node.
185 @param nodename: the name of the node to test
186 @type nodename: str
187 @param config: the configuration name
188 @type config: str
189 @param ns: namespace
190 @type ns: str
191 @param groupname: the group name
192 @type groupname: str
193 @return: `True`, if the nodename is in the group
194 @rtype: bool
195 '''
196 try:
197 if self._re_cap_nodes[(config, ns, groupname)].match(nodename):
198 return True
199 except:
200 pass
201 return False
202
204 for ns, groups in cap.items():
205 for groupname, descr in groups.items():
206 try:
207 nodes = descr['nodes']
208 def_list = ['\A' + n.strip().replace('*', '.*') + '\Z' for n in nodes]
209 if def_list:
210 self._re_cap_nodes[(config, ns, groupname)] = re.compile('|'.join(def_list), re.I)
211 else:
212 self._re_cap_nodes[(config, ns, groupname)] = re.compile('\b', re.I)
213 except:
214 rospy.logwarn("create_cap_nodes_pattern: %s" % traceback.format_exc(1))
215
217 '''
218 Add new capabilities. Based on this capabilities the node are grouped. The
219 view will be updated.
220 @param config: The name of the configuration containing this new capabilities.
221 @type config: C{str}
222 @param masteruri: The masteruri is used only used, if new nodes are created.
223 @type masteruri: C{str}
224 @param capabilities: The capabilities, which defines groups and containing nodes.
225 @type capabilities: C{dict(namespace: dict(group:dict('type' : str, 'images' : [str], 'description' : str, 'nodes' : [str])))}
226 '''
227 self._capcabilities[config] = capabilities
228 self._create_cap_nodes_pattern(config, capabilities)
229
230 for ns, groups in capabilities.items():
231 for group, descr in groups.items():
232 group_changed = False
233
234 nodes = descr['nodes']
235 if nodes:
236 groupItem = self.getGroupItem(roslib.names.ns_join(ns, group))
237 groupItem.descr_name = group
238 if descr['type']:
239 groupItem.descr_type = descr['type']
240 if descr['description']:
241 groupItem.descr = descr['description']
242 if descr['images']:
243 groupItem.descr_images = list(descr['images'])
244
245 group_changed = self.move_nodes2group(groupItem, config, ns, group, self)
246
247 for node_name in nodes:
248
249 if not re.search(r"\*", node_name):
250 items = groupItem.getNodeItemsByName(node_name)
251 if items:
252 for item in items:
253 item.addConfig(config)
254 group_changed = True
255 else:
256 items = self.getNodeItemsByName(node_name)
257 if items:
258
259 groupItem.addNode(items[0].node_info, config)
260 elif config:
261 groupItem.addNode(NodeInfo(node_name, masteruri), config)
262 group_changed = True
263 if group_changed:
264 groupItem.updateDisplayedConfig()
265 groupItem.updateIcon()
266
268 '''
269 Returns `True` if the group was changed by adding a new node.
270
271 @param GroupItem group_item: item to parse the children for nodes.
272 @param str config: the configuration name
273 @param str ns: namespace
274 @param str groupname: the group name
275 @param HostItem host_item: the host item contain the capability groups
276 @return: `True`, if the group was changed by adding a new node.
277 @rtype: bool
278 '''
279 self_changed = False
280 group_changed = False
281 for i in reversed(range(self.rowCount())):
282 item = self.child(i)
283 if isinstance(item, NodeItem):
284 if host_item.is_in_cap_group(item.name, config, ns, groupname):
285 row = self.takeRow(i)
286 group_item._addRow_sorted(row)
287 group_changed = True
288 elif isinstance(item, GroupItem) and not item.is_group:
289 group_changed = item.move_nodes2group(group_item, config, ns, groupname, host_item)
290 if self_changed:
291 self.update_displayed_config()
292 self.updateIcon()
293 return group_changed
294
296 '''
297 Removes internal entry of the capability, so the new nodes are not grouped.
298 To update view L{NodeTreeModel.removeConfigNodes()} and L{GroupItem.clearUp()}
299 must be called.
300 @param config: The name of the configuration containing this new capabilities.
301 @type config: C{str}
302 '''
303 try:
304 del self._capcabilities[config]
305 except:
306 pass
307 else:
308
309 pass
310
312 '''
313 Returns the names of groups, which contains the given node.
314 @param node_name: The name of the node
315 @type node_name: C{str}
316 @return: The name of the configuration containing this new capabilities.
317 @rtype: C{dict(config : [str])}
318 '''
319 result = dict()
320 try:
321 for cfg, cap in self._capcabilities.items():
322 for ns, groups in cap.items():
323 for group, _ in groups.items():
324 if self.is_in_cap_group(node_name, cfg, ns, group):
325 if cfg not in result:
326 result[cfg] = []
327 result[cfg].append(roslib.names.ns_join(ns, group))
328 except:
329 pass
330
331
332 return result
333
335 '''
336 Since the same node can be included by different groups, this method searches
337 for all nodes with given name and returns these items.
338 @param node_name: The name of the node
339 @type node_name: C{str}
340 @param recursive: Searches in (sub) groups
341 @type recursive: C{bool}
342 @return: The list with node items.
343 @rtype: C{[U{QtGui.QStandardItem<https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>}]}
344 '''
345 result = []
346 for i in range(self.rowCount()):
347 item = self.child(i)
348 if isinstance(item, GroupItem):
349 if recursive:
350 result[len(result):] = item.getNodeItemsByName(node_name)
351 elif isinstance(item, NodeItem) and item == node_name:
352 return [item]
353 return result
354
356 '''
357 Returns all nodes in this group and subgroups.
358 @param recursive: returns the nodes of the subgroups
359 @type recursive: bool
360 @return: The list with node items.
361 @rtype: C{[U{QtGui.QStandardItem<https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>}]}
362 '''
363 result = []
364 for i in range(self.rowCount()):
365 item = self.child(i)
366 if isinstance(item, GroupItem):
367 if recursive:
368 result[len(result):] = item.getNodeItems()
369 elif isinstance(item, NodeItem):
370 result.append(item)
371 return result
372
373 - def getGroupItem(self, group_name, is_group=True, nocreate=False):
374 '''
375 Returns a GroupItem with given name. If no group with this name exists, a
376 new one will be created.
377 Assumption: No groups in group!!
378 @param group_name: the name of the group
379 @type group_name: C{str}
380 @param nocreate: avoid creation of new group if not exists. (Default: False)
381 @return: The group with given name
382 @rtype: L{GroupItem}
383 '''
384 lns, rns = group_name, ''
385 if nm.settings().group_nodes_by_namespace:
386 lns, rns = lnamespace(group_name)
387 if lns == rospy.names.SEP and type(self) == HostItem:
388 lns, rns = lnamespace(rns)
389 if lns == rospy.names.SEP:
390 return self
391 for i in range(self.rowCount()):
392 item = self.child(i)
393 if isinstance(item, GroupItem):
394 if item == lns:
395 if rns:
396 return item.getGroupItem(rns, is_group)
397 return item
398 elif item > lns and not nocreate:
399 items = []
400 newItem = GroupItem(lns, self, is_group=(is_group and not rns))
401 items.append(newItem)
402 cfgitem = CellItem(group_name, newItem)
403 items.append(cfgitem)
404 self.insertRow(i, items)
405 if rns:
406 return newItem.getGroupItem(rns, is_group)
407 return newItem
408 if nocreate:
409 return None
410 items = []
411 newItem = GroupItem(lns, self, is_group=(is_group and not rns))
412 items.append(newItem)
413 cfgitem = CellItem(group_name, newItem)
414 items.append(cfgitem)
415 self.appendRow(items)
416 if rns:
417 return newItem.getGroupItem(rns, is_group)
418 return newItem
419
421 '''
422 Adds a new node with given name.
423 @param node: the NodeInfo of the node to create
424 @type node: L{NodeInfo}
425 @param cfg: The configuration, which describes the node
426 @type cfg: C{str}
427 '''
428 groups = self.getCapabilityGroups(node.name)
429 if groups:
430 for _, group_list in groups.items():
431 for group_name in group_list:
432
433 groupItem = self.getGroupItem(group_name, True)
434 groupItem.addNode(node, cfg)
435 else:
436 group_item = self
437 if nm.settings().group_nodes_by_namespace:
438 if type(group_item) == HostItem:
439
440 group_item = self.getGroupItem(namespace(node.name), False)
441
442 new_item_row = NodeItem.newNodeRow(node.name, node.masteruri)
443 group_item._addRow_sorted(new_item_row)
444 new_item_row[0].node_info = node
445 if cfg or cfg == '':
446 new_item_row[0].addConfig(cfg)
447 group_item.updateIcon()
448
450 for i in range(self.rowCount()):
451 item = self.child(i)
452 if item > row[0].name:
453 self.insertRow(i, row)
454 row[0].parent_item = self
455 return
456 self.appendRow(row)
457 row[0].parent_item = self
458
459 - def clearUp(self, fixed_node_names=None):
460 '''
461 Removes not running and not configured nodes.
462 @param fixed_node_names: If the list is not None, the node not in the list are
463 set to not running!
464 @type fixed_node_names: C{[str]}
465 '''
466 self._clearup(fixed_node_names)
467 self._clearup_reinsert()
468 self._clearup_riseup()
469
470 - def _clearup(self, fixed_node_names=None):
507
509 inserted = False
510 for i in reversed(range(self.rowCount())):
511 item = self.child(i)
512 if isinstance(item, NodeItem):
513 if item.with_namespace:
514 group_item = self.getGroupItem(namespace(item.name), False, nocreate=True)
515 if group_item is not None and group_item != self:
516 inserted = True
517 row = self.takeRow(i)
518 group_item._addRow_sorted(row)
519 group_item.updateIcon()
520 else:
521 inserted = item._clearup_reinsert() or inserted
522 return inserted
523
541
543 for i in reversed(range(self.rowCount())):
544 item = self.child(i)
545 if type(item) == GroupItem and item == name and item.rowCount() == 0:
546 self.removeRow(i)
547
549 self._remote_launched_nodes_updated = False
550
552 if self._has_remote_launched_nodes:
553 return self._remote_launched_nodes_updated
554 return True
555
557 '''
558 Updates the running state of the nodes given in a dictionary.
559 @param nodes: A dictionary with node names and their running state described by L{NodeInfo}.
560 @type nodes: C{dict(str: U{master_discovery_fkie.NodeInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.NodeInfo>})}
561 '''
562 for (name, node) in nodes.items():
563
564 items = self.getNodeItemsByName(name)
565 if items:
566 for item in items:
567
568 item.node_info = node
569 else:
570
571 self.addNode(node)
572 if self._has_remote_launched_nodes:
573 self._remote_launched_nodes_updated = True
574 self.clearUp(nodes.keys())
575
577 '''
578 Returns the names of all running nodes. A running node is defined by his
579 PID.
580 @see: U{master_dicovery_fkie.NodeInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.NodeInfo>}
581 @return: A list with node names
582 @rtype: C{[str]}
583 '''
584 result = []
585 for i in range(self.rowCount()):
586 item = self.child(i)
587 if isinstance(item, GroupItem):
588 result[len(result):] = item.getRunningNodes()
589 elif isinstance(item, NodeItem) and item.node_info.pid is not None:
590 result.append(item.name)
591 return result
592
594 '''
595 While a synchronization same node on different hosts have the same name, the
596 nodes with the same on other host are marked.
597 @param running_nodes: The dictionary with names of running nodes and their masteruri
598 @type running_nodes: C{dict(str:str)}
599 @param is_sync_running: If the master_sync is running, the nodes are marked
600 as ghost nodes. So they are handled as running nodes, but has not run
601 informations. This nodes are running on remote host, but are not
602 syncronized because of filter or errors.
603 @type is_sync_running: bool
604 '''
605 ignore = ['/master_sync', '/master_discovery', '/node_manager']
606 for i in range(self.rowCount()):
607 item = self.child(i)
608 if isinstance(item, GroupItem):
609 item.markNodesAsDuplicateOf(running_nodes, is_sync_running)
610 elif isinstance(item, NodeItem):
611 if is_sync_running:
612 item.is_ghost = (item.node_info.uri is None and (item.name in running_nodes and running_nodes[item.name] == item.node_info.masteruri))
613 item.has_running = (item.node_info.uri is None and item.name not in ignore and (item.name in running_nodes and running_nodes[item.name] != item.node_info.masteruri))
614 else:
615 if item.is_ghost:
616 item.is_ghost = False
617 item.has_running = (item.node_info.uri is None and item.name not in ignore and (item.name in running_nodes))
618
620 if isinstance(self, HostItem):
621
622 return
623 has_running = False
624 has_off = False
625 has_duplicate = False
626 has_ghosts = False
627 diag_level = 0
628 for i in range(self.rowCount()):
629 item = self.child(i)
630 if isinstance(item, (GroupItem, NodeItem)):
631 if item.state == NodeItem.STATE_WARNING:
632 self.setIcon(QIcon(':/icons/crystal_clear_warning.png'))
633 return
634 elif item.state == NodeItem.STATE_OFF:
635 has_off = True
636 elif item.state == NodeItem.STATE_RUN:
637 has_running = True
638 if item.diagnostic_array and item.diagnostic_array[-1].level > 0:
639 if diag_level == 0:
640 diag_level = item.diagnostic_array[-1].level
641 elif item.diagnostic_array[-1].level == 2:
642 diag_level = 2
643 self.diagnostic_array = item.diagnostic_array
644 elif item.state == NodeItem.STATE_GHOST:
645 has_ghosts = True
646 elif item.state == NodeItem.STATE_DUPLICATE:
647 has_duplicate = True
648 elif item.state == NodeItem.STATE_PARTS:
649 has_running = True
650 has_off = True
651 diag_icon = None
652 if diag_level > 0:
653 diag_icon = NodeItem._diagnostic_level2icon(diag_level)
654 if has_duplicate:
655 self._state = NodeItem.STATE_DUPLICATE
656 self.setIcon(QIcon(':/icons/imacadam_stop.png'))
657 elif has_ghosts:
658 self._state = NodeItem.STATE_GHOST
659 self.setIcon(QIcon(':/icons/state_ghost.png'))
660 elif has_running and has_off:
661 if diag_icon is not None:
662 self.setIcon(diag_icon)
663 else:
664 self._state = NodeItem.STATE_PARTS
665 self.setIcon(QIcon(':/icons/state_part.png'))
666 elif not has_running:
667 self._state = NodeItem.STATE_OFF
668 self.setIcon(QIcon(':/icons/state_off.png'))
669 elif not has_off and has_running:
670 if diag_icon is not None:
671 self.setIcon(diag_icon)
672 else:
673 self._state = NodeItem.STATE_RUN
674 self.setIcon(QIcon(':/icons/state_run.png'))
675 if self.parent_item is not None:
676 self.parent_item.updateIcon()
677
679 result = ''
680 if items:
681 result += '<b><u>%s</u></b>' % title
682 if len(items) > 1:
683 result += ' <span style="color:gray;">[%d]</span>' % len(items)
684 result += '<ul><span></span><br>'
685 for i in items:
686 result += '<a href="node://%s">%s</a><br>' % (i, i)
687 result += '</ul>'
688 return result
689
701
703 tooltip = ''
704 if self.descr_type or self.descr_name or self.descr:
705 tooltip += '<h4>%s</h4><dl>' % self.descr_name
706 if self.descr_type:
707 tooltip += '<dt>Type: %s</dt></dl>' % self.descr_type
708 if extended:
709 try:
710 from docutils import examples
711 if self.descr:
712 tooltip += '<b><u>Detailed description:</u></b>'
713 tooltip += examples.html_body(utf8(self.descr))
714 except:
715 rospy.logwarn("Error while generate description for a tooltip: %s", traceback.format_exc(1))
716 tooltip += '<br>'
717
718 nodes = []
719 for j in range(self.rowCount()):
720 nodes.append(self.child(j).name)
721 if nodes:
722 tooltip += self._create_html_list('Nodes:', nodes)
723 return '<div>%s</div>' % tooltip
724
726 '''
727 Sets the description of the robot. To update the tooltip of the host item use L{updateTooltip()}.
728 @param descr_type: the type of the robot
729 @type descr_type: C{str}
730 @param descr_name: the name of the robot
731 @type descr_name: C{str}
732 @param descr: the description of the robot as a U{http://docutils.sourceforge.net/rst.html|reStructuredText}
733 @type descr: C{str}
734 '''
735 self.descr_type = descr_type
736 self.descr_name = descr_name
737 self.descr = descr
738
740 '''
741 Updates the configuration representation in other column.
742 '''
743 if self.parent_item is not None:
744
745 cfgs = []
746 for j in range(self.rowCount()):
747 if self.child(j).cfgs:
748 cfgs[len(cfgs):] = self.child(j).cfgs
749 if cfgs:
750 cfgs = list(set(cfgs))
751 cfg_col = self.parent_item.child(self.row(), NodeItem.COL_CFG)
752 if cfg_col is not None and isinstance(cfg_col, QStandardItem):
753 cfg_col.setText('[%d]' % len(cfgs) if len(cfgs) > 1 else "")
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769 has_launches = NodeItem.has_launch_cfgs(cfgs)
770 has_defaults = NodeItem.has_default_cfgs(cfgs)
771 if has_launches and has_defaults:
772 cfg_col.setIcon(QIcon(':/icons/crystal_clear_launch_file_def_cfg.png'))
773 elif has_launches:
774 cfg_col.setIcon(QIcon(':/icons/crystal_clear_launch_file.png'))
775 elif has_defaults:
776 cfg_col.setIcon(QIcon(':/icons/default_cfg.png'))
777 else:
778 cfg_col.setIcon(QIcon())
779
781 '''
782 Returns a tuple with counts for launch and default configurations.
783 '''
784 cfgs = []
785 for j in range(self.rowCount()):
786 if isinstance(self.child(j), GroupItem):
787 glcfgs, gdcfgs = self.child(j).get_configs()
788 cfgs[len(cfgs):] = glcfgs
789 cfgs[len(cfgs):] = gdcfgs
790 elif self.child(j).cfgs:
791 cfgs[len(cfgs):] = self.child(j).cfgs
792 cfgs = list(set(cfgs))
793 dccfgs = []
794 lccfgs = []
795 for c in cfgs:
796 if NodeItem.is_default_cfg(c):
797 dccfgs.append(c)
798 else:
799 lccfgs.append(c)
800 return (lccfgs, dccfgs)
801
804
806 '''
807 Compares the name of the group.
808 '''
809 if isinstance(item, str) or isinstance(item, unicode):
810 return self.name.lower() == item.lower()
811 elif not (item is None):
812 return self.name.lower() == item.name.lower()
813 return False
814
816 return not (self == item)
817
819 '''
820 Compares the name of the group.
821 '''
822 if isinstance(item, str) or isinstance(item, unicode):
823
824 if self.is_system_group:
825 if self.name.lower() != item.lower():
826 return True
827 elif item.lower() == 'system':
828 return False
829 return self.name.lower() > item.lower()
830 elif not (item is None):
831
832 if item.is_system_group:
833 if self.name.lower() != item.lower():
834 return True
835 elif self.is_syste_group:
836 return False
837 return self.name.lower() > item.name.lower()
838 return False
839
840
841
842
843
844
845 -class HostItem(GroupItem):
846 '''
847 The HostItem stores the information about a host.
848 '''
849 ITEM_TYPE = Qt.UserRole + 26
850
851 - def __init__(self, masteruri, address, local, parent=None):
852 '''
853 Initialize the HostItem object with given values.
854 @param masteruri: URI of the ROS master assigned to the host
855 @type masteruri: C{str}
856 @param address: the address of the host
857 @type address: C{str}
858 @param local: is this host the localhost where the node_manager is running.
859 @type local: C{bool}
860 '''
861 self._has_remote_launched_nodes = False
862 name = self.create_host_description(masteruri, address)
863 self._masteruri = masteruri
864 self._host = address
865 self._mastername = address
866 self._local = local
867 GroupItem.__init__(self, name, parent, has_remote_launched_nodes=self._has_remote_launched_nodes)
868 image_file = nm.settings().robot_image_file(name)
869 if QFile.exists(image_file):
870 self.setIcon(QIcon(image_file))
871 else:
872 if local:
873 self.setIcon(QIcon(':/icons/crystal_clear_miscellaneous.png'))
874 else:
875 self.setIcon(QIcon(':/icons/remote.png'))
876 self.descr_type = self.descr_name = self.descr = ''
877
878 @property
881
882 @property
885
886 @property
895
896 @property
899
900 @property
902 return self._masteruri
903
904 @property
906 result = nm.nameres().mastername(self._masteruri, self._host)
907 if result is None or not result:
908 result = self.hostname
909 return result
910
936
948
950 from docutils import examples
951 tooltip = ''
952 if self.descr_type or self.descr_name or self.descr:
953 tooltip += '<h4>%s</h4><dl>' % self.descr_name
954 if self.descr_type:
955 tooltip += '<dt>Type: %s</dt></dl>' % self.descr_type
956 if extended:
957 try:
958 if self.descr:
959 tooltip += '<b><u>Detailed description:</u></b>'
960 tooltip += examples.html_body(self.descr, input_encoding='utf8')
961 except:
962 rospy.logwarn("Error while generate description for a tooltip: %s", traceback.format_exc(1))
963 tooltip += '<br>'
964 tooltip += '<h3>%s</h3>' % self.mastername
965 tooltip += '<font size="+1"><i>%s</i></font><br>' % self.masteruri
966 tooltip += '<font size="+1">Host: <b>%s%s</b></font><br>' % (self.hostname, ' %s' % self.addresses if self.addresses else '')
967 tooltip += '<a href="open-sync-dialog://%s">open sync dialog</a>' % (utf8(self.masteruri).replace('http://', ''))
968 tooltip += '<p>'
969 tooltip += '<a href="show-all-screens://%s">show all screens</a>' % (utf8(self.masteruri).replace('http://', ''))
970 tooltip += '<p>'
971 tooltip += '<a href="rosclean://%s" title="calls `rosclean purge` at `%s`">rosclean purge</a>' % (self.hostname, self.hostname)
972 tooltip += '<p>'
973 tooltip += '<a href="poweroff://%s" title="calls `sudo poweroff` at `%s` via SSH">poweroff `%s`</a>' % (self.hostname, self.hostname, self.hostname)
974 tooltip += '<p>'
975 tooltip += '<a href="remove-all-launch-server://%s">kill all launch server</a>' % utf8(self.masteruri).replace('http://', '')
976 tooltip += '<p>'
977
978 capabilities = []
979 for j in range(self.rowCount()):
980 item = self.child(j)
981 if isinstance(item, GroupItem):
982 capabilities.append(item.name)
983 if capabilities:
984 tooltip += '<b><u>Capabilities:</u></b>'
985 try:
986 tooltip += examples.html_body('- %s' % ('\n- '.join(capabilities)), input_encoding='utf8')
987 except:
988 rospy.logwarn("Error while generate description for a tooltip: %s", traceback.format_exc(1))
989 return '<div>%s</div>' % tooltip if tooltip else ''
990
993
995 '''
996 Compares the address of the masteruri.
997 '''
998 if isinstance(item, str) or isinstance(item, unicode):
999 rospy.logwarn("compare HostItem with unicode depricated")
1000 return False
1001 elif isinstance(item, tuple):
1002 return self.masteruri == item[0] and self.host == item[1]
1003 elif isinstance(item, HostItem):
1004 return self.masteruri == item.masteruri and self.host == item.host
1005 return False
1006
1008 '''
1009 Compares the address of the masteruri.
1010 '''
1011 if isinstance(item, str) or isinstance(item, unicode):
1012 rospy.logwarn("compare HostItem with unicode depricated")
1013 return False
1014 elif isinstance(item, tuple):
1015 return self.masteruri > item[0]
1016 elif isinstance(item, HostItem):
1017 return self.masteruri > item.masteruri
1018 return False
1019
1020
1021
1022
1023
1024
1025 -class NodeItem(QStandardItem):
1026 '''
1027 The NodeItem stores the information about the node using the ExtendedNodeInfo
1028 class and represents it in a U{QTreeView<https://srinikom.github.io/pyside-docs/PySide/QtGui/QTreeView.html>} using the
1029 U{QStandardItemModel<https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItemModel.html>}
1030 '''
1031
1032 ITEM_TYPE = QStandardItem.UserType + 35
1033 NAME_ROLE = Qt.UserRole + 1
1034 COL_CFG = 1
1035
1036
1037 STATE_OFF = 0
1038 STATE_RUN = 1
1039 STATE_WARNING = 2
1040 STATE_GHOST = 3
1041 STATE_DUPLICATE = 4
1042 STATE_PARTS = 5
1043
1045 '''
1046 Initialize the NodeItem instance.
1047 @param node_info: the node information
1048 @type node_info: U{master_discovery_fkie.NodeInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.NodeInfo>}
1049 '''
1050 QStandardItem.__init__(self, node_info.name)
1051 self._parent_item = None
1052 self._node_info = node_info.copy()
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062 self._cfgs = []
1063 self.launched_cfg = None
1064 self.next_start_cfg = None
1065 self._std_config = None
1066 self._is_ghost = False
1067 self._has_running = False
1068 self.setIcon(QIcon(':/icons/state_off.png'))
1069 self._state = NodeItem.STATE_OFF
1070 self.diagnostic_array = []
1071 self.nodelet_mngr = ''
1072 self.nodelets = []
1073 self.has_screen = True
1074 self._with_namespace = rospy.names.SEP in node_info.name
1075
1076 @property
1079
1080 @property
1082 return self._node_info.name
1083
1084 @name.setter
1085 - def name(self, new_name):
1087
1088 @property
1091
1092 @property
1094 return self._node_info.publishedTopics
1095
1096 @property
1098 return self._node_info.subscribedTopics
1099
1100 @property
1103
1104 @property
1106 return self._parent_item
1107
1108 @parent_item.setter
1118
1119 @property
1121 '''
1122 Returns the NodeInfo instance of this node.
1123 @rtype: U{master_discovery_fkie.NodeInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.NodeInfo>}
1124 '''
1125 return self._node_info
1126
1127 @node_info.setter
1129 '''
1130 Sets the NodeInfo and updates the view, if needed.
1131 '''
1132 abbos_changed = False
1133 run_changed = False
1134
1135
1136
1137
1138 if self._node_info.publishedTopics != node_info.publishedTopics:
1139 abbos_changed = True
1140 self._node_info._publishedTopics = list(node_info.publishedTopics)
1141 if self._node_info.subscribedTopics != node_info.subscribedTopics:
1142 abbos_changed = True
1143 self._node_info._subscribedTopics = list(node_info.subscribedTopics)
1144 if self._node_info.services != node_info.services:
1145 abbos_changed = True
1146 self._node_info._services = list(node_info.services)
1147 if self._node_info.pid != node_info.pid:
1148 self._node_info.pid = node_info.pid
1149 run_changed = True
1150 if self._node_info.uri != node_info.uri:
1151 self._node_info.uri = node_info.uri
1152 run_changed = True
1153
1154 if run_changed and (self.is_running() or self.has_configs) or abbos_changed:
1155 self.has_screen = True
1156 self.updateDispayedName()
1157
1158 if self.parent_item is not None:
1159 self.parent_item.updateIcon()
1160
1161 @property
1163 return self._node_info.uri
1164
1165 @property
1167 return self._node_info.pid
1168
1169 @property
1171 '''
1172 Returns C{True}, if there are exists other nodes with the same name. This
1173 variable must be set manually!
1174 @rtype: C{bool}
1175 '''
1176 return self._has_running
1177
1178 @has_running.setter
1190
1191 @property
1193 '''
1194 Returns C{True}, if there are exists other runnig nodes with the same name. This
1195 variable must be set manually!
1196 @rtype: C{bool}
1197 '''
1198 return self._is_ghost
1199
1200 @is_ghost.setter
1213
1214 @property
1216 '''
1217 Returns `True` if the node name contains a '/' in his name
1218
1219 :rtype: bool
1220 '''
1221 return self._with_namespace
1222
1230
1231 - def data(self, role):
1232 if role == self.NAME_ROLE:
1233 return self.name
1234 else:
1235 return QStandardItem.data(self, role)
1236
1237 @staticmethod
1239 if level == 1:
1240 return QIcon(':/icons/state_diag_warn.png')
1241 elif level == 2:
1242 return QIcon(':/icons/state_diag_error.png')
1243 elif level == 3:
1244 return QIcon(':/icons/state_diag_stale.png')
1245 else:
1246 return QIcon(':/icons/state_diag_other.png')
1247
1249 '''
1250 Updates the name representation of the Item
1251 '''
1252 tooltip = '<h4>%s</h4><dl>' % self.node_info.name
1253 tooltip += '<dt><b>URI:</b> %s</dt>' % self.node_info.uri
1254 tooltip += '<dt><b>PID:</b> %s</dt>' % self.node_info.pid
1255 tooltip += '<dt><b>ORG.MASTERURI:</b> %s</dt></dl>' % self.node_info.masteruri
1256 master_discovered = nm.nameres().has_master(self.node_info.masteruri)
1257
1258
1259
1260 if self.node_info.pid is not None:
1261 self._state = NodeItem.STATE_RUN
1262 if self.diagnostic_array and self.diagnostic_array[-1].level > 0:
1263 level = self.diagnostic_array[-1].level
1264 self.setIcon(self._diagnostic_level2icon(level))
1265 self.setToolTip(self.diagnostic_array[-1].message)
1266 else:
1267 self.setIcon(QIcon(':/icons/state_run.png'))
1268 self.setToolTip('')
1269 elif self.node_info.uri is not None and not self.node_info.isLocal:
1270 self._state = NodeItem.STATE_RUN
1271 self.setIcon(QIcon(':/icons/state_unknown.png'))
1272 tooltip += '<dl><dt>(Remote nodes will not be ping, so they are always marked running)</dt></dl>'
1273 tooltip += '</dl>'
1274 self.setToolTip('<div>%s</div>' % tooltip)
1275
1276
1277
1278
1279
1280
1281
1282 elif self.node_info.pid is None and self.node_info.uri is None and (self.node_info.subscribedTopics or self.node_info.publishedTopics or self.node_info.services):
1283 self.setIcon(QIcon(':/icons/crystal_clear_warning.png'))
1284 self._state = NodeItem.STATE_WARNING
1285 tooltip += '<dl><dt>Can\'t get node contact information, but there exists publisher, subscriber or services of this node.</dt></dl>'
1286 tooltip += '</dl>'
1287 self.setToolTip('<div>%s</div>' % tooltip)
1288 elif self.node_info.uri is not None:
1289 self._state = NodeItem.STATE_WARNING
1290 self.setIcon(QIcon(':/icons/crystal_clear_warning.png'))
1291 if not self.node_info.isLocal and master_discovered:
1292 tooltip = '<h4>%s is not local, however the ROS master on this host is discovered, but no information about this node received!</h4>' % self.node_info.name
1293 self.setToolTip('<div>%s</div>' % tooltip)
1294 elif self.is_ghost:
1295 self._state = NodeItem.STATE_GHOST
1296 self.setIcon(QIcon(':/icons/state_ghost.png'))
1297 tooltip = '<h4>The node is running, but not synchronized because of filter or errors, see master_sync log.</h4>'
1298 self.setToolTip('<div>%s</div>' % tooltip)
1299 elif self.has_running:
1300 self._state = NodeItem.STATE_DUPLICATE
1301 self.setIcon(QIcon(':/icons/imacadam_stop.png'))
1302 tooltip = '<h4>There are nodes with the same name on remote hosts running. These will be terminated, if you run this node! (Only if master_sync is running or will be started somewhere!)</h4>'
1303 self.setToolTip('<div>%s</div>' % tooltip)
1304 else:
1305 self._state = NodeItem.STATE_OFF
1306 self.setIcon(QIcon(':/icons/state_off.png'))
1307 self.setToolTip('')
1308
1309
1310
1312 '''
1313 Updates the URI representation in other column.
1314 '''
1315 if self.parent_item is not None:
1316 uri_col = self.parent_item.child(self.row(), NodeItem.COL_URI)
1317 if uri_col is not None and isinstance(uri_col, QStandardItem):
1318 uri_col.setText(utf8(self.node_info.uri) if self.node_info.uri is not None else "")
1319
1320 @property
1322 '''
1323 Returns the list with all launch configurations assigned to this item.
1324 @rtype: C{[str]}
1325 '''
1326 return self._cfgs
1327
1329 '''
1330 Add the given configurations to the node.
1331 @param cfg: the loaded configuration, which contains this node.
1332 @type cfg: C{str}
1333 '''
1334 if cfg == '':
1335 self._std_config = cfg
1336 elif cfg and cfg not in self._cfgs:
1337 self._cfgs.append(cfg)
1338 self.updateDisplayedConfig()
1339
1341 '''
1342 Remove the given configurations from the node.
1343 @param cfg: the loaded configuration, which contains this node.
1344 @type cfg: C{str}
1345 '''
1346 result = False
1347 if cfg == '':
1348 self._std_config = None
1349 result = True
1350 if cfg in self._cfgs:
1351 self._cfgs.remove(cfg)
1352 result = True
1353 if result and (self.has_configs() or self.is_running()):
1354 self.updateDisplayedConfig()
1355 return result
1356
1358 '''
1359 Updates the configuration representation in other column.
1360 '''
1361 if self.parent_item is not None:
1362 cfg_col = self.parent_item.child(self.row(), NodeItem.COL_CFG)
1363 if cfg_col is not None and isinstance(cfg_col, QStandardItem):
1364 cfg_count = len(self._cfgs)
1365 cfg_col.setText(utf8(''.join(['[', utf8(cfg_count), ']'])) if cfg_count > 1 else "")
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381 has_launches = NodeItem.has_launch_cfgs(self._cfgs)
1382 has_defaults = NodeItem.has_default_cfgs(self._cfgs)
1383 if has_launches and has_defaults:
1384 cfg_col.setIcon(QIcon(':/icons/crystal_clear_launch_file_def_cfg.png'))
1385 elif has_launches:
1386 cfg_col.setIcon(QIcon(':/icons/crystal_clear_launch_file.png'))
1387 elif has_defaults:
1388 cfg_col.setIcon(QIcon(':/icons/default_cfg.png'))
1389 else:
1390 cfg_col.setIcon(QIcon())
1391
1392
1393
1394
1397
1398 @classmethod
1400 '''
1401 Creates a new node row and returns it as a list with items. This list is
1402 used for the visualization of node data as a table row.
1403 @param name: the node name
1404 @type name: C{str}
1405 @param masteruri: the URI or the ROS master assigned to this node.
1406 @type masteruri: C{str}
1407 @return: the list for the representation as a row
1408 @rtype: C{[L{NodeItem}, U{QtGui.QStandardItem<https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>}(Cofigurations), U{QtGui.QStandardItem<https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>}(Node URI)]}
1409 '''
1410 items = []
1411 item = NodeItem(NodeInfo(name, masteruri))
1412 items.append(item)
1413 cfgitem = CellItem(name, item)
1414 items.append(cfgitem)
1415
1416
1417 return items
1418
1420 return not (len(self._cfgs) == 0)
1421
1423 return not (self._node_info.pid is None and self._node_info.uri is None)
1424
1426 return self._std_config == ''
1427
1429 result = 0
1430 for c in self.cfgs:
1431 if not self.is_default_cfg(c):
1432 result += 1
1433 return result
1434
1436 result = 0
1437 for c in self.cfgs:
1438 if self.is_default_cfg(c):
1439 result += 1
1440 return result
1441
1442 @classmethod
1448
1449 @classmethod
1455
1456 @classmethod
1458 return isinstance(cfg, tuple)
1459
1461 '''
1462 Compares the name of the node.
1463 '''
1464 if isinstance(item, str) or isinstance(item, unicode):
1465 return self.name == item
1466 elif not (item is None):
1467 return self.name == item.name
1468 return False
1469
1471 '''
1472 Compares the name of the node.
1473 '''
1474 if isinstance(item, str) or isinstance(item, unicode):
1475 return self.name > item
1476 elif not (item is None):
1477 return self.name > item.name
1478 return False
1479
1480
1481
1482
1483
1484
1485 -class NodeTreeModel(QStandardItemModel):
1486 '''
1487 The model to show the nodes running in a ROS system or loaded by a launch
1488 configuration.
1489 '''
1490
1491
1492
1493
1494
1495
1496
1497 header = [('Name', 450),
1498 ('Info', -1)]
1499
1500
1501 hostInserted = Signal(HostItem)
1502 '''@ivar: the Qt signal, which is emitted, if a new host was inserted.
1503 Parameter: U{QtCore.QModelIndex<https://srinikom.github.io/pyside-docs/PySide/QtCore/QModelIndex.html>} of the inserted host item'''
1504
1505 - def __init__(self, host_address, masteruri, parent=None):
1506 '''
1507 Initialize the model.
1508 '''
1509 super(NodeTreeModel, self).__init__(parent)
1510 self.setColumnCount(len(NodeTreeModel.header))
1511 self.setHorizontalHeaderLabels([label for label, _ in NodeTreeModel.header])
1512 self._local_host_address = host_address
1513 self._local_masteruri = masteruri
1514 self._std_capabilities = {'': {'SYSTEM': {'images': [],
1515 'nodes': ['/rosout',
1516 '/master_discovery',
1517 '/zeroconf',
1518 '/master_sync',
1519 '/node_manager',
1520 '/dynamic_reconfigure/*'],
1521 'type': '',
1522 'description': 'This group contains the system management nodes.'}}}
1523
1524
1525 self.parameterHandler = ParameterHandler()
1526
1527 self.parameterHandler.parameter_values_signal.connect(self._on_param_values)
1528
1529
1530 @property
1532 return self._local_host_address
1533
1534 - def flags(self, index):
1535 if not index.isValid():
1536 return Qt.NoItemFlags
1537 return Qt.ItemIsEnabled | Qt.ItemIsSelectable
1538
1540 if host_item is not None:
1541 cap = self._std_capabilities
1542 mastername = roslib.names.SEP.join(['', host_item.mastername, '*', 'default_cfg'])
1543 if mastername not in cap['']['SYSTEM']['nodes']:
1544 cap['']['SYSTEM']['nodes'].append(mastername)
1545 host_item.addCapabilities('', cap, host_item.masteruri)
1546 return cap
1547 return dict(self._std_capabilities)
1548
1550 '''
1551 Searches for the host item in the model. If no item is found a new one will
1552 created and inserted in sorted order.
1553 @param masteruri: ROS master URI
1554 @type masteruri: C{str}
1555 @param address: the address of the host
1556 @type address: C{str}
1557 @return: the item associated with the given master
1558 @rtype: L{HostItem}
1559 '''
1560 if masteruri is None:
1561 return None
1562 resaddr = nm.nameres().hostname(address)
1563 host = (masteruri, resaddr)
1564
1565 local = (self.local_addr in [address] + nm.nameres().resolve_cached(address) and
1566 self._local_masteruri == masteruri)
1567
1568 root = self.invisibleRootItem()
1569 for i in range(root.rowCount()):
1570 if root.child(i) == host:
1571 return root.child(i)
1572 elif root.child(i) > host:
1573 hostItem = HostItem(masteruri, resaddr, local)
1574 self.insertRow(i, hostItem)
1575 self.hostInserted.emit(hostItem)
1576 self._set_std_capabilities(hostItem)
1577 return hostItem
1578 hostItem = HostItem(masteruri, resaddr, local)
1579 self.appendRow(hostItem)
1580 self.hostInserted.emit(hostItem)
1581 self._set_std_capabilities(hostItem)
1582 return hostItem
1583
1617
1618
1619
1626
1628 '''
1629 Updates the capability groups of nodes from ROS parameter server.
1630 @param masteruri: The URI of the ROS parameter server
1631 @type masteruri: C{str}
1632 @param code: The return code of the request. If not 1, the message is set and the list can be ignored.
1633 @type code: C{int}
1634 @param msg: The message of the result.
1635 @type msg: C{str}
1636 @param params: The dictionary the parameter names and request result.
1637 @type params: C{dict(paramName : (code, statusMessage, parameterValue))}
1638 '''
1639 host = nm.nameres().address(masteruri)
1640 if host is None:
1641
1642 host = get_hostname(masteruri)
1643 hostItem = self.get_hostitem(masteruri, host)
1644 changed = False
1645 if hostItem is not None and code == 1:
1646 capabilities = self._set_std_capabilities(hostItem)
1647 available_ns = set([''])
1648 available_groups = set(['SYSTEM'])
1649
1650 for p, (code_n, _, val) in params.items():
1651 nodename = roslib.names.namespace(p).rstrip(roslib.names.SEP)
1652 ns = roslib.names.namespace(nodename).rstrip(roslib.names.SEP)
1653 if not ns:
1654 ns = roslib.names.SEP
1655 available_ns.add(ns)
1656 if code_n == 1:
1657
1658 if val:
1659 available_groups.add(val)
1660 if ns not in capabilities:
1661 capabilities[ns] = dict()
1662 if val not in capabilities[ns]:
1663 capabilities[ns][val] = {'images': [], 'nodes': [], 'type': '', 'description': 'This group is created from `capability_group` parameter of the node defined in ROS parameter server.'}
1664 if nodename not in capabilities[ns][val]['nodes']:
1665 capabilities[ns][val]['nodes'].append(nodename)
1666 changed = True
1667 else:
1668 try:
1669 for group, _ in capabilities[ns].items():
1670 try:
1671
1672 groupItem = hostItem.getGroupItem(roslib.names.ns_join(ns, group))
1673 if groupItem is not None:
1674 nodeItems = groupItem.getNodeItemsByName(nodename, True)
1675 for item in nodeItems:
1676 item.remConfig('')
1677 capabilities[ns][group]['nodes'].remove(nodename)
1678
1679 if not capabilities[ns][group]['nodes']:
1680 del capabilities[ns][group]
1681 if not capabilities[ns]:
1682 del capabilities[ns]
1683 groupItem.updateDisplayedConfig()
1684 changed = True
1685 except:
1686 pass
1687 except:
1688 pass
1689
1690 for ns in capabilities.keys():
1691 if ns and ns not in available_ns:
1692 del capabilities[ns]
1693 changed = True
1694 else:
1695 for group in capabilities[ns].keys():
1696 if group and group not in available_groups:
1697 del capabilities[ns][group]
1698 changed = True
1699
1700 if changed:
1701 if capabilities:
1702 hostItem.addCapabilities('', capabilities, hostItem.masteruri)
1703 hostItem.clearUp()
1704 else:
1705 rospy.logwarn("Error on retrieve \'capability group\' parameter from %s: %s", utf8(masteruri), msg)
1706
1708 '''
1709 Sets the default capabilities description, which is assigned to each new
1710 host.
1711 @param capabilities: the structure for capabilities
1712 @type capabilities: C{dict(namespace: dict(group:dict('type' : str, 'description' : str, 'nodes' : [str])))}
1713 '''
1714 self._std_capabilities = capabilities
1715
1717 '''
1718 Adds groups to the model
1719 @param masteruri: ROS master URI
1720 @type masteruri: C{str}
1721 @param host_address: the address the host
1722 @type host_address: C{str}
1723 @param cfg: the configuration name (launch file name or tupel for default configuration)
1724 @type cfg: C{str or (str, str))}
1725 @param capabilities: the structure for capabilities
1726 @type capabilities: C{dict(namespace: dict(group:dict('type' : str, 'description' : str, 'nodes' : [str])))}
1727 '''
1728 hostItem = self.get_hostitem(masteruri, host_address)
1729 if hostItem is not None:
1730
1731 hostItem.addCapabilities(cfg, capabilities, hostItem.masteruri)
1732 self.removeEmptyHosts()
1733
1735 '''
1736 Adds nodes to the model. If the node is already in the model, only his
1737 configuration list will be extended.
1738 @param masteruri: ROS master URI
1739 @type masteruri: C{str}
1740 @param host_address: the address the host
1741 @type host_address: C{str}
1742 @param nodes: a dictionary with node names and their configurations
1743 @type nodes: C{dict(str : str)}
1744 '''
1745 hostItem = self.get_hostitem(masteruri, host_address)
1746 if hostItem is not None:
1747 groups = set()
1748 for (name, cfg) in nodes.items():
1749 items = hostItem.getNodeItemsByName(name)
1750 for item in items:
1751 if item.parent_item is not None:
1752 groups.add(item.parent_item)
1753
1754 if isinstance(item.parent_item, HostItem):
1755 item.addConfig(cfg)
1756 elif hostItem.is_in_cap_group(item.name, cfg, rospy.names.namespace(item.name).rstrip(rospy.names.SEP), item.parent_item.name):
1757 item.addConfig(cfg)
1758
1759 elif hostItem.is_in_cap_group(item.name, '', '', item.parent_item.name):
1760 item.addConfig(cfg)
1761 else:
1762 item.addConfig(cfg)
1763 if not items:
1764
1765 node_info = NodeInfo(name, masteruri)
1766 hostItem.addNode(node_info, cfg)
1767
1768 items = hostItem.getNodeItemsByName(name)
1769 for item in items:
1770 if item.parent_item is not None:
1771 groups.add(item.parent_item)
1772
1773 for g in groups:
1774 g.updateDisplayedConfig()
1775 hostItem.clearUp()
1776 self.removeEmptyHosts()
1777
1778
1779
1781 '''
1782 Removes nodes from the model. If node is running or containing in other
1783 launch or default configurations , only his configuration list will be
1784 reduced.
1785 @param cfg: the name of the confugration to close
1786 @type cfg: C{str}
1787 '''
1788 for i in reversed(range(self.invisibleRootItem().rowCount())):
1789 host = self.invisibleRootItem().child(i)
1790 items = host.getNodeItems()
1791 groups = set()
1792 for item in items:
1793 removed = item.remConfig(cfg)
1794 if removed and item.parent_item is not None:
1795 groups.add(item.parent_item)
1796 for g in groups:
1797 g.updateDisplayedConfig()
1798 host.remCapablities(cfg)
1799 host.clearUp()
1800 if host.rowCount() == 0:
1801 self.invisibleRootItem().removeRow(i)
1802 elif groups:
1803
1804 self._requestCapabilityGroupParameter(host)
1805
1807
1808 for i in reversed(range(self.invisibleRootItem().rowCount())):
1809 host = self.invisibleRootItem().child(i)
1810 if host.rowCount() == 0 or not host.remote_launched_nodes_updated():
1811 self.invisibleRootItem().removeRow(i)
1812
1814 for i in reversed(range(self.invisibleRootItem().rowCount())):
1815 host = self.invisibleRootItem().child(i)
1816 if host is not None:
1817 nodes = host.getNodeItemsByName(node_name)
1818 for n in nodes:
1819 if n.has_running:
1820 return True
1821 return False
1822
1823 - def getNode(self, node_name, masteruri):
1824 '''
1825 Since the same node can be included by different groups, this method searches
1826 for all nodes with given name and returns these items.
1827 @param node_name: The name of the node
1828 @type node_name: C{str}
1829 @return: The list with node items.
1830 @rtype: C{[U{QtGui.QStandardItem<https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>}]}
1831 '''
1832 for i in reversed(range(self.invisibleRootItem().rowCount())):
1833 host = self.invisibleRootItem().child(i)
1834 if host is not None and (masteruri is None or host.masteruri == masteruri):
1835 res = host.getNodeItemsByName(node_name)
1836 if res:
1837 return res
1838 return []
1839
1841 '''
1842 Returns a list with all known running nodes.
1843 @rtype: C{[str]}
1844 '''
1845 running_nodes = list()
1846
1847 for i in reversed(range(self.invisibleRootItem().rowCount())):
1848 host = self.invisibleRootItem().child(i)
1849 if host is not None:
1850 running_nodes[len(running_nodes):] = host.getRunningNodes()
1851 return running_nodes
1852
1854 '''
1855 If there are a synchronization running, you have to avoid to running the
1856 node with the same name on different hosts. This method helps to find the
1857 nodes with same name running on other hosts and loaded by a configuration.
1858 The nodes loaded by a configuration will be inform about a currently running
1859 nodes, so a warning can be displayed!
1860 @param running_nodes: The dictionary with names of running nodes and their masteruri
1861 @type running_nodes: C{dict(str:str)}
1862 @param is_sync_running: If the master_sync is running, the nodes are marked
1863 as ghost nodes. So they are handled as running nodes, but has not run
1864 informations. This nodes are running on remote host, but are not
1865 syncronized because of filter or errors.
1866 @type is_sync_running: bool
1867 '''
1868 for i in reversed(range(self.invisibleRootItem().rowCount())):
1869 host = self.invisibleRootItem().child(i)
1870 if host is not None:
1871 host.markNodesAsDuplicateOf(running_nodes, is_sync_running)
1872
1874 '''
1875 Updates the description of a host.
1876 @param masteruri: ROS master URI of the host to update
1877 @type masteruri: C{str}
1878 @param host: host to update
1879 @type host: C{str}
1880 @param descr_type: the type of the robot
1881 @type descr_type: C{str}
1882 @param descr_name: the name of the robot
1883 @type descr_name: C{str}
1884 @param descr: the description of the robot as a U{http://docutils.sourceforge.net/rst.html|reStructuredText}
1885 @type descr: C{str}
1886 '''
1887 root = self.invisibleRootItem()
1888 for i in range(root.rowCount()):
1889 if root.child(i) == (utf8(masteruri), utf8(host)):
1890 h = root.child(i)
1891 h.updateDescription(descr_type, descr_name, descr)
1892 return h.updateTooltip()
1893