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 roslib
40 import rospy
41 import node_manager_fkie as nm
42 from master_discovery_fkie.master_info import NodeInfo
43
44
45
46
47
48
50 '''
51 This class is used for visualization of robots or capabilities in header of
52 the capability table. It is also used to manage the displayed robots or
53 capabilities. Furthermore L{QtGui.QHeaderView.paintSection()} method is
54 overridden to paint the images in background of the cell.
55 '''
56
57 description_requested_signal = QtCore.Signal(str, str)
58 '''the signal is emitted by click on a header to show a description.'''
59
61 QtGui.QHeaderView.__init__(self, orientation, parent)
62 self._data = []
63 ''' @ivar a list with dictionaries C{dict('cfgs' : [], 'name': str, 'displayed_name' : str, 'type' : str, 'description' : str, 'images' : [QtGui.QPixmap])}'''
64 if orientation == QtCore.Qt.Horizontal:
65 self.setDefaultAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignBottom)
66 elif orientation == QtCore.Qt.Vertical:
67 self.setDefaultAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignBottom)
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
106
108 '''
109 Interpret the mouse events to send the description of a robot or capability
110 if the user click on the header.
111 '''
112 QtGui.QHeaderView.mousePressEvent(self, event)
113 index = self.logicalIndexAt(event.pos())
114 if index in range(len(self._data)):
115 suffix = 'Capability'
116 if self.orientation() == QtCore.Qt.Horizontal:
117 suffix = 'Robot'
118 title = ' - '.join([self._data[index]['name'], suffix])
119 text = self._data[index]['description']
120 try:
121 from docutils import examples
122 text = examples.html_body(text)
123 except:
124 import traceback
125 rospy.logwarn("Error while generate description for %s: %s", self._data[index]['name'], traceback.format_exc())
126 self.description_requested_signal.emit(title, text)
127
129 '''
130 Sets the values of an existing item to the given items.
131 '''
132 if index < len(self._data):
133 obj = self._data[index]
134 if not cfg in obj['cfgs']:
135 obj['cfgs'].append(cfg)
136 obj['name'] = name
137 obj['displayed_name'] = displayed_name
138 obj['type'] = type
139 obj['description'] = description
140 del obj['images'][:]
141 for image_path in images:
142 img = os.path.join(nm.settings().PACKAGE_DIR, image_path)
143 if os.path.isfile(img):
144 obj['images'].append(QtGui.QPixmap(img))
145
147 '''
148 Sets the values of an existing item to the given items only if the current
149 value is empty.
150 '''
151 if index < len(self._data):
152 obj = self._data[index]
153 if not cfg in obj['cfgs']:
154 obj['cfgs'].append(cfg)
155 if not obj['name']:
156 obj['name'] = name
157 if not obj['displayed_name']:
158 obj['displayed_name'] = displayed_name
159 if not obj['type']:
160 obj['type'] = type
161 if not obj['description']:
162 obj['description'] = description
163 if not obj['images']:
164 for image_path in images:
165 img = os.path.join(nm.settings().PACKAGE_DIR, image_path)
166 if os.path.isfile(img):
167 obj['images'].append(QtGui.QPixmap(img))
168
170 '''
171 Removes an existing value from the header.
172 @param index: the index of the item to remove.
173 @type index: C{int}
174 '''
175 if index < len(self._data):
176 self._data.pop(index)
177
179 '''
180 Inserts an item at the given index into the header.
181 @param index: the index
182 @type index: C{int}
183 '''
184 new_dict = {'cfgs' : [], 'name': '', 'displayed_name' : '', 'type' : '', 'description' : '', 'images' : []}
185 if index < len(self._data):
186 self._data.insert(index, new_dict)
187 else:
188 self._data.append(new_dict)
189
191 '''
192 Insert the new item with given name at the sorted position and return the index of
193 the item.
194 @param name: the name of the new item
195 @type name: C{str}
196 @return: index of the inserted item
197 @rtype: C{int}
198 '''
199 new_dict = {'cfgs' : [], 'name': name, 'displayed_name' : displayed_name, 'type' : '', 'description' : '', 'images' : []}
200 for index, item in enumerate(self._data):
201 if item['displayed_name'].lower() > displayed_name.lower():
202 self._data.insert(index, new_dict)
203 return index
204 self._data.append(new_dict)
205 return len(self._data)-1
206
208 '''
209 Removes the configuration entries from objects and returns the list with
210 indexes, where the configuration was removed.
211 @param cfg: configuration to remove
212 @type cfg: C{str}
213 @return: the list the indexes, where the configuration was removed
214 @rtype: C{[int]}
215 '''
216 result = []
217 for index, d in enumerate(self._data):
218 if cfg in d['cfgs']:
219 d['cfgs'].remove(cfg)
220 result.append(index)
221 return result
222
224 '''
225 @return: The count of items in the header.
226 @rtype: C{int}
227 '''
228 return len(self._data)
229
231 '''
232 @return: The configurations assigned to the item at the given index
233 @rtype: C{str}
234 '''
235 result = []
236 if index < len(self._data):
237 result = list(self._data[index]['cfgs'])
238 return result
239
240
241
242
243
244
334
335
336
337
338
339
340
342 '''
343 The table shows all detected capabilities of robots in tabular view. The
344 columns represents the robot and rows the capabilities. The cell of available
345 capability contains a L{CapabilityControlWidget} to show the state and manage
346 the capability.
347 '''
348
349 start_nodes_signal = QtCore.Signal(str, str, list)
350 '''@ivar: the signal is emitted to start on host(described by masteruri) the nodes described in the list, Parameter(masteruri, config, nodes).'''
351
352 stop_nodes_signal = QtCore.Signal(str, list)
353 '''@ivar: the signal is emitted to stop on masteruri the nodes described in the list.'''
354
355 description_requested_signal = QtCore.Signal(str, str)
356 '''@ivar: the signal is emitted by click on a header to show a description.'''
357
358
367
369 '''
370 Updates the capabilities view.
371 @param masteruri: the ROS master URI of updated ROS master.
372 @type masteruri: C{str}
373 @param cfg_name: The name of the node provided the capabilities description.
374 @type cfg_name: C{str}
375 @param description: The capabilities description object.
376 @type description: L{default_cfg_fkie.Description}
377 '''
378
379 robot_index = self._robotHeader.index(masteruri)
380 robot_name = description.robot_name if description.robot_name else nm.nameres().mastername(masteruri)
381 if robot_index == -1:
382
383 robot_index = self._robotHeader.insertSortedItem(masteruri, robot_name)
384 self.insertColumn(robot_index)
385
386
387 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)
388 item = QtGui.QTableWidgetItem()
389 item.setSizeHint(QtCore.QSize(96,96))
390 self.setHorizontalHeaderItem(robot_index, item)
391 self.horizontalHeaderItem(robot_index).setText(robot_name)
392 else:
393
394 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)
395
396
397 for c in description.capabilities:
398 cap_index = self._capabilityHeader.index(c.name.decode(sys.getfilesystemencoding()))
399 if cap_index == -1:
400
401 cap_index = self._capabilityHeader.insertSortedItem(c.name.decode(sys.getfilesystemencoding()), c.name.decode(sys.getfilesystemencoding()))
402 self.insertRow(cap_index)
403 self.setRowHeight(cap_index, 96)
404 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)
405 item = QtGui.QTableWidgetItem()
406 item.setSizeHint(QtCore.QSize(96,96))
407 self.setVerticalHeaderItem(cap_index, item)
408 self.verticalHeaderItem(cap_index).setText(c.name.decode(sys.getfilesystemencoding()))
409 else:
410 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)
411
412
413 controlWidget = CapabilityControlWidget(masteruri, cfg_name, c.nodes)
414 controlWidget.start_nodes_signal.connect(self._start_nodes)
415 controlWidget.stop_nodes_signal.connect(self._stop_nodes)
416 self.setCellWidget(cap_index, robot_index, controlWidget)
417
419 '''
420 @param cfg: The name of the node provided the capabilities description.
421 @type cfg: C{str}
422 '''
423 removed_from_robots = self._robotHeader.removeCfg(cfg)
424
425
426
427
428 removed_from_caps = self._capabilityHeader.removeCfg(cfg)
429
430 for r in reversed(removed_from_robots):
431 for c in removed_from_caps:
432 controlWidget = self.cellWidget(c, r)
433 if isinstance(controlWidget, CapabilityControlWidget) and controlWidget.config() == cfg:
434 self.removeCellWidget(c, r)
435
436 for r in removed_from_robots:
437 is_empty = True
438 for c in reversed(range(self.rowCount())):
439 controlWidget = self.cellWidget(c, r)
440 if isinstance(controlWidget, CapabilityControlWidget):
441 is_empty = False
442 break
443 if is_empty:
444 self.removeColumn(r)
445 self._robotHeader.removeDescription(r)
446
447 for c in reversed(removed_from_caps):
448 is_empty = True
449 for r in reversed(range(self.columnCount())):
450 controlWidget = self.cellWidget(c, r)
451 if isinstance(controlWidget, CapabilityControlWidget):
452 is_empty = False
453 break
454 if is_empty:
455 self.removeRow(c)
456 self._capabilityHeader.removeDescription(c)
457
459 '''
460 Updates the run state of the capability.
461 @param masteruri: The ROS master, which sends the master_info
462 @type masteruri: C{str}
463 @param master_info: The state of the ROS master
464 @type master_info: L{master_discovery_fkie.MasterInfo}
465 '''
466 if master_info is None or masteruri is None:
467 return
468 robot_index = self._robotHeader.index(masteruri)
469 if robot_index != -1:
470 for c in range(self.rowCount()):
471 controlWidget = self.cellWidget(c, robot_index)
472 if not controlWidget is None:
473 running_nodes = []
474 stopped_nodes = []
475 error_nodes = []
476 for n in controlWidget.nodes():
477 node = master_info.getNode(n)
478 if not node is None:
479
480 if not node.uri is None and masteruri == node.masteruri:
481 if not node.pid is None:
482 running_nodes.append(n)
483 else:
484 error_nodes.append(n)
485 else:
486 stopped_nodes.append(n)
487 controlWidget.setNodeState(running_nodes, stopped_nodes, error_nodes)
488
491
494
497