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 QStandardItem, QStandardItemModel
37
38 from master_discovery_fkie.common import get_hostname
39 from node_manager_fkie.common import lnamespace, namespace, normns, utf8
40 import node_manager_fkie as nm
44 '''
45 The service item stored in the service model. This class stores the service as
46 U{master_discovery_fkie.ServiceInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.ServiceInfo>}.
47 The name of the service is represented in HTML.
48 '''
49
50 ITEM_TYPE = QStandardItem.UserType + 37
51 NAME_ROLE = Qt.UserRole + 1
52 TYPE_ROLE = Qt.UserRole + 2
53 NODENAMES_ROLE = Qt.UserRole + 3
54
55 - def __init__(self, service, parent=None):
56 '''
57 Initialize the service item.
58 @param service: the service object to view
59 @type service: U{master_discovery_fkie.ServiceInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.ServiceInfo>}
60 '''
61 QStandardItem.__init__(self, service.name)
62 self._parent_item = parent
63 self.service = service
64 '''@ivar: service info as U{master_discovery_fkie.ServiceInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.ServiceInfo>}.'''
65 self._with_namespace = rospy.names.SEP in service.name
66
67 @property
70
71 @name.setter
72 - def name(self, new_name):
74
75 @property
77 stype = ''
78 try:
79 stype = utf8(self.service.get_service_class(False))
80 except Exception:
81 pass
82 return stype
83
84 @property
86 return self._parent_item
87
88 @parent_item.setter
90 self._parent_item = parent_item
91 if parent_item is None:
92 self.setText(self.text())
93 self._with_namespace = rospy.names.SEP in self.text()
94 else:
95 new_name = self.text().replace(parent_item.get_namespace(), '', 1)
96 self.setText(new_name)
97 self._with_namespace = rospy.names.SEP in new_name
98
99 @property
101 '''
102 Returns `True` if the topic name contains a '/' in his name
103
104 :rtype: bool
105 '''
106 return self._with_namespace
107
109 '''
110 Updates the view
111 '''
112 if service_info is not None:
113 self.service = service_info
114 self.updateServiceView()
115
117 '''
118 Updates the view of the service on changes.
119
120 :param parent: the item containing this item
121 :type parent: U{QtGui.QStandardItem<https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>}
122 '''
123 if self.parent_item is not None:
124
125 child = self.parent_item.child(self.row(), 1)
126 if child is not None:
127 self.updateTypeView(self.service, child)
128
131
132 @classmethod
134 '''
135 Creates the list of the items from service. This list is used for the
136 visualization of service data as a table row.
137
138 :param service: the service data
139 :type service: U{master_discovery_fkie.ServiceInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.ServiceInfo>}
140 :return: the list for the representation as a row
141 :rtype: C{[L{ServiceItem} or U{QtGui.QStandardItem<https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>}, ...]}
142 '''
143 items = []
144 item = ServiceItem(service, parent=root)
145
146
147 items.append(item)
148 typeItem = QStandardItem()
149 ServiceItem.updateTypeView(service, typeItem)
150 items.append(typeItem)
151 return items
152
153 @classmethod
155 '''
156 Updates the representation of the column contains the type of the service.
157 @param service: the service data
158 @type service: U{master_discovery_fkie.ServiceInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.ServiceInfo>}
159 @param item: corresponding item in the model
160 @type item: L{ServiceItem}
161 '''
162 try:
163 if service.isLocal and service.type:
164 service_class = service.get_service_class(nm.is_local(get_hostname(service.uri)))
165 item.setText(service_class._type)
166 elif service.type:
167 item.setText(service.type)
168 else:
169 item.setText('unknown type')
170
171
172
173
174
175
176
177
178
179
180 item.setToolTip('')
181 except Exception:
182 if not service.isLocal:
183 tooltip = ''.join(['<h4>', 'Service type is not available due to he running on another host.', '</h4>'])
184 item.setToolTip(''.join(['<div>', tooltip, '</div>']))
185
186 - def data(self, role):
195
197 '''
198 Compares the name of service.
199 '''
200 if isinstance(item, str) or isinstance(item, unicode):
201 return self.service.name.lower() == item.lower()
202 elif not (item is None):
203 return self.service.name.lower() == item.service.name.lower()
204 return False
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220 -class ServiceGroupItem(QStandardItem):
221 '''
222 The ServiceGroupItem stores the information about a group of nodes.
223 '''
224 ITEM_TYPE = Qt.UserRole + 45
225
226 - def __init__(self, name, parent=None, is_group=False):
227 '''
228 Initialize the ServiceGroupItem object with given values.
229
230 :param str name: the name of the group
231 :param parent: the parent item. In most cases this is the HostItem. The variable is used to determine the different columns of the NodeItem.
232 :type parent: :class:`QtGui.QStandardItem` <https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>
233 :param bool is_group: True if this is a capability group. In other case it is a namespace group.
234 '''
235 dname = name
236 if is_group:
237 dname = '{%s}' % name
238 elif name != rospy.names.SEP:
239 dname = '%s/' % name
240 else:
241 dname = 'services@master/'
242 QStandardItem.__init__(self, dname)
243 self.parent_item = parent
244 self._name = name
245 self._is_group = is_group
246 self.is_system_group = name == 'SYSTEM'
247
248 @property
250 '''
251 The name of this group.
252
253 :rtype: str
254 '''
255 return self._name
256
257 @name.setter
258 - def name(self, new_name):
259 '''
260 Set the new name of this group and updates the displayed name of the item.
261
262 :param str new_name: The new name of the group. Used also to identify the group.
263 '''
264 self._name = new_name
265 if self._is_group:
266 self.setText('{' + self._name + '}')
267 else:
268 self.setText(self._name + '/')
269
270 @property
272 return self._is_group
273
282
284 '''
285 :retrun: Returns count of nodes inside this group.
286 :rtype: int
287 '''
288 result = 0
289 for i in range(self.rowCount()):
290 item = self.child(i)
291 if isinstance(item, ServiceGroupItem):
292 result += item.count_nodes()
293 elif isinstance(item, ServiceItem):
294 result += 1
295 return result
296
298 '''
299 Since the same node can be included by different groups, this method searches
300 for all nodes with given name and returns these items.
301
302 :param str name: The name of the topic
303 :param bool recursive: Searches in (sub) groups
304 :return: The list with node items.
305 :rtype: list(:class:`QtGui.QStandardItem` <https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>)
306 '''
307 result = []
308 for i in range(self.rowCount()):
309 item = self.child(i)
310 if isinstance(item, ServiceGroupItem):
311 if recursive:
312 result[len(result):] = item.get_service_items_by_name(name)
313 elif isinstance(item, ServiceItem) and item == name:
314 return [item]
315 return result
316
318 '''
319 Returns all nodes in this group and subgroups.
320
321 :param bool recursive: returns the nodes of the subgroups
322 :return: The list with node items.
323 :rtype: list(:class:`QtGui.QStandardItem` <https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>)
324 '''
325 result = []
326 for i in range(self.rowCount()):
327 item = self.child(i)
328 if isinstance(item, ServiceGroupItem):
329 if recursive:
330 result[len(result):] = item.get_service_items()
331 elif isinstance(item, ServiceItem):
332 result.append(item)
333 return result
334
335 @classmethod
337 '''
338 Creates the list of the items for this group. This list is used for the
339 visualization of group data as a table row.
340
341 :param str name: the group name
342 :return: the list for the representation as a row
343 :rtype: C{[L{ServiceGroupItem} or U{QStandardItem<https://srinikom.github.io/pyside-docs/PySide/QtGui/QStandardItem.html>}, ...]}
344 '''
345 items = []
346 item = ServiceGroupItem(name, parent, is_group)
347 items.append(item)
348 typeItem = QStandardItem()
349 items.append(typeItem)
350 return items
351
353 '''
354 Returns a ServiceGroupItem with given name. If no group with this name exists, a
355 new one will be created. The given name will be split by slashes if exists
356 and subgroups are created.
357
358 :param str group_name: the name of the group
359 :param bool is_group: True if it is a capability group. False if a namespace group. (Default: True)
360 :param bool nocreate: avoid creation of new group if not exists. (Default: False)
361 :return: The group with given name of None if `nocreate` is True and group not exists.
362 :rtype: :class:`ServiceGroupItem`
363 '''
364 lns, rns = group_name, ''
365 if nm.settings().group_nodes_by_namespace:
366 lns, rns = lnamespace(group_name)
367 if lns == rospy.names.SEP:
368 lns, rns = lnamespace(rns)
369 if lns == rospy.names.SEP:
370 return self
371 for i in range(self.rowCount()):
372 item = self.child(i)
373 if isinstance(item, ServiceGroupItem):
374 if item == lns:
375 if rns:
376 return item.get_group_item(rns, is_group)
377 return item
378 elif item > lns and not nocreate:
379 items = ServiceGroupItem.create_item_list(lns, self, is_group=(is_group and not rns))
380 self.insertRow(i, items)
381 if rns:
382 return items[0].get_group_item(rns, is_group)
383 return items[0]
384 if nocreate:
385 return None
386 items = ServiceGroupItem.create_item_list(lns, self, is_group=(is_group and not rns))
387 self.appendRow(items)
388 if rns:
389 return items[0].get_group_item(rns, is_group)
390 return items[0]
391
393 '''
394 Adds a new topic with given name.
395
396 :param service: the TopicInfo of the node to create
397 :type service: :class:`TopicInfo`
398 '''
399 group_item = self
400 if nm.settings().group_nodes_by_namespace:
401 ns = namespace(service.name)
402 if ns != rospy.names.SEP:
403
404 group_item = self.get_group_item(ns, False)
405
406 new_item_row = ServiceItem.create_item_list(service, self)
407 group_item._add_row(new_item_row)
408
413
414 - def clearup(self, fixed_node_names=None):
415 '''
416 Removes not running and not configured nodes.
417
418 :param list(str) fixed_node_names: If the list is not None, the node not in the list are set to not running!
419 '''
420 self._clearup(fixed_node_names)
421 self._clearup_reinsert()
422 self._clearup_riseup()
423
424 - def _clearup(self, fixed_node_names=None):
425 '''
426 Removes not running and not configured nodes.
427
428 :param list(str) fixed_node_names: If the list is not None, the node not in the list are set to not running!
429 '''
430 removed = False
431
432 for i in reversed(range(self.rowCount())):
433 item = self.child(i)
434 if isinstance(item, ServiceItem):
435 pass
436 else:
437 removed = item._clearup(fixed_node_names) or removed
438 if self.rowCount() == 0 and self.parent_item is not None:
439 self.parent_item._remove_group(self.name)
440 return removed
441
443 inserted = False
444 for i in reversed(range(self.rowCount())):
445 item = self.child(i)
446 if isinstance(item, ServiceItem):
447 if item.with_namespace:
448 group_item = self.get_group_item(namespace(item.name), False, nocreate=True)
449 if group_item is not None and group_item != self:
450 inserted = True
451 row = self.takeRow(i)
452 group_item._add_row(row)
453 else:
454 inserted = item._clearup_reinsert() or inserted
455 return inserted
456
474
476 for i in reversed(range(self.rowCount())):
477 item = self.child(i)
478 if type(item) == ServiceGroupItem and item == name and item.rowCount() == 0:
479 self.removeRow(i)
480
497
506
508 '''
509 Returns for given topics the list of QModelIndex in this model.
510
511 :param [str] services: the list of service names
512 :return: the list of QModelIndex
513 :rtype: [QtCore.QModelIndex]
514 '''
515 result = []
516 for i in range(self.rowCount()):
517 item = self.child(i)
518 if type(item) == ServiceGroupItem:
519 result[len(result):] = item.index_from_names(services)
520 elif type(item) == ServiceItem:
521 if item.service.name in services:
522 result.append(item.index())
523 return result
524
527
529 '''
530 Compares the name of the group.
531 '''
532 if isinstance(item, str) or isinstance(item, unicode):
533 return self.name.lower() == item.lower()
534 elif not (item is None):
535 return self.name.lower() == item.name.lower()
536 return False
537
539 return not (self == item)
540
542 '''
543 Compares the name of the group.
544 '''
545 if isinstance(item, str) or isinstance(item, unicode):
546
547 if self.is_system_group:
548 if self.name.lower() != item.lower():
549 return True
550 elif item.lower() == 'system':
551 return False
552 return self.name.lower() > item.lower()
553 elif not (item is None):
554
555 if item.is_system_group:
556 if self.name.lower() != item.lower():
557 return True
558 elif self.is_syste_group:
559 return False
560 return self.name.lower() > item.name.lower()
561 return False
562
565 '''
566 The model to manage the list with services in ROS network.
567 '''
568 header = [('Name', 300),
569 ('Type', -1)]
570 '''@ivar: the list with columns C{[(name, width), ...]}'''
571
573 '''
574 Creates a new list model.
575 '''
576 QStandardItemModel.__init__(self)
577 self.setColumnCount(len(ServiceModel.header))
578 self.setHorizontalHeaderLabels([label for label, _ in ServiceModel.header])
579 self.pyqt_workaround = dict()
580 topics = ['*/get_loggers', '*/set_logger_level']
581 def_list = ['\A' + n.strip().replace('*', '.*') + '\Z' for n in topics]
582 self._re_cap_systopics = re.compile('|'.join(def_list), re.I)
583 root_items = ServiceGroupItem.create_item_list(rospy.names.SEP, self.invisibleRootItem(), False)
584 self.invisibleRootItem().appendRow(root_items)
585 self._pyqt_workaround_add(rospy.names.SEP, root_items[0])
586
588 '''
589 :param index: parent of the list
590 :type index: QtCore.QModelIndex<https://srinikom.github.io/pyside-docs/PySide/QtCore/QModelIndex.html>
591 :return: Flag or the requestet item
592 :rtype: QtCore.Qt.ItemFlag<https://srinikom.github.io/pyside-docs/PySide/QtCore/Qt.html>
593 :see: http://www.pyside.org/docs/pyside-1.0.1/PySide/QtCore/Qt.html
594 '''
595 if not index.isValid():
596 return Qt.NoItemFlags
597 return Qt.ItemIsEnabled | Qt.ItemIsSelectable
598
600 match = False
601 try:
602 match = self._re_cap_systopics.match(topic_name)
603 except Exception:
604 pass
605 if match:
606 root = self.get_root_group()
607 for i in range(root.rowCount()):
608 item = root.child(i)
609 if type(item) == ServiceGroupItem:
610 if item == 'SYSTEM' and item.is_group:
611 return item
612 items = ServiceGroupItem.create_item_list('SYSTEM', root, True)
613 root.appendRow(items)
614 self.pyqt_workaround['{SYSTEM}'] = items[0]
615 return items[0]
616 return None
617
619 root = self.invisibleRootItem()
620 for i in range(root.rowCount()):
621 item = root.child(i)
622 if type(item) == ServiceGroupItem:
623 if item == rospy.names.SEP:
624 return item
625 return None
626
627 - def updateModelData(self, services, added_srvs, updated_srvs, removed_srvs):
628 '''
629 Updates the service list model. New services will be inserted in sorting
630 order. Not available services removed from the model.
631
632 :param services: The dictionary with services
633 :type services: dict(service name : U{master_discovery_fkie.ServiceInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.ServiceInfo>})
634 :param added_srvs: the list of new services in the :service: list
635 :type added_srvs: list or set
636 :param updated_srvs: the list of updated services in the :service: list
637 :type updated_srvs: list or set
638 :param removed_srvs: the list of removed services in the :service: list
639 :type removed_srvs: list or set
640 '''
641
642 parents = set()
643 for rm_topic in removed_srvs:
644 new_parent = self._remove_node(rm_topic)
645 if type(new_parent) == ServiceGroupItem:
646 parents.add(new_parent)
647 for parent in parents:
648 parent.clearup()
649
650 root = self.invisibleRootItem()
651 for i in reversed(range(root.rowCount())):
652 item = root.child(i)
653 if type(item) == ServiceGroupItem:
654 item.update_view(updated_srvs, services)
655
656 for name in added_srvs:
657 try:
658 topic = services[name]
659
660 sys_group = self.get_cap_group(name)
661 if sys_group is not None:
662 sys_group.add_node(topic)
663 else:
664
665 root_group = self.get_root_group()
666 if root_group is not None:
667 root_group.add_node(topic)
668 except Exception:
669 import traceback
670 print traceback.format_exc()
671 pass
672
674 '''
675 Returns for given services the list of QModelIndex in this model.
676
677 :param [str] services: the list of service names
678 :return: the list of QModelIndex
679 :rtype: [QtCore.QModelIndex<https://srinikom.github.io/pyside-docs/PySide/QtCore/QModelIndex.html>}]
680 '''
681 result = []
682 root = self.invisibleRootItem()
683 for i in range(root.rowCount()):
684 item = root.child(i)
685 if type(item) == ServiceGroupItem:
686 result[len(result):] = item.index_from_names(services)
687 elif type(item) == ServiceItem:
688 if item.service.name in services:
689 result.append(item.index())
690 return result
691
693 root = self.invisibleRootItem()
694 for i in range(root.rowCount()):
695 item = root.child(i)
696 if type(item) == ServiceGroupItem:
697 parent = item.remove_node(name)
698 if parent is not None:
699 return parent
700 return None
701
703 self.pyqt_workaround[name] = item
704
706 try:
707 del self.pyqt_workaround[name]
708 except Exception:
709 pass
710