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

Source Code for Module node_manager_fkie.capability_table

  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 python_qt_binding.QtCore import Signal, Qt, QRect, QSize 
 34  from python_qt_binding.QtGui import QBrush, QColor, QIcon, QPalette, QPixmap 
 35  import os 
 36  import rospy 
 37  import sys 
 38   
 39  import node_manager_fkie as nm 
 40   
 41  from .common import resolve_paths 
 42  try: 
 43      from python_qt_binding.QtGui import QFrame, QLabel, QPushButton, QTableWidget, QTableWidgetItem 
 44      from python_qt_binding.QtGui import QHeaderView, QHBoxLayout, QVBoxLayout, QSpacerItem, QSizePolicy 
 45  except: 
 46      from python_qt_binding.QtWidgets import QFrame, QLabel, QPushButton, QTableWidget, QTableWidgetItem 
 47      from python_qt_binding.QtWidgets import QHeaderView, QHBoxLayout, QVBoxLayout, QSpacerItem, QSizePolicy 
 48   
 49   
 50  ################################################################################ 
 51  ##############                  CapabilityHeader                  ############## 
 52  ################################################################################ 
53 -class CapabilityHeader(QHeaderView):
54 ''' 55 This class is used for visualization of robots or capabilities in header of 56 the capability table. It is also used to manage the displayed robots or 57 capabilities. Furthermore U{QtGui.QHeaderView.paintSection()<https://srinikom.github.io/pyside-docs/PySide/QtGui/QHeaderView.html#PySide.QtGui.PySide.QtGui.QHeaderView.paintSection>} method is 58 overridden to paint the images in background of the cell. 59 ''' 60 61 description_requested_signal = Signal(str, str) 62 '''the signal is emitted by click on a header to show a description.''' 63
64 - def __init__(self, orientation, parent=None):
65 QHeaderView.__init__(self, orientation, parent) 66 self._data = [] 67 '''@ivar: a list with dictionaries C{dict('cfgs': [], 'name': str, 'displayed_name': str, 'type': str, 'description': str, 'images': [QtGui.QPixmap])}''' 68 if orientation == Qt.Horizontal: 69 self.setDefaultAlignment(Qt.AlignHCenter | Qt.AlignBottom) 70 elif orientation == Qt.Vertical: 71 self.setDefaultAlignment(Qt.AlignLeft | Qt.AlignBottom) 72 self.controlWidget = []
73
74 - def index(self, name):
75 ''' 76 Returns the index of the object stored with given name 77 @param name: the name of the item 78 @type name: C{str} 79 @return: the index or -1 if the item was not found 80 @rtype: C{int} 81 ''' 82 for index, entry in enumerate(self._data): 83 if entry['name'] == name: 84 return index 85 return -1
86
87 - def paintSection(self, painter, rect, logicalIndex):
88 ''' 89 The method paint the robot or capability images in the backgroud of the cell. 90 @see: U{QtGui.QHeaderView.paintSection()<https://srinikom.github.io/pyside-docs/PySide/QtGui/QHeaderView.html#PySide.QtGui.PySide.QtGui.QHeaderView.paintSection>} 91 ''' 92 painter.save() 93 QHeaderView.paintSection(self, painter, rect, logicalIndex) 94 painter.restore() 95 96 if logicalIndex in range(len(self._data)) and self._data[logicalIndex]['images']: 97 if len(self._data[logicalIndex]['images']) == 1: 98 pix = self._data[logicalIndex]['images'][0] 99 pix = pix.scaled(rect.width(), rect.height() - 20, Qt.KeepAspectRatio, Qt.SmoothTransformation) 100 self.style().drawItemPixmap(painter, rect, 5, pix) 101 elif len(self._data[logicalIndex]['images']) > 1: 102 new_rect = QRect(rect.left(), rect.top(), rect.width(), (rect.height() - 20) / 2.) 103 pix = self._data[logicalIndex]['images'][0] 104 pix = pix.scaled(new_rect.width(), new_rect.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation) 105 self.style().drawItemPixmap(painter, new_rect, 5, pix) 106 new_rect = QRect(rect.left(), rect.top() + new_rect.height(), rect.width(), new_rect.height()) 107 pix = self._data[logicalIndex]['images'][1] 108 pix = pix.scaled(new_rect.width(), new_rect.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation) 109 self.style().drawItemPixmap(painter, new_rect, 5, pix)
110
111 - def mousePressEvent(self, event):
112 ''' 113 Interpret the mouse events to send the description of a robot or capability 114 if the user click on the header. 115 ''' 116 QHeaderView.mousePressEvent(self, event) 117 index = self.logicalIndexAt(event.pos()) 118 if index in range(len(self._data)): 119 suffix = 'Capability' 120 if self.orientation() == Qt.Horizontal: 121 suffix = 'Robot' 122 title = ' - '.join([self._data[index]['name'], suffix]) 123 text = self._data[index]['description'] 124 try: 125 from docutils import examples 126 text = examples.html_body(text) 127 except: 128 import traceback 129 rospy.logwarn("Error while generate description for %s: %s", self._data[index]['name'], traceback.format_exc(1)) 130 self.description_requested_signal.emit(title, text)
131
132 - def setDescription(self, index, cfg, name, displayed_name, robot_type, description, images):
133 ''' 134 Sets the values of an existing item to the given items. 135 ''' 136 if index < len(self._data): 137 obj = self._data[index] 138 if cfg not in obj['cfgs']: 139 obj['cfgs'].append(cfg) 140 obj['name'] = name 141 obj['displayed_name'] = displayed_name 142 obj['type'] = robot_type 143 obj['description'] = resolve_paths(description) 144 del obj['images'][:] 145 for image_path in images: 146 img = resolve_paths(image_path) 147 if img and img[0] != os.path.sep: 148 img = os.path.join(nm.settings().PACKAGE_DIR, image_path) 149 if os.path.isfile(img): 150 obj['images'].append(QPixmap(img))
151
152 - def updateDescription(self, index, cfg, name, displayed_name, robot_type, description, images):
153 ''' 154 Sets the values of an existing item to the given items only if the current 155 value is empty. 156 ''' 157 if index < len(self._data): 158 obj = self._data[index] 159 if cfg not in obj['cfgs']: 160 obj['cfgs'].append(cfg) 161 if not obj['name']: 162 obj['name'] = name 163 if not obj['displayed_name']: 164 obj['displayed_name'] = displayed_name 165 if not obj['type']: 166 obj['type'] = robot_type 167 if not obj['description']: 168 obj['description'] = resolve_paths(description) 169 if not obj['images']: 170 for image_path in images: 171 img = resolve_paths(image_path) 172 if img and img[0] != os.path.sep: 173 img = os.path.join(nm.settings().PACKAGE_DIR, image_path) 174 if os.path.isfile(img): 175 obj['images'].append(QPixmap(img))
176
177 - def removeDescription(self, index):
178 ''' 179 Removes an existing value from the header. 180 @param index: the index of the item to remove. 181 @type index: C{int} 182 ''' 183 if index < len(self._data): 184 self._data.pop(index)
185
186 - def insertItem(self, index):
187 ''' 188 Inserts an item at the given index into the header. 189 @param index: the index 190 @type index: C{int} 191 ''' 192 new_dict = {'cfgs': [], 'name': '', 'displayed_name': '', 'type': '', 'description': '', 'images': []} 193 if index < len(self._data): 194 self._data.insert(index, new_dict) 195 else: 196 self._data.append(new_dict)
197
198 - def insertSortedItem(self, name, displayed_name):
199 ''' 200 Insert the new item with given name at the sorted position and return the index of 201 the item. 202 @param name: the name of the new item 203 @type name: C{str} 204 @return: index of the inserted item 205 @rtype: C{int} 206 ''' 207 new_dict = {'cfgs': [], 'name': name, 'displayed_name': displayed_name, 'type': '', 'description': '', 'images': []} 208 for index, item in enumerate(self._data): 209 if item['displayed_name'].lower() > displayed_name.lower(): 210 self._data.insert(index, new_dict) 211 return index 212 self._data.append(new_dict) 213 return len(self._data) - 1
214
215 - def removeCfg(self, cfg):
216 ''' 217 Removes the configuration entries from objects and returns the list with 218 indexes, where the configuration was removed. 219 @param cfg: configuration to remove 220 @type cfg: C{str} 221 @return: the list the indexes, where the configuration was removed 222 @rtype: C{[int]} 223 ''' 224 result = [] 225 for index, d in enumerate(self._data): 226 if cfg in d['cfgs']: 227 d['cfgs'].remove(cfg) 228 result.append(index) 229 return result
230
231 - def count(self):
232 ''' 233 @return: The count of items in the header. 234 @rtype: C{int} 235 ''' 236 return len(self._data)
237
238 - def getConfigs(self, index):
239 ''' 240 @return: The configurations assigned to the item at the given index 241 @rtype: C{str} 242 ''' 243 result = [] 244 if index < len(self._data): 245 result = list(self._data[index]['cfgs']) 246 return result
247 248 249 ################################################################################ 250 ############## CapabilityControlWidget ############## 251 ################################################################################ 252
253 -class CapabilityControlWidget(QFrame):
254 ''' 255 The control widget contains buttons for control a capability. Currently this 256 are C{On} and C{Off} buttons. Additionally, the state of the capability is 257 color coded. 258 ''' 259 260 start_nodes_signal = Signal(str, str, list) 261 '''@ivar: the signal is emitted to start on host(described by masteruri) the nodes described in the list, Parameter(masteruri, config, nodes).''' 262 263 stop_nodes_signal = Signal(str, list) 264 '''@ivar: the signal is emitted to stop on masteruri the nodes described in the list.''' 265
266 - def __init__(self, masteruri, cfg, ns, nodes, parent=None):
267 QFrame.__init__(self, parent) 268 self._masteruri = masteruri 269 self._nodes = {cfg: {ns: nodes}} 270 frame_layout = QVBoxLayout(self) 271 frame_layout.setContentsMargins(0, 0, 0, 0) 272 # create frame for warning label 273 self.warning_frame = warning_frame = QFrame(self) 274 warning_layout = QHBoxLayout(warning_frame) 275 warning_layout.setContentsMargins(0, 0, 0, 0) 276 warning_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) 277 self.warning_label = QLabel() 278 icon = QIcon(':/icons/crystal_clear_warning.png') 279 self.warning_label.setPixmap(icon.pixmap(QSize(40, 40))) 280 self.warning_label.setToolTip('Multiple configuration for same node found!\nA first one will be selected for the start a node!') 281 warning_layout.addWidget(self.warning_label) 282 warning_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) 283 frame_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) 284 frame_layout.addWidget(warning_frame) 285 # create frame for start/stop buttons 286 buttons_frame = QFrame() 287 buttons_layout = QHBoxLayout(buttons_frame) 288 buttons_layout.setContentsMargins(0, 0, 0, 0) 289 buttons_layout.addItem(QSpacerItem(20, 20)) 290 self.on_button = QPushButton() 291 self.on_button.setFlat(False) 292 self.on_button.setText("On") 293 self.on_button.clicked.connect(self.on_on_clicked) 294 buttons_layout.addWidget(self.on_button) 295 296 self.off_button = QPushButton() 297 self.off_button.setFlat(True) 298 self.off_button.setText("Off") 299 self.off_button.clicked.connect(self.on_off_clicked) 300 buttons_layout.addWidget(self.off_button) 301 buttons_layout.addItem(QSpacerItem(20, 20)) 302 frame_layout.addWidget(buttons_frame) 303 frame_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) 304 self.warning_frame.setVisible(False)
305
306 - def hasConfigs(self):
307 ''' 308 @return: True, if a configurations for this widget are available. 309 @rtype: bool 310 ''' 311 return len(self._nodes) > 0
312
313 - def nodes(self, cfg=''):
314 ''' 315 @return: the list with nodes required by this capability. The nodes are 316 defined by ROS full name. 317 @rtype: C{[str]} 318 ''' 319 try: 320 if cfg: 321 return [n for l in self._nodes[cfg].itervalues() for n in l] 322 else: 323 return [n for c in self._nodes.itervalues() for l in c.itervalues() for n in l] 324 except: 325 return []
326
327 - def setNodeState(self, running_nodes, stopped_nodes, error_nodes):
328 ''' 329 Sets the state of this capability. 330 @param running_nodes: a list with running nodes. 331 @type running_nodes: C{[str]} 332 @param stopped_nodes: a list with not running nodes. 333 @type stopped_nodes: C{[str]} 334 @param error_nodes: a list with nodes having a problem. 335 @type error_nodes: C{[str]} 336 ''' 337 self.setAutoFillBackground(True) 338 self.setBackgroundRole(QPalette.Base) 339 palette = QPalette() 340 if error_nodes: 341 brush = QBrush(QColor(255, 100, 0)) 342 elif running_nodes and stopped_nodes: 343 brush = QBrush(QColor(140, 185, 255)) # 30, 50, 255 344 elif running_nodes: 345 self.on_button.setFlat(True) 346 self.off_button.setFlat(False) 347 brush = QBrush(QColor(59, 223, 18)) # 59, 223, 18 348 else: 349 brush = QBrush(QColor(255, 255, 255)) 350 self.on_button.setFlat(False) 351 self.off_button.setFlat(True) 352 palette.setBrush(QPalette.Active, QPalette.Base, brush) 353 brush.setStyle(Qt.SolidPattern) 354 palette.setBrush(QPalette.Inactive, QPalette.Base, brush) 355 self.setPalette(palette)
356
357 - def removeCfg(self, cfg):
358 try: 359 del self._nodes[cfg] 360 except: 361 pass
362
363 - def updateNodes(self, cfg, ns, nodes):
364 self._nodes[cfg] = {ns: nodes} 365 test_nodes = self.nodes() 366 self.warning_frame.setVisible(len(test_nodes) != len(set(test_nodes)))
367
368 - def on_on_clicked(self):
369 started = set() # do not start nodes multiple times 370 for c in self._nodes.iterkeys(): 371 node2start = set(self.nodes(c)) - started 372 self.start_nodes_signal.emit(self._masteruri, c, list(node2start)) 373 started.update(node2start) 374 self.on_button.setFlat(True) 375 self.off_button.setFlat(False)
376
377 - def on_off_clicked(self):
378 self.stop_nodes_signal.emit(self._masteruri, self.nodes()) 379 self.on_button.setFlat(False) 380 self.off_button.setFlat(True)
381 382 383 ################################################################################ 384 ############## CapabilityTable ############## 385 ################################################################################ 386
387 -class CapabilityTable(QTableWidget):
388 ''' 389 The table shows all detected capabilities of robots in tabular view. The 390 columns represents the robot and rows the capabilities. The cell of available 391 capability contains a L{CapabilityControlWidget} to show the state and manage 392 the capability. 393 ''' 394 395 start_nodes_signal = Signal(str, str, list) 396 '''@ivar: the signal is emitted to start on host(described by masteruri) the nodes described in the list, Parameter(masteruri, config, nodes).''' 397 398 stop_nodes_signal = Signal(str, list) 399 '''@ivar: the signal is emitted to stop on masteruri the nodes described in the list.''' 400 401 description_requested_signal = Signal(str, str) 402 '''@ivar: the signal is emitted by click on a header to show a description.''' 403
404 - def __init__(self, parent=None):
405 QTableWidget.__init__(self, parent) 406 self._robotHeader = CapabilityHeader(Qt.Horizontal, self) 407 self._robotHeader.description_requested_signal.connect(self._show_description) 408 self.setHorizontalHeader(self._robotHeader) 409 self._capabilityHeader = CapabilityHeader(Qt.Vertical, self) 410 self._capabilityHeader.description_requested_signal.connect(self._show_description) 411 self.setVerticalHeader(self._capabilityHeader)
412
413 - def updateCapabilities(self, masteruri, cfg_name, description):
414 ''' 415 Updates the capabilities view. 416 @param masteruri: the ROS master URI of updated ROS master. 417 @type masteruri: C{str} 418 @param cfg_name: The name of the node provided the capabilities description. 419 @type cfg_name: C{str} 420 @param description: The capabilities description object. 421 @type description: U{multimaster_msgs_fkie.srv.ListDescription<http://docs.ros.org/api/multimaster_msgs_fkie/html/srv/ListDescription.html>} Response 422 ''' 423 # if it is a new masteruri add a new column 424 robot_index = self._robotHeader.index(masteruri) 425 robot_name = description.robot_name if description.robot_name else nm.nameres().mastername(masteruri) 426 # append a new robot 427 new_robot = False 428 if robot_index == -1: 429 robot_index = self._robotHeader.insertSortedItem(masteruri, robot_name) 430 self.insertColumn(robot_index) 431 # robot_index = self.columnCount()-1 432 # self._robotHeader.insertItem(robot_index) 433 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) 434 item = QTableWidgetItem() 435 item.setSizeHint(QSize(96, 96)) 436 self.setHorizontalHeaderItem(robot_index, item) 437 self.horizontalHeaderItem(robot_index).setText(robot_name) 438 new_robot = True 439 else: 440 # update 441 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) 442 443 # set the capabilities 444 for c in description.capabilities: 445 cap_index = self._capabilityHeader.index(c.name.decode(sys.getfilesystemencoding())) 446 if cap_index == -1 or new_robot: 447 if cap_index == -1: 448 # append a new capability 449 cap_index = self._capabilityHeader.insertSortedItem(c.name.decode(sys.getfilesystemencoding()), c.name.decode(sys.getfilesystemencoding())) 450 self.insertRow(cap_index) 451 self.setRowHeight(cap_index, 96) 452 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) 453 item = QTableWidgetItem() 454 item.setSizeHint(QSize(96, 96)) 455 self.setVerticalHeaderItem(cap_index, item) 456 self.verticalHeaderItem(cap_index).setText(c.name.decode(sys.getfilesystemencoding())) 457 else: 458 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) 459 # add the capability control widget 460 controlWidget = CapabilityControlWidget(masteruri, cfg_name, c.namespace, c.nodes) 461 controlWidget.start_nodes_signal.connect(self._start_nodes) 462 controlWidget.stop_nodes_signal.connect(self._stop_nodes) 463 self.setCellWidget(cap_index, robot_index, controlWidget) 464 self._capabilityHeader.controlWidget.insert(cap_index, controlWidget) 465 else: 466 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) 467 try: 468 self.cellWidget(cap_index, robot_index).updateNodes(cfg_name, c.namespace, c.nodes) 469 except: 470 import traceback 471 print traceback.format_exc()
472
473 - def removeConfig(self, cfg):
474 ''' 475 @param cfg: The name of the node provided the capabilities description. 476 @type cfg: C{str} 477 ''' 478 removed_from_robots = self._robotHeader.removeCfg(cfg) 479 # for r in removed_from_robots: 480 # if not self._robotHeader.getConfigs(r): 481 # #remove the column with robot 482 # pass 483 removed_from_caps = self._capabilityHeader.removeCfg(cfg) 484 # remove control widget with given configuration 485 for r in reversed(removed_from_robots): 486 for c in removed_from_caps: 487 controlWidget = self.cellWidget(c, r) 488 if isinstance(controlWidget, CapabilityControlWidget): 489 controlWidget.removeCfg(cfg) 490 if not controlWidget.hasConfigs(): 491 self.removeCellWidget(c, r) 492 # remove empty columns 493 for r in removed_from_robots: 494 is_empty = True 495 for c in reversed(range(self.rowCount())): 496 controlWidget = self.cellWidget(c, r) 497 if isinstance(controlWidget, CapabilityControlWidget): 498 is_empty = False 499 break 500 if is_empty: 501 self.removeColumn(r) 502 self._robotHeader.removeDescription(r) 503 # remove empty rows 504 for c in reversed(removed_from_caps): 505 is_empty = True 506 for r in reversed(range(self.columnCount())): 507 controlWidget = self.cellWidget(c, r) 508 if isinstance(controlWidget, CapabilityControlWidget): 509 is_empty = False 510 break 511 if is_empty: 512 self.removeRow(c) 513 self._capabilityHeader.removeDescription(c)
514
515 - def updateState(self, masteruri, master_info):
516 ''' 517 Updates the run state of the capability. 518 @param masteruri: The ROS master, which sends the master_info 519 @type masteruri: C{str} 520 @param master_info: The state of the ROS master 521 @type master_info: U{master_discovery_fkie.MasterInfo<http://docs.ros.org/api/master_discovery_fkie/html/modules.html#module-master_discovery_fkie.master_info>} 522 ''' 523 if master_info is None or masteruri is None: 524 return 525 robot_index = self._robotHeader.index(masteruri) 526 if robot_index != -1: 527 for c in range(self.rowCount()): 528 controlWidget = self.cellWidget(c, robot_index) 529 if controlWidget is not None: 530 running_nodes = [] 531 stopped_nodes = [] 532 error_nodes = [] 533 for n in controlWidget.nodes(): 534 node = master_info.getNode(n) 535 if node is not None: 536 # while a synchronization there are node from other hosts in the master_info -> filter these nodes 537 if node.uri is not None and masteruri == node.masteruri: 538 if node.pid is not None: 539 running_nodes.append(n) 540 else: 541 error_nodes.append(n) 542 else: 543 stopped_nodes.append(n) 544 controlWidget.setNodeState(running_nodes, stopped_nodes, error_nodes)
545
546 - def _start_nodes(self, masteruri, cfg, nodes):
547 self.start_nodes_signal.emit(masteruri, cfg, nodes)
548
549 - def _stop_nodes(self, masteruri, nodes):
551
552 - def _show_description(self, name, description):
553 self.description_requested_signal.emit(name, description)
554