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 urlparse import urlparse
34
35 from PySide import QtCore
36 from PySide import QtGui
37
38 import roslib
39 import rospy
40 import node_manager_fkie as nm
41 from master_discovery_fkie.master_info import NodeInfo
42
43
44
45
46
47
48 -class GroupItem(QtGui.QStandardItem):
49 '''
50 The GroupItem stores the information about a group of nodes.
51 '''
52 ITEM_TYPE = QtCore.Qt.UserRole + 25
53
55 '''
56 Initialize the GroupItem 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: L{PySide.QtGui.QStandardItem}
62 '''
63 QtGui.QStandardItem.__init__(self, GroupItem.toHTML(name))
64 self.parent_item = parent
65 self._name = name
66 self.setIcon(QtGui.QIcon(':/icons/state_off.png'))
67 self.descr_type = self.descr_name = self.descr = ''
68 self.descr_images = []
69 self._capcabilities = dict()
70 '''
71 @ivar: dict(config : dict(namespace: dict(group:dict('type' : str, 'images' : [str], 'description' : str, 'nodes' : [str]))))
72 '''
73
74 @property
76 '''
77 The name of this group.
78 @rtype: C{str}
79 '''
80 return self._name
81
82 @name.setter
83 - def name(self, new_name):
84 '''
85 Set the new name of this group and updates the displayed name of the item.
86 @param new_name: The new name of the group. Used also to identify the group.
87 @type new_name: C{str}
88 '''
89 self._name = new_name
90 self.setText(GroupItem.toHTML(self._name))
91
93 '''
94 Add new capabilities. Based on this capabilities the node are grouped. The
95 view will be updated.
96 @param config: The name of the configuration containing this new capabilities.
97 @type config: C{str}
98 @param masteruri: The masteruri is used only used, if new nodes are created.
99 @type masteruri: C{str}
100 @param capabilities: The capabilities, which defines groups and containing nodes.
101 @type capabilities: C{dict(namespace: dict(group:dict('type' : str, 'images' : [str], 'description' : str, 'nodes' : [str])))}
102 '''
103 self._capcabilities[config] = capabilities
104
105 for ns, groups in capabilities.items():
106 for group, descr in groups.items():
107
108 nodes = descr['nodes']
109 if nodes:
110 groupItem = self.getGroupItem(roslib.names.ns_join(ns, group))
111 groupItem.descr_name = group
112 if descr['type']:
113 groupItem.descr_type = descr['type']
114 if descr['description']:
115 groupItem.descr = descr['description']
116 if descr['images']:
117 groupItem.descr_images = list(descr['images'])
118
119 for i in reversed(range(self.rowCount())):
120 item = self.child(i)
121 if isinstance(item, NodeItem) and item.name in nodes:
122 row = self.takeRow(i)
123 groupItem._addRow_sorted(row)
124
125 groupItem.updateDisplayedConfig()
126
127
128 for node_name in nodes:
129 items = groupItem.getNodeItemsByName(node_name)
130 if items:
131 for item in items:
132 item.addConfig(config)
133 else:
134 items = self.getNodeItemsByName(node_name)
135 if items:
136
137 groupItem.addNode(items[0].node_info, config)
138 else:
139 groupItem.addNode(NodeInfo(node_name, masteruri), config)
140 groupItem.updateIcon()
141
142
144 '''
145 Removes internal entry of the capability, so the new nodes are not grouped.
146 To update view L{NodeTreeModel.removeConfigNodes()} and L{GroupItem.clearUp()}
147 must be called.
148 @param config: The name of the configuration containing this new capabilities.
149 @type config: C{str}
150 '''
151 try:
152 if self._capcabilities.has_key(config):
153 del self._capcabilities[config]
154 except:
155 pass
156 else:
157
158 pass
159
161 '''
162 Returns the names of groups, which contains the given node.
163 @param node_name: The name of the node
164 @type node_name: C{str}
165 @param cfg: The name of configuration, which describes the node.
166 @type cfg: C{str}
167 @return: The name of the configuration containing this new capabilities.
168 @rtype: C{dict(config : [str])}
169 '''
170 result = dict()
171 try:
172 if cfg:
173 for ns, groups in self._capcabilities[cfg].items():
174 for group, descr in groups.items():
175 if node_name in descr['nodes']:
176 if not result.has_key(c):
177 result[c] = []
178 result[c].append(roslib.ns_join(ns, group))
179 except:
180 pass
181
182
183 return result
184
186 '''
187 Since the same node can be included by different groups, this method searches
188 for all nodes with given name and returns these items.
189 @param node_name: The name of the node
190 @type node_name: C{str}
191 @param recursive: Searches in (sub) groups
192 @type recursive: C{bool}
193 @return: The list with node items.
194 @rtype: C{[L{PySide.QtGui.QStandardItem}]}
195 '''
196 result = []
197 for i in range(self.rowCount()):
198 item = self.child(i)
199 if isinstance(item, GroupItem):
200 if recursive:
201 result[len(result):] = item.getNodeItemsByName(node_name)
202 elif isinstance(item, NodeItem) and item == node_name:
203 return [item]
204 return result
205
207 '''
208 Returns all nodes in this group and subgroups.
209 @return: The list with node items.
210 @rtype: C{[L{PySide.QtGui.QStandardItem}]}
211 '''
212 result = []
213 for i in range(self.rowCount()):
214 item = self.child(i)
215 if isinstance(item, GroupItem):
216 result[len(result):] = item.getNodeItems()
217 elif isinstance(item, NodeItem):
218 result.append(item)
219 return result
220
222 '''
223 Returns all group items this group
224 @return: The list with group items.
225 @rtype: C{[L{GroupItem}]}
226 '''
227 result = []
228 for i in range(self.rowCount()):
229 item = self.child(i)
230 if isinstance(item, GroupItem):
231 result.append(item)
232 result[len(result):] = item.getGroupItems()
233 return result
234
235
237 '''
238 Returns a GroupItem with given name. If no group with this name exists, a
239 new one will be created.
240 Assumption: No groups in group!!
241 @param group_name: the name of the group
242 @type group_name: C{str}
243 @return: The group with given name
244 @rtype: L{GroupItem}
245 '''
246 for i in range(self.rowCount()):
247 item = self.child(i)
248 if isinstance(item, GroupItem):
249 if item == group_name:
250 return item
251 elif item > group_name:
252 items = []
253 newItem = GroupItem(group_name, self)
254 items.append(newItem)
255 cfgitem = QtGui.QStandardItem()
256 items.append(cfgitem)
257 self.insertRow(i, items)
258 return newItem
259 items = []
260 newItem = GroupItem(group_name, self)
261 items.append(newItem)
262 cfgitem = QtGui.QStandardItem()
263 items.append(cfgitem)
264 self.appendRow(items)
265 return newItem
266
268 '''
269 Adds a new node with given name.
270 @param node: the NodeInfo of the node to create
271 @type node: L{NodeInfo}
272 @param cfg: The configuration, which describes the node
273 @type cfg: C{str}
274 '''
275 groups = self.getCapabilityGroups(node.name, cfg)
276 if groups:
277 for c, group_list in groups.items():
278 for group_name in group_list:
279
280 groupItem = self.getGroupItem(group_name)
281 groupItem.addNode(node)
282 else:
283
284 new_item_row = NodeItem.newNodeRow(node.name, node.masteruri)
285 self._addRow_sorted(new_item_row)
286 new_item_row[0].node_info = node
287
288 if cfg:
289 new_item_row[0].addConfig(cfg)
290
292 for i in range(self.rowCount()):
293 item = self.child(i)
294 if item > row[0].name:
295 self.insertRow(i, row)
296 row[0].parent_item = self
297 return
298 self.appendRow(row)
299 row[0].parent_item = self
300
301 - def clearUp(self, fixed_node_names = None):
302 '''
303 Removes not running and not configured nodes.
304 @param fixed_node_names: If the list is not None, the node not in the list are
305 set to not running!
306 @type fixed_node_names: C{[str]}
307 '''
308
309 groups = self.getGroupItems()
310 for group in groups:
311 group.clearUp(fixed_node_names)
312
313
314 for i in reversed(range(self.rowCount())):
315 item = self.child(i)
316 if isinstance(item, NodeItem):
317
318 if not fixed_node_names is None and not item.name in fixed_node_names:
319 item.node_info = NodeInfo(item.name, item.node_info.masteruri)
320 if not item.is_valid():
321 self.removeRow(i)
322 elif not isinstance(self, HostItem):
323 if item.state == NodeItem.STATE_RUN and len(item.cfgs) == 0:
324
325 if not self.parent_item is None and isinstance(self.parent_item, HostItem):
326 items_in_host = self.parent_item.getNodeItemsByName(item.name, False)
327 if len(items_in_host) == 0:
328 row = self.takeRow(i)
329 self.parent_item._addRow_sorted(row)
330
331 else:
332
333 self.removeRow(i)
334
335
336 for i in reversed(range(self.rowCount())):
337 item = self.child(i)
338 if isinstance(item, GroupItem):
339
340 if item.rowCount() == 0:
341 self.removeRow(i)
342
344 '''
345 Updates the running state of the nodes given in a dictionary.
346 @param nodes: A dictionary with node names and their running state described by L{NodeInfo}.
347 @type nodes: C{dict(str: L{master_discovery_fkie.NodeInfo})}
348 '''
349 for (name, node) in nodes.items():
350
351 items = self.getNodeItemsByName(name)
352 if items:
353 for item in items:
354
355 item.node_info = node
356 else:
357
358 self.addNode(node)
359 self.clearUp(nodes.keys())
360
362 '''
363 Returns the names of all running nodes. A running node is defined by his
364 PID.
365 @see: L{master_dicovery_fkie.NodeInfo}
366 @return: A list with node names
367 @rtype: C{[str]}
368 '''
369 result = []
370 for i in range(self.rowCount()):
371 item = self.child(i)
372 if isinstance(item, GroupItem):
373 result[len(result):] = item.getRunningNodes()
374 elif isinstance(item, NodeItem) and not item.node_info.pid is None:
375 result.append(item.name)
376 return result
377
379 '''
380 While a synchronization same node on different hosts have the same name, the
381 nodes with the same on other host are marked.
382 @param running_nodes: A list with node names, which are running on other hosts.
383 @type running_nodes: C{[str]}
384 '''
385 ignore = ['/master_sync', '/master_discovery', '/node_manager']
386 for i in range(self.rowCount()):
387 item = self.child(i)
388 if isinstance(item, GroupItem):
389 item.markNodesAsDuplicateOf(running_nodes)
390 elif isinstance(item, NodeItem):
391 item.has_running = (item.node_info.uri is None and not item.name in ignore and item.name in running_nodes)
392
417
419 result = ''
420 if items:
421 result = ''.join([result, '<b><u>', title,'</u></b>'])
422 if len(items) > 1:
423 result = ''.join([result, ' [', str(len(items)),']'])
424 result = ''.join([result, '<ul>'])
425 for i in items:
426 result = ''.join([result, '<li>', i, '</li>'])
427 result = ''.join([result, '</ul>'])
428 return result
429
441
443 tooltip = ''
444 if self.descr_type or self.descr_name or self.descr:
445 tooltip = ''.join(['<h4>', self.descr_name, '</h4><dl>'])
446 if self.descr_type:
447 tooltip = ''.join([tooltip, '<dt>Type: ', self.descr_type, '</dt></dl>'])
448 if extended:
449 try:
450 from docutils import examples
451 if self.descr:
452 tooltip = ''.join([tooltip, '<b><u>Detailed description:</u></b>'])
453 tooltip = ''.join([tooltip, examples.html_body(self.descr)])
454 except:
455 import traceback
456 rospy.logwarn("Error while generate description for a tooltip: %s", str(traceback.format_exc()))
457 tooltip = ''.join([tooltip, '<br>'])
458
459 nodes = []
460 for j in range(self.rowCount()):
461 nodes.append(self.child(j).name)
462 if nodes:
463 tooltip = ''.join([tooltip, self._create_html_list('Nodes:', nodes)])
464 return ''.join(['<div>', tooltip, '</div>']) if tooltip else ''
465
467 '''
468 Sets the description of the robot. To update the tooltip of the host item use L{updateTooltip()}.
469 @param descr_type: the type of the robot
470 @type descr_type: C{str}
471 @param descr_name: the name of the robot
472 @type descr_name: C{str}
473 @param descr: the description of the robot as a U{http://docutils.sourceforge.net/rst.html|reStructuredText}
474 @type descr: C{str}
475 '''
476 self.descr_type = descr_type
477 self.descr_name = descr_name
478 self.descr = descr
479
481 '''
482 Updates the configuration representation in other column.
483 '''
484 if not self.parent_item is None:
485
486 cfgs = []
487 for j in range(self.rowCount()):
488 cfgs[len(cfgs):] = self.child(j).cfgs
489 if cfgs:
490 cfgs = list(set(cfgs))
491 cfg_col = self.parent_item.child(self.row(), NodeItem.COL_CFG)
492 if not cfg_col is None and isinstance(cfg_col, QtGui.QStandardItem):
493 cfg_col.setText(str(''.join(['[',str(len(cfgs)),']'])) if len(cfgs) > 1 else "")
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509 has_launches = NodeItem.has_launch_cfgs(cfgs)
510 has_defaults = NodeItem.has_default_cfgs(cfgs)
511 if has_launches and has_defaults:
512 cfg_col.setIcon(QtGui.QIcon(':/icons/crystal_clear_launch_file_def_cfg.png'))
513 elif has_launches:
514 cfg_col.setIcon(QtGui.QIcon(':/icons/crystal_clear_launch_file.png'))
515 elif has_defaults:
516 cfg_col.setIcon(QtGui.QIcon(':/icons/default_cfg.png'))
517 else:
518 cfg_col.setIcon(QtGui.QIcon())
519
520 @classmethod
522 '''
523 Creates a HTML representation of the group name.
524 @param group_name: the name of the group
525 @type group_name: C{str}
526 @return: the HTML representation of the name of the group
527 @rtype: C{str}
528 '''
529 if group_name.rfind('@') > 0:
530 name, sep, host = group_name.rpartition('@')
531 result = ''
532 if sep:
533 result = ''.join(['<div>', name, '<span style="color:gray;">', sep, host, '</span></div>'])
534 else:
535 result = group_name
536 else:
537 ns, sep, name = group_name.rpartition('/')
538 result = ''
539 if sep:
540 result = ''.join(['<div>', '<span style="color:gray;">', ns, sep, '</span><b>[', name, ']</b></div>'])
541 else:
542 result = name
543 return result
544
547
549 '''
550 Compares the name of the group.
551 '''
552 if isinstance(item, str) or isinstance(item, unicode):
553 return self.name.lower() == item.lower()
554 elif not (item is None):
555 return self.name.lower() == item.name.lower()
556 return False
557
559 '''
560 Compares the name of the group.
561 '''
562 if isinstance(item, str) or isinstance(item, unicode):
563 return self.name.lower() > item.lower()
564 elif not (item is None):
565 return self.name.lower() > item.name.lower()
566 return False
567
568
569
570
571
572
573
574 -class HostItem(GroupItem):
575 '''
576 The HostItem stores the information about a host.
577 '''
578 ITEM_TYPE = QtCore.Qt.UserRole + 26
579
580 - def __init__(self, masteruri, address, local, parent=None):
581 '''
582 Initialize the HostItem object with given values.
583 @param masteruri: URI of the ROS master assigned to the host
584 @type masteruri: C{str}
585 @param address: the address of the host
586 @type address: C{str}
587 @param local: is this host the localhost where the node_manager is running.
588 @type local: C{bool}
589 '''
590 name = self.hostNameFrom(masteruri, address)
591 GroupItem.__init__(self, name, parent)
592 self.id = (unicode(masteruri), unicode(address))
593 if QtCore.QFile.exists(''.join([nm.ROBOTS_DIR, name, '.png'])):
594 self.setIcon(QtGui.QIcon(''.join([nm.ROBOTS_DIR, name, '.png'])))
595 else:
596 if local:
597 self.setIcon(QtGui.QIcon(':/icons/crystal_clear_miscellaneous.png'))
598 else:
599 self.setIcon(QtGui.QIcon(':/icons/remote.png'))
600 self.descr_type = self.descr_name = self.descr = ''
601
602 @classmethod
624
625
637
639 tooltip = ''
640 if self.descr_type or self.descr_name or self.descr:
641 tooltip = ''.join(['<h4>', self.descr_name, '</h4><dl>'])
642 if self.descr_type:
643 tooltip = ''.join([tooltip, '<dt>Type: ', self.descr_type, '</dt></dl>'])
644 if extended:
645 try:
646 from docutils import examples
647 if self.descr:
648 tooltip = ''.join([tooltip, '<b><u>Detailed description:</u></b>'])
649 tooltip = ''.join([tooltip, examples.html_body(self.descr, input_encoding='utf8')])
650 except:
651 import traceback
652 rospy.logwarn("Error while generate description for a tooltip: %s", str(traceback.format_exc()))
653 tooltip = ''.join([tooltip, '<br>'])
654 tooltip = ''.join([tooltip, '<h4>ROS Master URI: ', self.id[0], '</h4>'])
655 tooltip = ''.join([tooltip, '<h4>Host: ', self.id[1], '</h4>'])
656
657 capabilities = []
658 for j in range(self.rowCount()):
659 item = self.child(j)
660 if isinstance(item, GroupItem):
661 capabilities.append(item.name)
662 if capabilities:
663 tooltip = ''.join([tooltip, '<b><u>Capabilities:</u></b>'])
664 try:
665 from docutils import examples
666 tooltip = ''.join([tooltip, examples.html_body(''.join(['- ', '\n- '.join(capabilities)]), input_encoding='utf8')])
667 except:
668 import traceback
669 rospy.logwarn("Error while generate description for a tooltip: %s", str(traceback.format_exc()))
670 return ''.join(['<div>', tooltip, '</div>']) if tooltip else ''
671
674
676 '''
677 Compares the address of the host.
678 '''
679 if isinstance(item, str) or isinstance(item, unicode):
680 return str(self.id).lower() == item.lower()
681 elif isinstance(item, tuple):
682 return str(self.id).lower() == str(item).lower()
683 elif isinstance(item, HostItem):
684 return str(self.id).lower() == str(item.id).lower()
685 return False
686
688 '''
689 Compares the address of the host.
690 '''
691 if isinstance(item, str) or isinstance(item, unicode):
692 return str(self.id).lower() > item.lower()
693 elif isinstance(item, tuple):
694 return str(self.id).lower() > str(item).lower()
695 elif isinstance(item, HostItem):
696 return str(self.id).lower() > str(item.id).lower()
697 return False
698
699
700
701
702
703
704 -class NodeItem(QtGui.QStandardItem):
705 '''
706 The NodeItem stores the information about the node using the ExtendedNodeInfo
707 class and represents it in a L{PySide.QtGui.QTreeModel} using the
708 L{PySide.QtGui.QStandardItemModel}
709 '''
710
711 ITEM_TYPE = QtGui.QStandardItem.UserType + 35
712 COL_CFG = 1
713
714
715 STATE_OFF = 0
716 STATE_RUN = 1
717 STATE_WARNING = 2
718 STATE_DUPLICATE = 3
719
721 '''
722 Initialize the NodeItem instance.
723 @param node_info: the node information
724 @type node_info: L{master_discovery_fkie.NodeInfo}
725 '''
726 QtGui.QStandardItem.__init__(self, self.toHTML(node_info.name))
727 self.parent_item = None
728 self._node_info = node_info.copy()
729
730
731
732
733
734
735
736
737
738 self._cfgs = []
739 self._has_running = False
740 self.setIcon(QtGui.QIcon(':/icons/state_off.png'))
741 self._state = NodeItem.STATE_OFF
742
743 @property
746
748 '''
749 Returns C{True} if the node has no configuration and is not running, so the pid
750 and node URI are C{None}
751 @rtype: C{bool}
752 '''
753 return not (self._node_info.pid is None and self._node_info.uri is None and len(self._cfgs) == 0)
754
755 @property
757 return self._node_info.name
758
759 @property
762
763 @property
765 return self._node_info.publishedTopics
766
767 @property
769 return self._node_info.subscribedTopics
770
771 @property
774
775 @property
777 '''
778 Returns the NodeInfo instance of this node.
779 @rtype: L{master_discovery_fkie.NodeInfo}
780 '''
781 return self._node_info
782
783 @node_info.setter
785 '''
786 Sets the NodeInfo and updates the view, if needed.
787 '''
788 abbos_changed = False
789 run_changed = False
790 if self._node_info.publishedTopics != node_info.publishedTopics:
791 abbos_changed = True
792 self._node_info._publishedTopics = list(node_info.publishedTopics)
793 if self._node_info.subscribedTopics != node_info.subscribedTopics:
794 abbos_changed = True
795 self._node_info._subscribedTopics = list(node_info.subscribedTopics)
796 if self._node_info.services != node_info.services:
797 abbos_changed = True
798 self._node_info._services = list(node_info.services)
799 if self._node_info.pid != node_info.pid:
800 self._node_info.pid = node_info.pid
801 run_changed = True
802 if self._node_info.uri != node_info.uri:
803 self._node_info.uri = node_info.uri
804 run_changed = True
805
806 if run_changed and self.is_valid():
807 self.updateDispayedName()
808
809 if not self.parent_item is None and not isinstance(self.parent_item, HostItem):
810 self.parent_item.updateIcon()
811
812 @property
814 return self._node_info.uri
815
816 @property
818 return self._node_info.pid
819
820 @property
822 '''
823 Returns C{True}, if there are exists other nodes with the same name. This
824 variable must be set manually!
825 @rtype: C{bool}
826 '''
827 return self._has_running
828
829 @has_running.setter
831 '''
832 Sets however other node with the same name are running or not (on other hosts)
833 and updates the view oth this item.
834 '''
835 if self._has_running != state:
836 self._has_running = state
837 if self.is_valid():
838 self.updateDispayedName()
839 if not self.parent_item is None and not isinstance(self.parent_item, HostItem):
840 self.parent_item.updateIcon()
841
842
844 '''
845 Updates the name representation of the Item
846 '''
847 tooltip = ''.join(['<h4>', self.node_info.name, '</h4><dl>'])
848 tooltip = ''.join([tooltip, '<dt><b>URI:</b> ', str(self.node_info.uri), '</dt>'])
849 tooltip = ''.join([tooltip, '<dt><b>PID:</b> ', str(self.node_info.pid), '</dt>'])
850 tooltip = ''.join([tooltip, '<dt><b>ORG.MASTERURI:</b> ', str(self.node_info.masteruri), '</dt></dl>'])
851
852 master_discovered = nm.nameres().hasMaster(self.node_info.masteruri)
853 local = False
854
855
856 if not self.node_info.pid is None:
857 self._state = NodeItem.STATE_RUN
858 self.setIcon(QtGui.QIcon(':/icons/state_run.png'))
859 self.setToolTip('')
860 elif not self.node_info.uri is None and not self.node_info.isLocal:
861 self._state = NodeItem.STATE_RUN
862 self.setIcon(QtGui.QIcon(':/icons/state_unknown.png'))
863 tooltip = ''.join([tooltip, '<dl><dt>(Remote nodes will not be ping, so they are always marked running)</dt></dl>'])
864 tooltip = ''.join([tooltip, '</dl>'])
865 self.setToolTip(''.join(['<div>', tooltip, '</div>']))
866
867
868
869
870
871
872
873 elif not self.node_info.uri is None:
874 self._state = NodeItem.STATE_WARNING
875 self.setIcon(QtGui.QIcon(':/icons/crystal_clear_warning.png'))
876 if not self.node_info.isLocal and master_discovered:
877 tooltip = ''.join(['<h4>', self.node_info.name, ' is not local, however the ROS master on this host is discovered, but no information about this node received!', '</h4>'])
878 tooltip = ''.join([tooltip, '</dl>'])
879 self.setToolTip(''.join(['<div>', tooltip, '</div>']))
880 elif self.has_running:
881 self._state = NodeItem.STATE_DUPLICATE
882 self.setIcon(QtGui.QIcon(':/icons/imacadam_stop.png'))
883 tooltip = ''.join(['<h4>Where 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>'])
884 tooltip = ''.join([tooltip, '</dl>'])
885 self.setToolTip(''.join(['<div>', tooltip, '</div>']))
886 else:
887 self._state = NodeItem.STATE_OFF
888 self.setIcon(QtGui.QIcon(':/icons/state_off.png'))
889 self.setToolTip('')
890
891
892
893
894
896 '''
897 Updates the URI representation in other column.
898 '''
899 if not self.parent_item is None:
900 uri_col = self.parent_item.child(self.row(), NodeItem.COL_URI)
901 if not uri_col is None and isinstance(uri_col, QtGui.QStandardItem):
902 uri_col.setText(str(self.node_info.uri) if not self.node_info.uri is None else "")
903
904 @property
906 '''
907 Returns the list with all launch configurations assigned to this item.
908 @rtype: C{[str]}
909 '''
910 return self._cfgs
911
913 '''
914 Add the given configurations to the node.
915 @param cfg: the loaded configuration, which contains this node.
916 @type cfg: C{str}
917 '''
918 if not cfg in self._cfgs:
919 self._cfgs.append(cfg)
920 self.updateDisplayedConfig()
921
923 '''
924 Remove the given configurations from the node.
925 @param cfg: the loaded configuration, which contains this node.
926 @type cfg: C{str}
927 '''
928 if cfg in self._cfgs:
929 self._cfgs.remove(cfg)
930 if self.is_valid():
931 self.updateDisplayedConfig()
932
934 '''
935 Updates the configuration representation in other column.
936 '''
937 if not self.parent_item is None:
938 cfg_col = self.parent_item.child(self.row(), NodeItem.COL_CFG)
939 if not cfg_col is None and isinstance(cfg_col, QtGui.QStandardItem):
940 cfg_col.setText(str(''.join(['[',str(len(self._cfgs)),']'])) if len(self._cfgs) > 1 else "")
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956 has_launches = NodeItem.has_launch_cfgs(self._cfgs)
957 has_defaults = NodeItem.has_default_cfgs(self._cfgs)
958 if has_launches and has_defaults:
959 cfg_col.setIcon(QtGui.QIcon(':/icons/crystal_clear_launch_file_def_cfg.png'))
960 elif has_launches:
961 cfg_col.setIcon(QtGui.QIcon(':/icons/crystal_clear_launch_file.png'))
962 elif has_defaults:
963 cfg_col.setIcon(QtGui.QIcon(':/icons/default_cfg.png'))
964 else:
965 cfg_col.setIcon(QtGui.QIcon())
966 if isinstance(self.parent_item, GroupItem):
967 self.parent_item.updateDisplayedConfig()
968
971
972 @classmethod
974 '''
975 Creates a new node row and returns it as a list with items. This list is
976 used for the visualization of node data as a table row.
977 @param name: the node name
978 @type name: C{str}
979 @param masteruri: the URI or the ROS master assigned to this node.
980 @type masteruri: C{str}
981 @return: the list for the representation as a row
982 @rtype: C{[L{NodeItem}, L{PySide.QtGui.QStandardItem}(Cofigurations), L{PySide.QtGui.QStandardItem}(Node URI)]}
983 '''
984 items = []
985 item = NodeItem(NodeInfo(name, masteruri))
986 items.append(item)
987 cfgitem = QtGui.QStandardItem()
988 items.append(cfgitem)
989
990
991 return items
992
993 @classmethod
999
1000 @classmethod
1006
1007 @classmethod
1009 return isinstance(cfg, tuple)
1010
1011 @classmethod
1013 '''
1014 Creates a HTML representation of the node name.
1015 @param node_name: the name of the node
1016 @type node_name: C{str}
1017 @return: the HTML representation of the name of the node
1018 @rtype: C{str}
1019 '''
1020 ns, sep, name = node_name.rpartition('/')
1021 result = ''
1022 if sep:
1023 result = ''.join(['<div>', '<span style="color:gray;">', str(ns), sep, '</span><b>', name, '</b></div>'])
1024 else:
1025 result = name
1026 return result
1027
1029 '''
1030 Compares the name of the node.
1031 '''
1032 if isinstance(item, str) or isinstance(item, unicode):
1033 return self.name.lower() == item.lower()
1034 elif not (item is None):
1035 return self.name.lower() == item.name.lower()
1036 return False
1037
1039 '''
1040 Compares the name of the node.
1041 '''
1042 if isinstance(item, str) or isinstance(item, unicode):
1043 return self.name.lower() > item.lower()
1044 elif not (item is None):
1045 return self.name.lower() > item.name.lower()
1046 return False
1047
1048
1049
1050
1051
1052
1053 -class NodeTreeModel(QtGui.QStandardItemModel):
1054 '''
1055 The model to show the nodes running in a ROS system or loaded by a launch
1056 configuration.
1057 '''
1058
1059
1060
1061
1062
1063
1064
1065 header = [('Name', 450),
1066 ('Cfgs', -1)]
1067
1068
1069 hostInserted = QtCore.Signal(HostItem)
1070 '''@ivar: the Qt signal, which is emitted, if a new host was inserted.
1071 Parameter: L{QtCore.QModelIndex} of the inserted host item'''
1072
1073 - def __init__(self, host_address, masteruri, parent=None):
1081
1082 @property
1084 return self._local_host_address
1085
1086 - def flags(self, index):
1087 if not index.isValid():
1088 return QtCore.Qt.NoItemFlags
1089 return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
1090
1092 '''
1093 Searches for the host item in the model. If no item is found a new one will
1094 created and inserted in sorted order.
1095 @param masteruri: ROS master URI
1096 @type masteruri: C{str}
1097 @param address: the address of the host
1098 @type address: C{str}
1099 @return: the item associated with the given master
1100 @rtype: L{HostItem}
1101 '''
1102 if masteruri is None:
1103 return None
1104 host = (unicode(masteruri), unicode(address))
1105 local = (self.local_addr == host)
1106
1107
1108 root = self.invisibleRootItem()
1109 for i in range(root.rowCount()):
1110 if root.child(i) == host:
1111 return root.child(i)
1112 elif root.child(i) > host:
1113 hostItem = HostItem(masteruri, address, local)
1114 self.insertRow(i, hostItem)
1115 self.hostInserted.emit(hostItem)
1116 return hostItem
1117 hostItem = HostItem(masteruri, address, local)
1118 self.appendRow(hostItem)
1119 self.hostInserted.emit(hostItem)
1120 return hostItem
1121
1123 '''
1124 Updates the model data.
1125 @param nodes: a dictionary with name and info objects of the nodes.
1126 @type nodes: C{dict(str:L{NodeInfo}, ...)}
1127 '''
1128
1129 hosts = dict()
1130 for (name, node) in nodes.items():
1131 host = (node.masteruri, nm.nameres().getHostname(node.uri if not node.uri is None else node.masteruri))
1132 if not hosts.has_key(host):
1133 hosts[host] = dict()
1134 hosts[host][name] = node
1135
1136 for ((masteruri, host), nodes_filtered) in hosts.items():
1137 hostItem = self.getHostItem(masteruri, host)
1138
1139 if not hostItem is None:
1140
1141
1142
1143 hostItem.updateRunningNodeState(nodes_filtered)
1144
1145 for i in reversed(range(self.invisibleRootItem().rowCount())):
1146 host = self.invisibleRootItem().child(i)
1147 if not hosts.has_key(host.id):
1148 host.updateRunningNodeState({})
1149 self.removeEmptyHosts()
1150
1151
1152
1154 '''
1155 Adds groups to the model
1156 @param masteruri: ROS master URI
1157 @type masteruri: C{str}
1158 @param host_address: the address the host
1159 @type host_address: C{str}
1160 @param cfg: the configuration name (launch file name or tupel for default configuration)
1161 @type cfg: C{str or (str, str))}
1162 @param capabilities: the structure for capabilities
1163 @type capabilities: C{dict(namespace: dict(group:dict('type' : str, 'description' : str, 'nodes' : [str])))}
1164 '''
1165 hostItem = self.getHostItem(masteruri, host_address)
1166 if not hostItem is None:
1167 hostItem.addCapabilities(cfg, capabilities, host_address)
1168 self.removeEmptyHosts()
1169
1171 '''
1172 Adds nodes to the model. If the node is already in the model, only his
1173 configuration list will be extended.
1174 @param masteruri: ROS master URI
1175 @type masteruri: C{str}
1176 @param host_address: the address the host
1177 @type host_address: C{str}
1178 @param nodes: a dictionary with node names and their configurations
1179 @type nodes: C{dict(str : str)}
1180 '''
1181 hostItem = self.getHostItem(masteruri, host_address)
1182 if not hostItem is None:
1183 for (name, cfg) in nodes.items():
1184 items = hostItem.getNodeItemsByName(name)
1185 for item in items:
1186 item.addConfig(cfg)
1187 if not items:
1188
1189 node_info = NodeInfo(name, masteruri)
1190 hostItem.addNode(node_info, cfg)
1191 self.removeEmptyHosts()
1192
1193
1194
1196 '''
1197 Removes nodes from the model. If node is running or containing in other
1198 launch or default configurations , only his configuration list will be
1199 reduced.
1200 @param cfg: the name of the confugration to close
1201 @type cfg: C{str}
1202 '''
1203 for i in reversed(range(self.invisibleRootItem().rowCount())):
1204 host = self.invisibleRootItem().child(i)
1205 items = host.getNodeItems()
1206 for item in items:
1207 item.remConfig(cfg)
1208 host.remCapablities(cfg)
1209 host.clearUp()
1210 if host.rowCount() == 0:
1211 self.invisibleRootItem().removeRow(i)
1212
1214
1215 for i in reversed(range(self.invisibleRootItem().rowCount())):
1216 host = self.invisibleRootItem().child(i)
1217 if host.rowCount() == 0:
1218 self.invisibleRootItem().removeRow(i)
1219
1221 for i in reversed(range(self.invisibleRootItem().rowCount())):
1222 host = self.invisibleRootItem().child(i)
1223 if not host is None:
1224 nodes = host.getNodeItemsByName(node_name)
1225 for n in nodes:
1226 if n.has_running:
1227 return True
1228 return False
1229
1231 '''
1232 Returns a list with all known running nodes.
1233 @rtype: C{[str]}
1234 '''
1235 running_nodes = list()
1236
1237 for i in reversed(range(self.invisibleRootItem().rowCount())):
1238 host = self.invisibleRootItem().child(i)
1239 if not host is None:
1240 running_nodes[len(running_nodes):] = host.getRunningNodes()
1241 return running_nodes
1242
1244 '''
1245 If there are a synchronization running, you have to avoid to running the
1246 node with the same name on different hosts. This method helps to find the
1247 nodes with same name running on other hosts and loaded by a configuration.
1248 The nodes loaded by a configuration will be inform about a currently running
1249 nodes, so a warning can be displayed!
1250 @param running_nodes: A list with node names, which are running on other hosts.
1251 @type running_nodes: C{[str]}
1252 '''
1253 for i in reversed(range(self.invisibleRootItem().rowCount())):
1254 host = self.invisibleRootItem().child(i)
1255 if not host is None:
1256 host.markNodesAsDuplicateOf(running_nodes)
1257
1259 '''
1260 Updates the description of a host.
1261 @param masteruri: ROS master URI of the host to update
1262 @type masteruri: C{str}
1263 @param host: host to update
1264 @type host: C{str}
1265 @param descr_type: the type of the robot
1266 @type descr_type: C{str}
1267 @param descr_name: the name of the robot
1268 @type descr_name: C{str}
1269 @param descr: the description of the robot as a U{http://docutils.sourceforge.net/rst.html|reStructuredText}
1270 @type descr: C{str}
1271 '''
1272 root = self.invisibleRootItem()
1273 for i in range(root.rowCount()):
1274 if root.child(i) == (unicode(masteruri), unicode(host)):
1275 h = root.child(i)
1276 h.updateDescription(descr_type, descr_name, descr)
1277 return h.updateTooltip()
1278