Package node_manager_fkie :: Module node_tree_model
[frames] | no frames]

Source Code for Module node_manager_fkie.node_tree_model

   1  # Software License Agreement (BSD License) 
   2  # 
   3  # Copyright (c) 2012, Fraunhofer FKIE/US, Alexander Tiderko 
   4  # All rights reserved. 
   5  # 
   6  # Redistribution and use in source and binary forms, with or without 
   7  # modification, are permitted provided that the following conditions 
   8  # are met: 
   9  # 
  10  #  * Redistributions of source code must retain the above copyright 
  11  #    notice, this list of conditions and the following disclaimer. 
  12  #  * Redistributions in binary form must reproduce the above 
  13  #    copyright notice, this list of conditions and the following 
  14  #    disclaimer in the documentation and/or other materials provided 
  15  #    with the distribution. 
  16  #  * Neither the name of Fraunhofer nor the names of its 
  17  #    contributors may be used to endorse or promote products derived 
  18  #    from this software without specific prior written permission. 
  19  # 
  20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
  21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
  22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
  23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
  24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
  25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
  26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
  27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
  28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
  29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
  30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
  31  # POSSIBILITY OF SUCH DAMAGE. 
  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 ############## GrouptItem ############## 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
54 - def __init__(self, name, parent=None):
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
75 - def name(self):
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
92 - def addCapabilities(self, config, capabilities, masteruri):
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 # update the view 105 for ns, groups in capabilities.items(): 106 for group, descr in groups.items(): 107 # create nodes for each group 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 # move the nodes from host to the group 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 # row[0].parent_item = groupItem 125 groupItem.updateDisplayedConfig() 126 127 # create new or update existing items in the group 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 # copy the state of the existing node 137 groupItem.addNode(items[0].node_info, config) 138 else: 139 groupItem.addNode(NodeInfo(node_name, masteruri), config) 140 groupItem.updateIcon()
141 # groupItem.updateTooltip() 142
143 - def remCapablities(self, config):
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 #todo update view? 158 pass
159
160 - def getCapabilityGroups(self, node_name, cfg=''):
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() # dict(config : [group names]) 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 # import traceback 182 # print traceback.format_exc() 183 return result
184
185 - def getNodeItemsByName(self, node_name, recursive=True):
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
206 - def getNodeItems(self):
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
221 - def getGroupItems(self):
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
236 - def getGroupItem(self, group_name):
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
267 - def addNode(self, node, cfg=''):
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 # insert in the group 280 groupItem = self.getGroupItem(group_name) 281 groupItem.addNode(node) 282 else: 283 # insert in order 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 # new_item_row[0].parent_item = self 288 if cfg: 289 new_item_row[0].addConfig(cfg)
290
291 - def _addRow_sorted(self, row):
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 # first clear sub groups 309 groups = self.getGroupItems() 310 for group in groups: 311 group.clearUp(fixed_node_names) 312 313 # move running nodes without configuration to the upper layer, remove not running and duplicate nodes 314 for i in reversed(range(self.rowCount())): 315 item = self.child(i) 316 if isinstance(item, NodeItem): 317 # set the running state of the node to None 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 # if it is in a group, is running, but has no configuration, move it to the host 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 # row[0].parent_item = self.parent_item 331 else: 332 #remove item 333 self.removeRow(i) 334 335 # remove empty groups 336 for i in reversed(range(self.rowCount())): 337 item = self.child(i) 338 if isinstance(item, GroupItem): 339 # remove empty groups 340 if item.rowCount() == 0: 341 self.removeRow(i)
342
343 - def updateRunningNodeState(self, nodes):
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 # get the node items 351 items = self.getNodeItemsByName(name) 352 if items: 353 for item in items: 354 # update the node item 355 item.node_info = node 356 else: 357 # create the new node 358 self.addNode(node) 359 self.clearUp(nodes.keys())
360
361 - def getRunningNodes(self):
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
378 - def markNodesAsDuplicateOf(self, running_nodes):
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
393 - def updateIcon(self):
394 has_running = False 395 has_off = False 396 has_duplicate = False 397 for i in range(self.rowCount()): 398 item = self.child(i) 399 if isinstance(item, NodeItem): 400 if item.state == NodeItem.STATE_WARNING: 401 self.setIcon(QtGui.QIcon(':/icons/crystal_clear_warning.png')) 402 return 403 elif item.state == NodeItem.STATE_OFF: 404 has_off = True 405 elif item.state == NodeItem.STATE_RUN: 406 has_running = True 407 elif item.state == NodeItem.STATE_DUPLICATE: 408 has_duplicate = True 409 if has_duplicate: 410 self.setIcon(QtGui.QIcon(':/icons/imacadam_stop.png')) 411 elif has_running and has_off: 412 self.setIcon(QtGui.QIcon(':/icons/state_part.png')) 413 elif not has_running: 414 self.setIcon(QtGui.QIcon(':/icons/state_off.png')) 415 elif not has_off and has_running: 416 self.setIcon(QtGui.QIcon(':/icons/state_run.png'))
417
418 - def _create_html_list(self, title, items):
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
430 - def updateTooltip(self):
431 ''' 432 Creates a tooltip description based on text set by L{updateDescription()} 433 and all childs of this host with valid sensor description. The result is 434 returned as a HTML part. 435 @return: the tooltip description coded as a HTML part 436 @rtype: C{str} 437 ''' 438 tooltip = self.generateDescription(False) 439 self.setToolTip(tooltip if tooltip else self.name) 440 return tooltip
441
442 - def generateDescription(self, extended=True):
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 # get nodes 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
466 - def updateDescription(self, descr_type, descr_name, descr):
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
480 - def updateDisplayedConfig(self):
481 ''' 482 Updates the configuration representation in other column. 483 ''' 484 if not self.parent_item is None: 485 # get nodes 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 # set tooltip 495 # removed for clarity !!! 496 # tooltip = '' 497 # if len(cfgs) > 0: 498 # tooltip = '' 499 # if len(cfgs) > 0: 500 # tooltip = ''.join([tooltip, '<h4>', 'Configurations:', '</h4><dl>']) 501 # for c in cfgs: 502 # if NodeItem.is_default_cfg(c): 503 # tooltip = ''.join([tooltip, '<dt>[default]', c[0], '</dt>']) 504 # else: 505 # tooltip = ''.join([tooltip, '<dt>', c, '</dt>']) 506 # tooltip = ''.join([tooltip, '</dl>']) 507 # cfg_col.setToolTip(''.join(['<div>', tooltip, '</div>'])) 508 # set icons 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
521 - def toHTML(cls, group_name):
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
545 - def type(self):
546 return GroupItem.ITEM_TYPE
547
548 - def __eq__(self, item):
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
558 - def __gt__(self, item):
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 ############## HostItem ############## 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
603 - def hostNameFrom(cls, masteruri, address):
604 ''' 605 Returns the name generated from masteruri and address 606 @param masteruri: URI of the ROS master assigned to the host 607 @type masteruri: C{str} 608 @param address: the address of the host 609 @type address: C{str} 610 ''' 611 #'print "hostNameFrom - mastername" 612 name = nm.nameres().mastername(masteruri, address) 613 if not name: 614 name = address 615 #'print "hostNameFrom - hostname" 616 hostname = nm.nameres().hostname(address) 617 if hostname is None: 618 hostname = str(address) 619 result = '@'.join([name, hostname]) 620 if nm.nameres().getHostname(masteruri) != hostname: 621 result = ''.join([result, '[', masteruri,']']) 622 #'print "- hostNameFrom" 623 return result
624 625
626 - def updateTooltip(self):
627 ''' 628 Creates a tooltip description based on text set by L{updateDescription()} 629 and all childs of this host with valid sensor description. The result is 630 returned as a HTML part. 631 @return: the tooltip description coded as a HTML part 632 @rtype: C{str} 633 ''' 634 tooltip = self.generateDescription(False) 635 self.setToolTip(tooltip if tooltip else self.name) 636 return tooltip
637
638 - def generateDescription(self, extended=True):
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 # get sensors 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
672 - def type(self):
673 return HostItem.ITEM_TYPE
674
675 - def __eq__(self, item):
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
687 - def __gt__(self, item):
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 ############## NodeItem ############## 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 # COL_URI = 2 714 715 STATE_OFF = 0 716 STATE_RUN = 1 717 STATE_WARNING = 2 718 STATE_DUPLICATE = 3 719
720 - def __init__(self, node_info):
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 # self.ICONS = {'empty' : QtGui.QIcon(), 730 # 'run' : QtGui.QIcon(':/icons/state_run.png'), 731 # 'off' :QtGui.QIcon(':/icons/state_off.png'), 732 # 'warning' : QtGui.QIcon(':/icons/crystal_clear_warning.png'), 733 # 'stop' : QtGui.QIcon('icons/imacadam_stop.png'), 734 # 'cfg+def' : QtGui.QIcon(':/icons/crystal_clear_launch_file_def_cfg.png'), 735 # 'cfg' : QtGui.QIcon(':/icons/crystal_clear_launch_file.png'), 736 # 'default_cfg' : QtGui.QIcon(':/icons/default_cfg.png') 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
744 - def state(self):
745 return self._state
746
747 - def is_valid(self):
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
756 - def name(self):
757 return self._node_info.name
758 759 @property
760 - def masteruri(self):
761 return self._node_info.masteruri
762 763 @property
764 - def published(self):
765 return self._node_info.publishedTopics
766 767 @property
768 - def subscribed(self):
769 return self._node_info.subscribedTopics
770 771 @property
772 - def services(self):
773 return self._node_info.services
774 775 @property
776 - def node_info(self):
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
784 - def node_info(self, node_info):
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 # update the tooltip and icon 806 if run_changed and self.is_valid(): 807 self.updateDispayedName() 808 # self.updateDisplayedURI() 809 if not self.parent_item is None and not isinstance(self.parent_item, HostItem): 810 self.parent_item.updateIcon()
811 812 @property
813 - def uri(self):
814 return self._node_info.uri
815 816 @property
817 - def pid(self):
818 return self._node_info.pid
819 820 @property
821 - def has_running(self):
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
830 - def has_running(self, state):
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
843 - def updateDispayedName(self):
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 #'print "updateDispayedName - hasMaster" 852 master_discovered = nm.nameres().hasMaster(self.node_info.masteruri) 853 local = False 854 # if not self.node_info.uri is None and not self.node_info.masteruri is None: 855 # local = (nm.nameres().getHostname(self.node_info.uri) == nm.nameres().getHostname(self.node_info.masteruri)) 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 # elif not self.node_info.isLocal and not master_discovered and not self.node_info.uri is None: 867 ## elif not local and not master_discovered and not self.node_info.uri is None: 868 # self._state = NodeItem.STATE_RUN 869 # self.setIcon(QtGui.QIcon(':/icons/state_run.png')) 870 # tooltip = ''.join([tooltip, '<dl><dt>(Remote nodes will not be ping, so they are always marked running)</dt></dl>']) 871 # tooltip = ''.join([tooltip, '</dl>']) 872 # self.setToolTip(''.join(['<div>', tooltip, '</div>'])) 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 #'print "- updateDispayedName" 891 892 # removed common tooltip for clarity !!! 893 # self.setToolTip(''.join(['<div>', tooltip, '</div>'])) 894
895 - def updateDisplayedURI(self):
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
905 - def cfgs(self):
906 ''' 907 Returns the list with all launch configurations assigned to this item. 908 @rtype: C{[str]} 909 ''' 910 return self._cfgs
911
912 - def addConfig(self, cfg):
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
922 - def remConfig(self, cfg):
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
933 - def updateDisplayedConfig(self):
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 # set tooltip 942 # removed tooltip for clarity !!! 943 # tooltip = '' 944 # if len(self._cfgs) > 0: 945 # tooltip = '' 946 # if len(self._cfgs) > 0: 947 # tooltip = ''.join([tooltip, '<h4>', 'Configurations:', '</h4><dl>']) 948 # for c in self._cfgs: 949 # if NodeItem.is_default_cfg(c): 950 # tooltip = ''.join([tooltip, '<dt>[default]', c[0], '</dt>']) 951 # else: 952 # tooltip = ''.join([tooltip, '<dt>', c, '</dt>']) 953 # tooltip = ''.join([tooltip, '</dl>']) 954 # cfg_col.setToolTip(''.join(['<div>', tooltip, '</div>'])) 955 # set icons 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
969 - def type(self):
970 return NodeItem.ITEM_TYPE
971 972 @classmethod
973 - def newNodeRow(self, name, masteruri):
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 # uriitem = QtGui.QStandardItem() 990 # items.append(uriitem) 991 return items
992 993 @classmethod
994 - def has_launch_cfgs(cls, cfgs):
995 for c in cfgs: 996 if not cls.is_default_cfg(c): 997 return True 998 return False
999 1000 @classmethod
1001 - def has_default_cfgs(cls, cfgs):
1002 for c in cfgs: 1003 if cls.is_default_cfg(c): 1004 return True 1005 return False
1006 1007 @classmethod
1008 - def is_default_cfg(cls, cfg):
1009 return isinstance(cfg, tuple)
1010 1011 @classmethod
1012 - def toHTML(cls, node_name):
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
1028 - def __eq__(self, item):
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
1038 - def __gt__(self, item):
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 ############## NodeTreeModel ############## 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 # ICONS = {'default' : QtGui.QIcon(), 1059 # 'run' : QtGui.QIcon(":/icons/state_run.png"), 1060 # 'warning' : QtGui.QIcon(":/icons/crystal_clear_warning.png"), 1061 # 'def_launch_cfg' : QtGui.QIcon(":/icons/crystal_clear_launch_file_def_cfg.png"), 1062 # 'launch_cfg' : QtGui.QIcon(":/icons/crystal_clear_launch_file.png"), 1063 # 'def_cfg' : QtGui.QIcon(":/icons/default_cfg.png") } 1064 1065 header = [('Name', 450), 1066 ('Cfgs', -1)] 1067 # ('URI', -1)] 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):
1074 ''' 1075 Initialize the model. 1076 ''' 1077 super(NodeTreeModel, self).__init__(parent) 1078 self.setColumnCount(len(NodeTreeModel.header)) 1079 self.setHorizontalHeaderLabels([label for label, width in NodeTreeModel.header]) 1080 self._local_host_address = host_address
1081 1082 @property
1083 - def local_addr(self):
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
1091 - def getHostItem(self, masteruri, address):
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 # find the host item by address 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
1122 - def updateModelData(self, nodes):
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 # separate into different hosts 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 # update nodes for each host 1136 for ((masteruri, host), nodes_filtered) in hosts.items(): 1137 hostItem = self.getHostItem(masteruri, host) 1138 # rename the host item if needed 1139 if not hostItem is None: 1140 # host_name = nm.nameres().getName(host=host) 1141 # if host_name and not (hostItem.name == host_name): 1142 # hostItem.name = host_name 1143 hostItem.updateRunningNodeState(nodes_filtered) 1144 # update nodes of the hosts, which are not more exists 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 # update the duplicate state 1151 # self.markNodesAsDuplicateOf(self.getRunningNodes()) 1152
1153 - def addCapabilities(self, masteruri, host_address, cfg, capabilities):
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
1170 - def appendConfigNodes(self, masteruri, host_address, nodes):
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 # create the new node 1189 node_info = NodeInfo(name, masteruri) 1190 hostItem.addNode(node_info, cfg) 1191 self.removeEmptyHosts()
1192 # update the duplicate state 1193 # self.markNodesAsDuplicateOf(self.getRunningNodes()) 1194
1195 - def removeConfigNodes(self, cfg):
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
1213 - def removeEmptyHosts(self):
1214 # remove empty hosts 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
1220 - def isDuplicateNode(self, node_name):
1221 for i in reversed(range(self.invisibleRootItem().rowCount())): 1222 host = self.invisibleRootItem().child(i) 1223 if not host is None: # should not occur 1224 nodes = host.getNodeItemsByName(node_name) 1225 for n in nodes: 1226 if n.has_running: 1227 return True 1228 return False
1229
1230 - def getRunningNodes(self):
1231 ''' 1232 Returns a list with all known running nodes. 1233 @rtype: C{[str]} 1234 ''' 1235 running_nodes = list() 1236 ## determine all running nodes 1237 for i in reversed(range(self.invisibleRootItem().rowCount())): 1238 host = self.invisibleRootItem().child(i) 1239 if not host is None: # should not occur 1240 running_nodes[len(running_nodes):] = host.getRunningNodes() 1241 return running_nodes
1242
1243 - def markNodesAsDuplicateOf(self, running_nodes):
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: # should not occur 1256 host.markNodesAsDuplicateOf(running_nodes)
1257
1258 - def updateHostDescription(self, masteruri, host, descr_type, descr_name, descr):
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