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 os
34 import sys
35
36 from python_qt_binding import QtCore
37 from python_qt_binding import QtGui
38
39 import rospy
40 import node_manager_fkie as nm
41 from common import resolve_paths
42
43
44
45
46
47
49 '''
50 This class is used for visualization of robots or capabilities in header of
51 the capability table. It is also used to manage the displayed robots or
52 capabilities. Furthermore L{QtGui.QHeaderView.paintSection()} method is
53 overridden to paint the images in background of the cell.
54 '''
55
56 description_requested_signal = QtCore.Signal(str, str)
57 '''the signal is emitted by click on a header to show a description.'''
58
60 QtGui.QHeaderView.__init__(self, orientation, parent)
61 self._data = []
62 ''' @ivar a list with dictionaries C{dict('cfgs' : [], 'name': str, 'displayed_name' : str, 'type' : str, 'description' : str, 'images' : [QtGui.QPixmap])}'''
63 if orientation == QtCore.Qt.Horizontal:
64 self.setDefaultAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignBottom)
65 elif orientation == QtCore.Qt.Vertical:
66 self.setDefaultAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignBottom)
67 self.controlWidget = []
68
70 '''
71 Returns the index of the object stored with given name
72 @param name: the name of the item
73 @type name: C{str}
74 @return: the index or -1 if the item was not found
75 @rtype: C{int}
76 '''
77 for index, entry in enumerate(self._data):
78 if entry['name'] == name:
79 return index
80 return -1
81
83 '''
84 The method paint the robot or capability images in the backgroud of the cell.
85 @see: L{QtGui.QHeaderView.paintSection()}
86 '''
87 painter.save()
88 QtGui.QHeaderView.paintSection(self, painter, rect, logicalIndex)
89 painter.restore()
90
91 if logicalIndex in range(len(self._data)) and self._data[logicalIndex]['images']:
92 if len(self._data[logicalIndex]['images']) == 1:
93 pix = self._data[logicalIndex]['images'][0]
94 pix = pix.scaled(rect.width(), rect.height()-20, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
95 self.style().drawItemPixmap(painter, rect, 5, pix)
96 elif len(self._data[logicalIndex]['images']) > 1:
97 new_rect = QtCore.QRect(rect.left(), rect.top(), rect.width(), (rect.height()-20) / 2.)
98 pix = self._data[logicalIndex]['images'][0]
99 pix = pix.scaled(new_rect.width(), new_rect.height(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
100 self.style().drawItemPixmap(painter, new_rect, 5, pix)
101 new_rect = QtCore.QRect(rect.left(), rect.top()+new_rect.height(), rect.width(), new_rect.height())
102 pix = self._data[logicalIndex]['images'][1]
103 pix = pix.scaled(new_rect.width(), new_rect.height(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
104 self.style().drawItemPixmap(painter, new_rect, 5, pix)
105
107 '''
108 Interpret the mouse events to send the description of a robot or capability
109 if the user click on the header.
110 '''
111 QtGui.QHeaderView.mousePressEvent(self, event)
112 index = self.logicalIndexAt(event.pos())
113 if index in range(len(self._data)):
114 suffix = 'Capability'
115 if self.orientation() == QtCore.Qt.Horizontal:
116 suffix = 'Robot'
117 title = ' - '.join([self._data[index]['name'], suffix])
118 text = self._data[index]['description']
119 try:
120 from docutils import examples
121 text = examples.html_body(text)
122 except:
123 import traceback
124 rospy.logwarn("Error while generate description for %s: %s", self._data[index]['name'], traceback.format_exc(1))
125 self.description_requested_signal.emit(title, text)
126
128 '''
129 Sets the values of an existing item to the given items.
130 '''
131 if index < len(self._data):
132 obj = self._data[index]
133 if not cfg in obj['cfgs']:
134 obj['cfgs'].append(cfg)
135 obj['name'] = name
136 obj['displayed_name'] = displayed_name
137 obj['type'] = robot_type
138 obj['description'] = resolve_paths(description)
139 del obj['images'][:]
140 for image_path in images:
141 img = resolve_paths(image_path)
142 if img and img[0] != os.path.sep:
143 img = os.path.join(nm.settings().PACKAGE_DIR, image_path)
144 if os.path.isfile(img):
145 obj['images'].append(QtGui.QPixmap(img))
146
148 '''
149 Sets the values of an existing item to the given items only if the current
150 value is empty.
151 '''
152 if index < len(self._data):
153 obj = self._data[index]
154 if not cfg in obj['cfgs']:
155 obj['cfgs'].append(cfg)
156 if not obj['name']:
157 obj['name'] = name
158 if not obj['displayed_name']:
159 obj['displayed_name'] = displayed_name
160 if not obj['type']:
161 obj['type'] = robot_type
162 if not obj['description']:
163 obj['description'] = resolve_paths(description)
164 if not obj['images']:
165 for image_path in images:
166 img = resolve_paths(image_path)
167 if img and img[0] != os.path.sep:
168 img = os.path.join(nm.settings().PACKAGE_DIR, image_path)
169 if os.path.isfile(img):
170 obj['images'].append(QtGui.QPixmap(img))
171
173 '''
174 Removes an existing value from the header.
175 @param index: the index of the item to remove.
176 @type index: C{int}
177 '''
178 if index < len(self._data):
179 self._data.pop(index)
180
182 '''
183 Inserts an item at the given index into the header.
184 @param index: the index
185 @type index: C{int}
186 '''
187 new_dict = {'cfgs' : [], 'name': '', 'displayed_name' : '', 'type' : '', 'description' : '', 'images' : []}
188 if index < len(self._data):
189 self._data.insert(index, new_dict)
190 else:
191 self._data.append(new_dict)
192
194 '''
195 Insert the new item with given name at the sorted position and return the index of
196 the item.
197 @param name: the name of the new item
198 @type name: C{str}
199 @return: index of the inserted item
200 @rtype: C{int}
201 '''
202 new_dict = {'cfgs' : [], 'name': name, 'displayed_name' : displayed_name, 'type' : '', 'description' : '', 'images' : []}
203 for index, item in enumerate(self._data):
204 if item['displayed_name'].lower() > displayed_name.lower():
205 self._data.insert(index, new_dict)
206 return index
207 self._data.append(new_dict)
208 return len(self._data)-1
209
211 '''
212 Removes the configuration entries from objects and returns the list with
213 indexes, where the configuration was removed.
214 @param cfg: configuration to remove
215 @type cfg: C{str}
216 @return: the list the indexes, where the configuration was removed
217 @rtype: C{[int]}
218 '''
219 result = []
220 for index, d in enumerate(self._data):
221 if cfg in d['cfgs']:
222 d['cfgs'].remove(cfg)
223 result.append(index)
224 return result
225
227 '''
228 @return: The count of items in the header.
229 @rtype: C{int}
230 '''
231 return len(self._data)
232
234 '''
235 @return: The configurations assigned to the item at the given index
236 @rtype: C{str}
237 '''
238 result = []
239 if index < len(self._data):
240 result = list(self._data[index]['cfgs'])
241 return result
242
243
244
245
246
247
376
377
378
379
380
381
382
384 '''
385 The table shows all detected capabilities of robots in tabular view. The
386 columns represents the robot and rows the capabilities. The cell of available
387 capability contains a L{CapabilityControlWidget} to show the state and manage
388 the capability.
389 '''
390
391 start_nodes_signal = QtCore.Signal(str, str, list)
392 '''@ivar: the signal is emitted to start on host(described by masteruri) the nodes described in the list, Parameter(masteruri, config, nodes).'''
393
394 stop_nodes_signal = QtCore.Signal(str, list)
395 '''@ivar: the signal is emitted to stop on masteruri the nodes described in the list.'''
396
397 description_requested_signal = QtCore.Signal(str, str)
398 '''@ivar: the signal is emitted by click on a header to show a description.'''
399
400
409
411 '''
412 Updates the capabilities view.
413 @param masteruri: the ROS master URI of updated ROS master.
414 @type masteruri: C{str}
415 @param cfg_name: The name of the node provided the capabilities description.
416 @type cfg_name: C{str}
417 @param description: The capabilities description object.
418 @type description: L{default_cfg_fkie.Description}
419 '''
420
421 robot_index = self._robotHeader.index(masteruri)
422 robot_name = description.robot_name if description.robot_name else nm.nameres().mastername(masteruri)
423 if robot_index == -1:
424
425 robot_index = self._robotHeader.insertSortedItem(masteruri, robot_name)
426 self.insertColumn(robot_index)
427
428
429 self._robotHeader.setDescription(robot_index, cfg_name, masteruri, robot_name, description.robot_type, description.robot_descr.replace("\\n ", "\n").decode(sys.getfilesystemencoding()), description.robot_images)
430 item = QtGui.QTableWidgetItem()
431 item.setSizeHint(QtCore.QSize(96,96))
432 self.setHorizontalHeaderItem(robot_index, item)
433 self.horizontalHeaderItem(robot_index).setText(robot_name)
434 else:
435
436 self._robotHeader.setDescription(robot_index, cfg_name, masteruri, robot_name, description.robot_type, description.robot_descr.replace("\\n ", "\n").decode(sys.getfilesystemencoding()), description.robot_images)
437
438
439 for c in description.capabilities:
440 cap_index = self._capabilityHeader.index(c.name.decode(sys.getfilesystemencoding()))
441 if cap_index == -1:
442
443 cap_index = self._capabilityHeader.insertSortedItem(c.name.decode(sys.getfilesystemencoding()), c.name.decode(sys.getfilesystemencoding()))
444 self.insertRow(cap_index)
445 self.setRowHeight(cap_index, 96)
446 self._capabilityHeader.setDescription(cap_index, cfg_name, c.name.decode(sys.getfilesystemencoding()), c.name.decode(sys.getfilesystemencoding()), c.type, c.description.replace("\\n ", "\n").decode(sys.getfilesystemencoding()), c.images)
447 item = QtGui.QTableWidgetItem()
448 item.setSizeHint(QtCore.QSize(96,96))
449 self.setVerticalHeaderItem(cap_index, item)
450 self.verticalHeaderItem(cap_index).setText(c.name.decode(sys.getfilesystemencoding()))
451
452 controlWidget = CapabilityControlWidget(masteruri, cfg_name, c.namespace, c.nodes)
453 controlWidget.start_nodes_signal.connect(self._start_nodes)
454 controlWidget.stop_nodes_signal.connect(self._stop_nodes)
455 self.setCellWidget(cap_index, robot_index, controlWidget)
456 self._capabilityHeader.controlWidget.insert(cap_index, controlWidget)
457 else:
458 self._capabilityHeader.updateDescription(cap_index, cfg_name, c.name.decode(sys.getfilesystemencoding()), c.name.decode(sys.getfilesystemencoding()), c.type, c.description.replace("\\n ", "\n").decode(sys.getfilesystemencoding()), c.images)
459 self._capabilityHeader.controlWidget[cap_index].updateNodes(cfg_name, c.namespace, c.nodes)
460
462 '''
463 @param cfg: The name of the node provided the capabilities description.
464 @type cfg: C{str}
465 '''
466 removed_from_robots = self._robotHeader.removeCfg(cfg)
467
468
469
470
471 removed_from_caps = self._capabilityHeader.removeCfg(cfg)
472
473 for r in reversed(removed_from_robots):
474 for c in removed_from_caps:
475 controlWidget = self.cellWidget(c, r)
476 if isinstance(controlWidget, CapabilityControlWidget):
477 controlWidget.removeCfg(cfg)
478 if not controlWidget.hasConfigs():
479 self.removeCellWidget(c, r)
480
481 for r in removed_from_robots:
482 is_empty = True
483 for c in reversed(range(self.rowCount())):
484 controlWidget = self.cellWidget(c, r)
485 if isinstance(controlWidget, CapabilityControlWidget):
486 is_empty = False
487 break
488 if is_empty:
489 self.removeColumn(r)
490 self._robotHeader.removeDescription(r)
491
492 for c in reversed(removed_from_caps):
493 is_empty = True
494 for r in reversed(range(self.columnCount())):
495 controlWidget = self.cellWidget(c, r)
496 if isinstance(controlWidget, CapabilityControlWidget):
497 is_empty = False
498 break
499 if is_empty:
500 self.removeRow(c)
501 self._capabilityHeader.removeDescription(c)
502
504 '''
505 Updates the run state of the capability.
506 @param masteruri: The ROS master, which sends the master_info
507 @type masteruri: C{str}
508 @param master_info: The state of the ROS master
509 @type master_info: L{master_discovery_fkie.MasterInfo}
510 '''
511 if master_info is None or masteruri is None:
512 return
513 robot_index = self._robotHeader.index(masteruri)
514 if robot_index != -1:
515 for c in range(self.rowCount()):
516 controlWidget = self.cellWidget(c, robot_index)
517 if not controlWidget is None:
518 running_nodes = []
519 stopped_nodes = []
520 error_nodes = []
521 for n in controlWidget.nodes():
522 node = master_info.getNode(n)
523 if not node is None:
524
525 if not node.uri is None and masteruri == node.masteruri:
526 if not node.pid is None:
527 running_nodes.append(n)
528 else:
529 error_nodes.append(n)
530 else:
531 stopped_nodes.append(n)
532 controlWidget.setNodeState(running_nodes, stopped_nodes, error_nodes)
533
536
539
542