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

Source Code for Module node_manager_fkie.master_view_proxy

   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 import loadUi 
  34  from python_qt_binding.QtCore import QRegExp, Qt, Signal, QRect 
  35  from python_qt_binding.QtGui import QImage, QKeySequence, QBrush, QPen 
  36  from rosgraph.names import is_legal_name 
  37  import getpass 
  38  import os 
  39  import re 
  40  import roslib 
  41  import rospy 
  42  import socket 
  43  import sys 
  44  import time 
  45  import traceback 
  46  import uuid 
  47  import xmlrpclib 
  48  
 
  49  from master_discovery_fkie.common import get_hostname 
  50  from master_discovery_fkie.master_info import NodeInfo 
  51  from node_manager_fkie.common import masteruri_from_ros, get_packages, package_name, resolve_paths, utf8 
  52  from node_manager_fkie.default_cfg_handler import DefaultConfigHandler 
  53  from node_manager_fkie.detailed_msg_box import MessageBox, DetailedError 
  54  from node_manager_fkie.html_delegate import HTMLDelegate 
  55  from node_manager_fkie.launch_config import LaunchConfig  # , LaunchConfigException 
  56  from node_manager_fkie.launch_server_handler import LaunchServerHandler 
  57  from node_manager_fkie.message_frame import MessageData, MessageFrame 
  58  from node_manager_fkie.node_tree_model import NodeTreeModel, NodeItem, GroupItem, HostItem, CellItem 
  59  from node_manager_fkie.parameter_dialog import ParameterDialog, MasterParameterDialog, ServiceDialog 
  60  from node_manager_fkie.parameter_handler import ParameterHandler 
  61  from node_manager_fkie.parameter_list_model import ParameterModel, ParameterNameItem, ParameterValueItem 
  62  from node_manager_fkie.progress_queue import ProgressQueue, InteractionNeededError  # , ProgressThread 
  63  from node_manager_fkie.select_dialog import SelectDialog 
  64  from node_manager_fkie.service_list_model import ServiceModel, ServiceItem, ServiceGroupItem 
  65  from node_manager_fkie.start_handler import AdvRunCfg 
  66  from node_manager_fkie.supervised_popen import SupervisedPopen 
  67  from node_manager_fkie.topic_list_model import TopicModel, TopicItem, TopicGroupItem 
  68  import node_manager_fkie as nm 
  69  try: 
  70      from python_qt_binding.QtGui import QAction, QFileDialog, QMenu, QShortcut, QWidget 
  71      from python_qt_binding.QtGui import QApplication, QVBoxLayout, QItemDelegate, QStyle 
  72  except: 
  73      from python_qt_binding.QtWidgets import QAction, QFileDialog, QMenu, QShortcut, QWidget 
  74      from python_qt_binding.QtWidgets import QApplication, QVBoxLayout, QItemDelegate, QStyle 
  75  
 
  76  
 
  77  try: 
  78      from python_qt_binding.QtGui import QItemSelection, QItemSelectionModel, QItemSelectionRange, QSortFilterProxyModel 
  79  except: 
  80      from python_qt_binding.QtCore import QItemSelection, QItemSelectionModel, QItemSelectionRange, QSortFilterProxyModel 
81 82 83 -class LaunchArgsSelectionRequest(Exception):
84 ''' Request needed to set the args of a launchfile from another thread. 85 @param args: a dictionary with args and values 86 @type args: dict 87 @param error: an error description 88 @type error: str 89 ''' 90
91 - def __init__(self, launchfile, args, error):
92 Exception.__init__(self) 93 self.launchfile = launchfile 94 self.args_dict = args 95 self.error = error
96
97 - def __str__(self):
98 return "LaunchArgsSelectionRequest for " + utf8(self.args_dict) + "::" + repr(self.error)
99
100 101 -class MasterViewProxy(QWidget):
102 ''' 103 This class stores the informations about a ROS master and shows it on request. 104 ''' 105 106 updateHostRequest = Signal(str) 107 host_description_updated = Signal(str, str, str) 108 '''@ivar: the signal is emitted on description changes and contains the 109 ROS Master URI, host address and description a parameter.''' 110 111 capabilities_update_signal = Signal(str, str, str, list) 112 '''@ivar: the signal is emitted if a description with capabilities is received 113 and has the ROS master URI, host address, the name of the default_cfg node and a list with 114 descriptions (U{multimaster_msgs_fkie.srv.ListDescription<http://docs.ros.org/api/multimaster_msgs_fkie/html/srv/ListDescription.html>} Response) as parameter.''' 115 remove_config_signal = Signal(str) 116 '''@ivar: the signal is emitted if a default_cfg was removed''' 117 118 description_signal = Signal(str, str, bool) 119 '''@ivar: the signal is emitted to show a description (title, description)''' 120 121 request_xml_editor = Signal(list, str) 122 '''@ivar: the signal to open a xml editor dialog (list with files, search text)''' 123 124 stop_nodes_signal = Signal(str, list) 125 '''@ivar: the signal is emitted to stop on masteruri the nodes described in the list.''' 126 127 robot_icon_updated = Signal(str, str) 128 '''@ivar: the signal is emitted, if the robot icon was changed by a configuration (masteruri, path)''' 129 130 loaded_config = Signal(str, object) 131 '''@ivar: the signal is emitted, after a launchfile is successful loaded (launchfile, LaunchConfig)''' 132 133 save_profile_signal = Signal(str) 134 '''@ivar: the signal is emitted, to save profile. (masteruri) If masteruri is empty, save all masters else only for this master.''' 135 136 DIAGNOSTIC_LEVELS = {0: 'OK', 137 1: 'WARN', 138 2: 'ERROR', 139 3: 'STALE', 140 4: 'UNKNOWN', 141 5: 'UNKNOWN'} 142
143 - def __init__(self, masteruri, parent=None):
144 ''' 145 Creates a new master. 146 @param masteruri: the URI of the ROS master 147 @type masteruri: C{str} 148 ''' 149 QWidget.__init__(self, parent) 150 self.setObjectName(' - '.join(['MasterViewProxy', masteruri])) 151 self.masteruri = masteruri 152 self.mastername = masteruri 153 self.main_window = parent 154 try: 155 self.mastername = get_hostname(self.masteruri) 156 except: 157 pass 158 159 self._tmpObjects = [] 160 self.__master_state = None 161 self.__master_info = None 162 self.__force_update = False 163 self.__configs = dict() # [file name] : LaunchConfig or tuple(ROS node name, ROS service uri, ROS master URI) : ROS nodes 164 self.__online = False 165 self.__run_id = '' 166 # self.rosconfigs = dict() # [launch file path] = LaunchConfig() 167 self.__in_question = [] # stores the changed files, until the user is interacted 168 # self.__uses_confgs = dict() # stores the decisions of the user for used configuration to start of node 169 '''@ivar: stored the question dialogs for changed files ''' 170 self._stop_ignores = ['rosout', rospy.get_name(), 'node_manager', 'master_discovery', 'master_sync', 'default_cfg', 'zeroconf'] 171 ''' @todo: detect the names of master_discovery and master_sync ndoes''' 172 173 self.__echo_topics_dialogs = dict() # [topic name] = EchoDialog 174 '''@ivar: stores the open EchoDialogs ''' 175 self.__last_info_text = None 176 self.__use_sim_time = False 177 self.__current_user = nm.settings().host_user(self.mastername) 178 self.__robot_icons = [] 179 self.__current_robot_icon = None 180 self.__current_parameter_robot_icon = '' 181 self.__republish_params = {} # { topic : params, created by dialog} 182 self.__last_selection = 0 183 self._on_stop_kill_roscore = False 184 self._on_stop_poweroff = False 185 self._start_nodes_after_load_cfg = dict() 186 # store the running_nodes to update to duplicates after load a launch file 187 self.__running_nodes = dict() # dict (node name : masteruri) 188 self._nodelets = dict() # dict(launchfile: dict(nodelet manager: list(nodes)) 189 self.default_cfg_handler = DefaultConfigHandler() 190 self.default_cfg_handler.node_list_signal.connect(self.on_default_cfg_nodes_retrieved) 191 self.default_cfg_handler.description_signal.connect(self.on_default_cfg_descr_retrieved) 192 self.default_cfg_handler.err_signal.connect(self.on_default_cfg_err) 193 194 self.__launch_servers = {} # uri : (pid, nodes) 195 self.launch_server_handler = LaunchServerHandler() 196 self.launch_server_handler.launch_server_signal.connect(self.on_launch_server_retrieved) 197 self.launch_server_handler.error_signal.connect(self.on_launch_server_err) 198 199 self.masterTab = QWidget() 200 ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'MasterTab.ui') 201 loadUi(ui_file, self.masterTab) 202 tabLayout = QVBoxLayout(self) 203 tabLayout.setContentsMargins(0, 0, 0, 0) 204 tabLayout.addWidget(self.masterTab) 205 self._progress_queue_prio = ProgressQueue(self.masterTab.progressPrioFrame, self.masterTab.progressPrioBar, self.masterTab.progressCancelPrioButton, 'Prio Master - %s' % self.mastername) 206 self._progress_queue = ProgressQueue(self.masterTab.progressFrame, self.masterTab.progressBar, self.masterTab.progressCancelButton, 'Master - %s' % self.mastername) 207 self._progress_queue_prio.no_screen_error_signal.connect(self._on_no_screen_error) 208 self._progress_queue.no_screen_error_signal.connect(self._on_no_screen_error) 209 210 # setup the node view 211 self.node_tree_model = NodeTreeModel(nm.nameres().address(self.masteruri), self.masteruri) 212 self.node_proxy_model = NodesSortFilterProxyModel(self) 213 self.node_proxy_model.setSourceModel(self.node_tree_model) 214 self.masterTab.nodeTreeView.setModel(self.node_proxy_model) 215 self.node_tree_model.hostInserted.connect(self.on_host_inserted) 216 for i, (_, width) in enumerate(NodeTreeModel.header): # _:=name 217 self.masterTab.nodeTreeView.setColumnWidth(i, width) 218 self.nodeNameDelegate = HTMLDelegate(check_for_ros_names=False, is_node=True) 219 self.masterTab.nodeTreeView.setItemDelegateForColumn(0, self.nodeNameDelegate) 220 self.node_delegate = IconsDelegate() 221 self.masterTab.nodeTreeView.setItemDelegateForColumn(1, self.node_delegate) 222 self.masterTab.nodeTreeView.collapsed.connect(self.on_node_collapsed) 223 self.masterTab.nodeTreeView.expanded.connect(self.on_node_expanded) 224 sm = self.masterTab.nodeTreeView.selectionModel() 225 sm.selectionChanged.connect(self.on_node_selection_changed) 226 self.masterTab.nodeTreeView.activated.connect(self.on_node_activated) 227 self.masterTab.nodeTreeView.clicked.connect(self.on_node_clicked) 228 # self.masterTab.nodeTreeView.setAcceptDrops(True) 229 # self.masterTab.nodeTreeWidget.setSortingEnabled(True) 230 231 # setup the topic view 232 self.topic_model = TopicModel() 233 self.topic_proxyModel = TopicsSortFilterProxyModel(self) 234 self.topic_proxyModel.setSourceModel(self.topic_model) 235 self.masterTab.topicsView.setModel(self.topic_proxyModel) 236 # self.masterTab.topicsView.setModel(self.topic_model) 237 self.masterTab.topicsView.expandAll() 238 self.masterTab.topicsView.sortByColumn(0, Qt.AscendingOrder) 239 for i, (_, width) in enumerate(TopicModel.header): # _:=name 240 self.masterTab.topicsView.setColumnWidth(i, width) 241 self.topicNameDelegate = HTMLDelegate(check_for_ros_names=False, is_node=True) 242 self.topicTypeDelegate = HTMLDelegate() 243 self.masterTab.topicsView.setItemDelegateForColumn(0, self.topicNameDelegate) 244 self.masterTab.topicsView.setItemDelegateForColumn(3, self.topicTypeDelegate) 245 sm = self.masterTab.topicsView.selectionModel() 246 sm.selectionChanged.connect(self.on_topic_selection_changed) 247 self.masterTab.topicsView.activated.connect(self.on_topic_activated) 248 self.masterTab.topicsView.clicked.connect(self.on_topic_clicked) 249 self.masterTab.topicsView.setSortingEnabled(True) 250 251 # setup the service view 252 self.service_model = ServiceModel() 253 self.service_proxyModel = ServicesSortFilterProxyModel(self) 254 self.service_proxyModel.setSourceModel(self.service_model) 255 self.masterTab.servicesView.setModel(self.service_proxyModel) 256 self.masterTab.servicesView.expandAll() 257 self.masterTab.servicesView.sortByColumn(0, Qt.AscendingOrder) 258 for i, (_, width) in enumerate(ServiceModel.header): # _:=name 259 self.masterTab.servicesView.setColumnWidth(i, width) 260 self.serviceNameDelegate = HTMLDelegate(check_for_ros_names=False, is_node=True) 261 self.serviceTypeDelegate = HTMLDelegate() 262 self.masterTab.servicesView.setItemDelegateForColumn(0, self.serviceNameDelegate) 263 self.masterTab.servicesView.setItemDelegateForColumn(1, self.serviceTypeDelegate) 264 sm = self.masterTab.servicesView.selectionModel() 265 sm.selectionChanged.connect(self.on_service_selection_changed) 266 self.masterTab.servicesView.activated.connect(self.on_service_activated) 267 self.masterTab.servicesView.clicked.connect(self.on_service_clicked) 268 self.masterTab.servicesView.setSortingEnabled(True) 269 270 # setup the parameter view 271 self.parameter_model = ParameterModel() 272 self.parameter_model.itemChanged.connect(self._on_parameter_item_changed) 273 self.parameter_proxyModel = ParameterSortFilterProxyModel(self) 274 self.parameter_proxyModel.setSourceModel(self.parameter_model) 275 self.masterTab.parameterView.setModel(self.parameter_proxyModel) 276 for i, (_, width) in enumerate(ParameterModel.header): # _:=name 277 self.masterTab.parameterView.setColumnWidth(i, width) 278 self.parameterNameDelegate = HTMLDelegate() 279 self.masterTab.parameterView.setItemDelegateForColumn(0, self.parameterNameDelegate) 280 sm = self.masterTab.parameterView.selectionModel() 281 sm.selectionChanged.connect(self.on_parameter_selection_changed) 282 self.masterTab.parameterView.setSortingEnabled(True) 283 284 # self.parameter_proxyModel.filterAcceptsRow = _filterParameterAcceptsRow 285 # self.masterTab.parameterView.activated.connect(self.on_service_activated) 286 287 # connect the buttons 288 self.masterTab.startButton.clicked.connect(self.on_start_clicked) 289 self.masterTab.stopButton.clicked.connect(self.on_stop_clicked) 290 # self.masterTab.stopContextButton.toggled.connect(self.on_stop_context_toggled) 291 self.masterTab.ioButton.clicked.connect(self.on_io_clicked) 292 self.masterTab.logButton.clicked.connect(self.on_log_clicked) 293 self.masterTab.logDeleteButton.clicked.connect(self.on_log_delete_clicked) 294 self.masterTab.dynamicConfigButton.clicked.connect(self.on_dynamic_config_clicked) 295 self.masterTab.editConfigButton.clicked.connect(self.on_edit_config_clicked) 296 self.masterTab.editRosParamButton.clicked.connect(self.on_edit_rosparam_clicked) 297 self.masterTab.saveButton.clicked.connect(self.on_save_clicked) 298 self.masterTab.closeCfgButton.clicked.connect(self.on_close_clicked) 299 300 self.masterTab.echoTopicButton.clicked.connect(self.on_topic_echo_clicked) 301 self.masterTab.hzTopicButton.clicked.connect(self.on_topic_hz_clicked) 302 self.masterTab.hzSshTopicButton.clicked.connect(self.on_topic_hz_ssh_clicked) 303 self.masterTab.pubTopicButton.clicked.connect(self.on_topic_pub_clicked) 304 self.masterTab.pubStopTopicButton.clicked.connect(self.on_topic_pub_stop_clicked) 305 306 self.masterTab.callServiceButton.clicked.connect(self.on_service_call_clicked) 307 self.masterTab.nodeFilterInput.textChanged.connect(self.on_node_filter_changed) 308 self.masterTab.topicFilterInput.textChanged.connect(self.on_topic_filter_changed) 309 self.masterTab.serviceFilterInput.textChanged.connect(self.on_service_filter_changed) 310 self.masterTab.parameterFilterInput.textChanged.connect(self.on_parameter_filter_changed) 311 self.masterTab.getParameterButton.clicked.connect(self.on_get_parameter_clicked) 312 self.masterTab.addParameterButton.clicked.connect(self.on_add_parameter_clicked) 313 self.masterTab.deleteParameterButton.clicked.connect(self.on_delete_parameter_clicked) 314 self.masterTab.saveParameterButton.clicked.connect(self.on_save_parameter_clicked) 315 316 # create a handler to request the parameter 317 self.parameterHandler = ParameterHandler() 318 self.parameterHandler.parameter_list_signal.connect(self._on_param_list) 319 self.parameterHandler.parameter_values_signal.connect(self._on_param_values) 320 self.parameterHandler.delivery_result_signal.connect(self._on_delivered_values) 321 # create a handler to request sim parameter 322 self.parameterHandler_sim = ParameterHandler() 323 # self.parameterHandler_sim.parameter_list_signal.connect(self._on_param_list) 324 self.parameterHandler_sim.parameter_values_signal.connect(self._on_sim_param_values) 325 # self.parameterHandler_sim.delivery_result_signal.connect(self._on_delivered_values) 326 327 self._shortcut_kill_node = QShortcut(QKeySequence(self.tr("Ctrl+Backspace", "Kill selected node")), self) 328 self._shortcut_kill_node.activated.connect(self.on_kill_nodes) 329 self._shortcut_kill_node = QShortcut(QKeySequence(self.tr("Ctrl+Delete", "Removes the registration of selected nodes from ROS master")), self) 330 self._shortcut_kill_node.activated.connect(self.on_unregister_nodes) 331 332 self.masterTab.ioButton.setEnabled(True) 333 self.masterTab.tabWidget.currentChanged.connect(self.on_tab_current_changed) 334 self._shortcut_screen_show_all = QShortcut(QKeySequence(self.tr("Shift+S", "Show all available screens")), self) 335 self._shortcut_screen_show_all.activated.connect(self.on_show_all_screens) 336 self._shortcut_screen_kill = QShortcut(QKeySequence(self.tr("Shift+Backspace", "Kill Screen")), self) 337 self._shortcut_screen_kill.activated.connect(self.on_kill_screens) 338 339 self.loaded_config.connect(self._apply_launch_config) 340 341 # set the shortcuts 342 self._shortcut1 = QShortcut(QKeySequence(self.tr("Alt+1", "Select first group")), self) 343 self._shortcut1.activated.connect(self.on_shortcut1_activated) 344 self._shortcut2 = QShortcut(QKeySequence(self.tr("Alt+2", "Select second group")), self) 345 self._shortcut2.activated.connect(self.on_shortcut2_activated) 346 self._shortcut3 = QShortcut(QKeySequence(self.tr("Alt+3", "Select third group")), self) 347 self._shortcut3.activated.connect(self.on_shortcut3_activated) 348 self._shortcut4 = QShortcut(QKeySequence(self.tr("Alt+4", "Select fourth group")), self) 349 self._shortcut4.activated.connect(self.on_shortcut4_activated) 350 self._shortcut5 = QShortcut(QKeySequence(self.tr("Alt+5", "Select fifth group")), self) 351 self._shortcut5.activated.connect(self.on_shortcut5_activated) 352 353 self._shortcut_collapse_all = QShortcut(QKeySequence(self.tr("Alt+C", "Collapse all groups")), self) 354 self._shortcut_collapse_all.activated.connect(self.on_shortcut_collapse_all) 355 self._shortcut_expand_all = QShortcut(QKeySequence(self.tr("Alt+E", "Expand all groups")), self) 356 self._shortcut_expand_all.activated.connect(self.masterTab.nodeTreeView.expandAll) 357 self._shortcut_run = QShortcut(QKeySequence(self.tr("Alt+R", "run selected nodes")), self) 358 self._shortcut_run.activated.connect(self.on_start_clicked) 359 self._shortcut_stop = QShortcut(QKeySequence(self.tr("Alt+S", "stop selected nodes")), self) 360 self._shortcut_stop.activated.connect(self.on_stop_clicked) 361 362 self.message_frame = MessageFrame() 363 self.masterTab.questionFrameLayout.addWidget(self.message_frame.frameui) 364 self.message_frame.accept_signal.connect(self._on_question_ok) 365 self.message_frame.cancel_signal.connect(self._on_question_cancel) 366 367 self.info_frame = MessageFrame(info=True) 368 self.masterTab.infoFrameLayout.addWidget(self.info_frame.frameui) 369 self.info_frame.accept_signal.connect(self._on_info_ok)
370 # self._shortcut_copy = QShortcut(QKeySequence(self.tr("Ctrl+C", "copy selected values to clipboard")), self) 371 # self._shortcut_copy.activated.connect(self.on_copy_c_pressed) 372 # self._shortcut_copy = QShortcut(QKeySequence(self.tr("Ctrl+X", "copy selected alternative values to clipboard")), self) 373 # self._shortcut_copy.activated.connect(self.on_copy_x_pressed) 374 375 # print "================ create", self.objectName() 376 # 377 # def __del__(self): 378 # print " Destroy mester view proxy", self.objectName(), " ..." 379 # print " ", self.objectName(), "destroyed" 380
381 - def stop(self):
382 print " Shutdown master", self.masteruri, "..." 383 self.default_cfg_handler.stop() 384 self.launch_server_handler.stop() 385 self._progress_queue_prio.stop() 386 self._progress_queue.stop() 387 if self._on_stop_kill_roscore: 388 self.killall_roscore() 389 for ps in self.__echo_topics_dialogs.values(): 390 try: 391 ps.terminate() 392 except: 393 pass 394 print " Master", self.masteruri, " is down!"
395 396 @property
397 - def current_user(self):
398 return self.__current_user
399 400 @current_user.setter
401 - def current_user(self, user):
402 self.__current_user = user 403 nm.settings().set_host_user(self.mastername, user)
404 405 @property
406 - def is_local(self):
407 return nm.is_local(get_hostname(self.masteruri))
408 409 @property
410 - def online(self):
411 ''' 412 The online meens that master is discovered and master_info was received. 413 ''' 414 return self.__online
415 416 @online.setter
417 - def online(self, state):
418 self.__online = state 419 self._start_queue(self._progress_queue) 420 self._start_queue(self._progress_queue_prio)
421 422 @property
423 - def master_state(self):
424 return self.__master_state
425 426 @master_state.setter
427 - def master_state(self, master_state):
428 self.__master_state = master_state
429 430 @property
431 - def master_info(self):
432 return self.__master_info
433 434 @master_info.setter
435 - def master_info(self, master_info):
436 ''' 437 Sets the new master information. To determine whether a node is running the 438 PID and his URI are needed. The PID of remote nodes (host of the ROS master 439 and the node are different) will be not determine by discovering. Thus this 440 information must be obtain from other MasterInfo object and stored while 441 updating. 442 @param master_info: the mater information object 443 @type master_info: U{master_discovery_fkie.msg.MasterInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#module-master_discovery_fkie.master_info>} 444 ''' 445 try: 446 update_result = (set(), set(), set(), set(), set(), set(), set(), set(), set()) 447 if self.__master_info is None: 448 if (master_info.masteruri == self.masteruri): 449 self.__master_info = master_info 450 update_result[0].update(self.__master_info.node_names) 451 update_result[3].update(self.__master_info.topic_names) 452 update_result[6].update(self.__master_info.service_names) 453 else: 454 update_result = self.__master_info.updateInfo(master_info) 455 # print "MINFO", self.__master_info.listedState() 456 # we receive the master info from remove nodes first -> skip 457 if self.__master_info is None: 458 return 459 try: 460 if (master_info.masteruri == self.masteruri): 461 self.update_system_parameter() 462 self.online = True 463 # request the info of new remote nodes 464 hosts2update = set([get_hostname(self.__master_info.getNode(nodename).uri) for nodename in update_result[0]]) 465 hosts2update.update([get_hostname(self.__master_info.getService(nodename).uri) for nodename in update_result[6]]) 466 for host in hosts2update: 467 if host != get_hostname(self.masteruri): 468 self.updateHostRequest.emit(host) 469 except: 470 pass 471 # cputimes = os.times() 472 # cputime_init = cputimes[0] + cputimes[1] 473 # update nodes in the model 474 if update_result[0] or update_result[1] or update_result[2] or self.__force_update: 475 self.updateRunningNodesInModel(self.__master_info) 476 self.on_node_selection_changed(None, None) 477 # Updates the topic view based on the current master information. 478 if update_result[3] or update_result[4] or update_result[5] or self.__force_update: 479 self.topic_model.updateModelData(self.__master_info.topics, update_result[3], update_result[4], update_result[5]) 480 self.on_topic_selection_changed(None, None) 481 # Updates the service view based on the current master information. 482 if update_result[6] or update_result[7] or update_result[8] or self.__force_update: 483 self.service_model.updateModelData(self.__master_info.services, update_result[6], update_result[7], update_result[8]) 484 self.on_service_selection_changed(None, None) 485 # update the default configuration 486 self.updateDefaultConfigs(self.__master_info) 487 self.__force_update = False 488 # cputimes = os.times() 489 # cputime = cputimes[0] + cputimes[1] - cputime_init 490 # print " update on ", self.__master_info.mastername if not self.__master_info is None else self.__master_state.name, cputime 491 except: 492 print traceback.format_exc(1)
493
494 - def _start_queue(self, queue):
495 if self.online and self.master_info is not None and isinstance(queue, ProgressQueue): 496 queue.start()
497 498 @property
499 - def use_sim_time(self):
500 return self.__use_sim_time
501
502 - def in_process(self):
503 return self._progress_queue.count() > 0 or self._progress_queue_prio.count() > 0
504
505 - def force_next_update(self):
506 self.__force_update = True
507
508 - def update_system_parameter(self):
509 self.parameterHandler_sim.requestParameterValues(self.masteruri, ["/run_id", "/use_sim_time", "/robot_icon", "/roslaunch/uris"])
510
511 - def markNodesAsDuplicateOf(self, running_nodes):
512 ''' 513 Marks all nodes, which are not running and in a given list as a duplicates nodes. 514 @param running_nodes: The list with names of running nodes 515 @type running_nodes: C{[str]} 516 ''' 517 # store the running_nodes to update to duplicates after load a launch file 518 self.__running_nodes = running_nodes 519 self.node_tree_model.markNodesAsDuplicateOf(running_nodes, (self.master_info is not None and self.master_info.getNodeEndsWith('master_sync')))
520
521 - def getRunningNodesIfSync(self):
522 ''' 523 Returns the list with all running nodes, which are registered by this ROS 524 master. Also the nodes, which are physically running on remote hosts. 525 @return: The list with names of running nodes 526 @rtype: C{[str]} 527 ''' 528 if self.master_info is not None and self.master_info.getNodeEndsWith('master_sync'): 529 return self.master_info.node_names 530 return []
531
532 - def getRunningNodesIfLocal(self, remove_system_nodes=False):
533 ''' 534 Returns the list with all running nodes, which are running (has process) on this host. 535 The nodes registered on this ROS master, but running on remote hosts are not 536 returned. 537 @return: The dictionary with names of running nodes and their masteruri 538 @rtype: C{dict(str:str)} 539 ''' 540 result = dict() 541 if self.master_info is not None: 542 for _, node in self.master_info.nodes.items(): # _:=name 543 if node.isLocal: 544 if not remove_system_nodes or not self._is_in_ignore_list(node.name): 545 result[node.name] = self.master_info.masteruri 546 return result
547
548 - def updateRunningNodesInModel(self, master_info):
549 ''' 550 Creates the dictionary with ExtendedNodeInfo objects and updates the nodes view. 551 @param master_info: the mater information object 552 @type master_info: U{master_discovery_fkie.msg.MasterInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#module-master_discovery_fkie.master_info>} 553 ''' 554 if master_info is not None: 555 self.node_tree_model.updateModelData(master_info.nodes) 556 self.updateButtons()
557
558 - def getNode(self, node_name):
559 ''' 560 @param node_name: The name of the node. 561 @type node_name: str 562 @return: The list the nodes with given name. 563 @rtype: [] 564 ''' 565 return self.node_tree_model.getNode("%s" % node_name, self.masteruri)
566
567 - def updateButtons(self, selected_nodes=None):
568 ''' 569 Updates the enable state of the buttons depending of the selection and 570 running state of the selected node. 571 ''' 572 selectedNodes = selected_nodes 573 if selectedNodes is None: 574 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 575 has_running = False 576 has_stopped = False 577 for node in selectedNodes: 578 if node.uri is not None: 579 has_running = True 580 else: 581 has_stopped = True 582 self.masterTab.startButton.setEnabled(True) 583 self.masterTab.stopButton.setEnabled(True) 584 # self.masterTab.ioButton.setEnabled(has_running or has_stopped) 585 self.masterTab.logButton.setEnabled(True) 586 # self.masterTab.logButton.setEnabled(has_running or has_stopped) 587 self.masterTab.logDeleteButton.setEnabled(has_running or has_stopped) 588 # test for available dynamic reconfigure services 589 if self.master_info is not None: 590 dyn_cfg_available = False 591 # dyncfgServices = [srv for srv_name, srv in self.master_info.services.items() if (srv_name.endswith('/set_parameters'))] 592 for n in selectedNodes: 593 for srv_name, srv in self.master_info.services.items(): 594 if (srv_name.endswith('/set_parameters')) and n.name in srv.serviceProvider: 595 dyn_cfg_available = True 596 break 597 self.masterTab.dynamicConfigButton.setEnabled(dyn_cfg_available) 598 # the configuration is only available, if only one node is selected 599 cfg_enable = False 600 if len(selectedNodes) >= 1: 601 cfg_enable = len(self._getCfgChoises(selectedNodes[0], True)) > 0 602 self.masterTab.editConfigButton.setEnabled(cfg_enable and len(selectedNodes) == 1) 603 # self.startNodesAtHostAct.setEnabled(cfg_enable) 604 self.masterTab.editRosParamButton.setEnabled(len(selectedNodes) == 1) 605 # self.masterTab.saveButton.setEnabled(len(self.launchfiles) > 1) 606 # enable the close button only for local configurations 607 self.masterTab.closeCfgButton.setEnabled(True)
608 # self.masterTab.closeCfgButton.setEnabled(len([path for path, _ in self.__configs.items() if (isinstance(path, tuple) and path[2] == self.masteruri) or not isinstance(path, tuple)]) > 0) #_:=cfg 609
610 - def hasLaunchfile(self, path):
611 ''' 612 @param path: the launch file 613 @type path: C{str} 614 @return: C{True} if the given launch file is open 615 @rtype: C{boolean} 616 ''' 617 return path in self.launchfiles
618 619 @property
620 - def default_cfgs(self):
621 ''' 622 Returns the copy of the dictionary with default configurations on this host 623 @rtype: C{[str(ROS node name)]} 624 ''' 625 result = [] 626 for (c, _) in self.__configs.items(): # _:=cfg 627 if isinstance(c, tuple): 628 result.append(c[0]) 629 return result
630 631 @property
632 - def launchfiles(self):
633 ''' 634 Returns the copy of the dictionary with loaded launch files on this host 635 @rtype: C{dict(str(file) : L{LaunchConfig}, ...)} 636 ''' 637 result = dict() 638 for (c, cfg) in self.__configs.items(): 639 if not isinstance(c, tuple): 640 result[c] = cfg 641 return result
642 643 @launchfiles.setter
644 - def launchfiles(self, launchfile):
645 ''' 646 Loads the launch file. If this file is already loaded, it will be reloaded. 647 After successful load the node view will be updated. 648 @param launchfile: the launch file path 649 @type launchfile: C{str} 650 ''' 651 lfile = launchfile 652 argv = [] 653 if isinstance(launchfile, tuple): 654 lfile, argv = launchfile 655 self._progress_queue_prio.add2queue(utf8(uuid.uuid4()), 656 'Loading %s' % os.path.basename(lfile), 657 self._load_launchfile, 658 (lfile, argv)) 659 self._start_queue(self._progress_queue_prio)
660
661 - def _load_launchfile(self, launchfile, argv_forced=[], pqid=None):
662 ''' 663 This method will be called in another thread. The configuration parameter 664 of the launch file will be requested using `LaunchArgsSelectionRequest` and 665 `InteractionNeededError`. After the file is successful loaded a 666 `loaded_config` signal will be emitted. 667 ''' 668 if argv_forced: 669 rospy.loginfo("LOAD launch: %s with args: %s" % (launchfile, argv_forced)) 670 else: 671 rospy.loginfo("LOAD launch: %s" % launchfile) 672 stored_argv = None 673 if launchfile in self.__configs: 674 # close the current loaded configuration with the same name 675 stored_argv = self.__configs[launchfile].argv 676 # load launch configuration 677 try: 678 # test for required args 679 launchConfig = LaunchConfig(launchfile, masteruri=self.masteruri) 680 loaded = False 681 # if the launch file currently open these args will be used 682 if stored_argv is None: 683 if argv_forced: 684 # if the parameter already requested `argv_forced` is filled: load! 685 loaded = launchConfig.load(argv_forced) 686 else: 687 # get the list with needed launch args 688 req_args = launchConfig.getArgs() 689 if req_args: 690 params = dict() 691 arg_dict = launchConfig.argvToDict(req_args) 692 for arg in arg_dict.keys(): 693 params[arg] = ('string', [arg_dict[arg]]) 694 # request the args: the dialog must run in the main thread of Qt 695 req = LaunchArgsSelectionRequest(launchfile, params, 'Needs input for args') 696 raise nm.InteractionNeededError(req, self._load_launchfile, (launchfile,)) 697 # load the launch file with args of currently open launch file 698 if not loaded or stored_argv is not None: 699 launchConfig.load(req_args if stored_argv is None else stored_argv) 700 # update the names of the hosts stored in the launch file 701 for _, machine in launchConfig.Roscfg.machines.items(): # _:=name 702 if machine.name: 703 nm.nameres().add_info(machine.name, machine.address) 704 # do not load if the loadings process was canceled 705 if self._progress_queue_prio.has_id(pqid): 706 self.loaded_config.emit(launchfile, launchConfig) 707 except InteractionNeededError: 708 raise 709 except Exception as e: 710 err_text = ''.join([os.path.basename(launchfile), ' loading failed!']) 711 err_details = ''.join([err_text, '\n\n', e.__class__.__name__, ": ", utf8(e)]) 712 rospy.logwarn("Loading launch file: %s", err_details) 713 raise DetailedError("Loading launch file", err_text, err_details) 714 # MessageBox.warning(self, "Loading launch file", err_text, err_details) 715 except: 716 print traceback.format_exc(3)
717
718 - def _apply_launch_config(self, launchfile, launchConfig):
719 stored_roscfg = None 720 expanded_items = None 721 if launchfile in self.__configs: 722 # store expanded items 723 expanded_items = self._get_expanded_groups() 724 # close the current loaded configuration with the same name 725 self.removeConfigFromModel(launchfile) 726 stored_roscfg = self.__configs[launchfile].Roscfg 727 del self.__configs[launchfile] 728 try: 729 # add launch file object to the list 730 self.__configs[launchfile] = launchConfig 731 self.appendConfigToModel(launchfile, launchConfig.Roscfg) 732 # self.masterTab.tabWidget.setCurrentIndex(0) 733 # get the descriptions of capabilities and hosts 734 try: 735 robot_descr = launchConfig.getRobotDescr() 736 capabilities = launchConfig.getCapabilitiesDesrc() 737 for (host, caps) in capabilities.items(): 738 if not host: 739 host = nm.nameres().mastername(self.masteruri) 740 host_addr = nm.nameres().address(host) 741 self.node_tree_model.addCapabilities(self.masteruri, host_addr, launchfile, caps) 742 for (host, descr) in robot_descr.items(): 743 if not host: 744 host = nm.nameres().mastername(self.masteruri) 745 host_addr = nm.nameres().address(host) 746 tooltip = self.node_tree_model.updateHostDescription(self.masteruri, host_addr, descr['type'], descr['name'], descr['description']) 747 self.host_description_updated.emit(self.masteruri, host_addr, tooltip) 748 except: 749 import traceback 750 print traceback.format_exc() 751 752 # by this call the name of the host will be updated if a new one is defined in the launch file 753 self.updateRunningNodesInModel(self.__master_info) 754 # detect files changes 755 if stored_roscfg and self.__configs[launchfile].Roscfg: 756 stored_values = [(name, utf8(p.value)) for name, p in stored_roscfg.params.items()] 757 new_values = [(name, utf8(p.value)) for name, p in self.__configs[launchfile].Roscfg.params.items()] 758 # detect changes parameter 759 paramset = set(name for name, _ in (set(new_values) - set(stored_values))) # _:=value 760 # detect new parameter 761 paramset |= (set(self.__configs[launchfile].Roscfg.params.keys()) - set(stored_roscfg.params.keys())) 762 # detect removed parameter 763 paramset |= (set(stored_roscfg.params.keys()) - set(self.__configs[launchfile].Roscfg.params.keys())) 764 # detect new nodes 765 stored_nodes = [roslib.names.ns_join(item.namespace, item.name) for item in stored_roscfg.nodes] 766 new_nodes = [roslib.names.ns_join(item.namespace, item.name) for item in self.__configs[launchfile].Roscfg.nodes] 767 nodes2start = set(new_nodes) - set(stored_nodes) 768 # determine the nodes of the changed parameter 769 for p in paramset: 770 for n in new_nodes: 771 if p.startswith(n): 772 nodes2start.add(n) 773 # detect changes in the arguments and remap 774 for n in stored_roscfg.nodes: 775 for new_n in self.__configs[launchfile].Roscfg.nodes: 776 if n.name == new_n.name and n.namespace == new_n.namespace: 777 if n.args != new_n.args or n.remap_args != new_n.remap_args: 778 nodes2start.add(roslib.names.ns_join(n.namespace, n.name)) 779 # filter out anonymous nodes 780 nodes2start = [n for n in nodes2start if not re.search(r"\d{3,6}_\d{10,}", n)] 781 # restart nodes 782 if nodes2start: 783 restart, ok = SelectDialog.getValue('Restart nodes?', "Select nodes to restart <b>@%s</b>:" % self.mastername, nodes2start, False, True, '', self) 784 if ok: 785 self.stop_nodes_by_name(restart) 786 self.start_nodes_by_name(restart, launchfile, True) 787 # set the robot_icon 788 if launchfile in self.__robot_icons: 789 self.__robot_icons.remove(launchfile) 790 self.__robot_icons.insert(0, launchfile) 791 self.markNodesAsDuplicateOf(self.__running_nodes) 792 # expand items to restore old view 793 if expanded_items is not None: 794 self._expand_groups(expanded_items) 795 # update nodelets 796 nodelets = {} 797 for n in launchConfig.Roscfg.nodes: 798 if n.package == 'nodelet' and n.type == 'nodelet': 799 args = n.args.split(' ') 800 if len(args) == 3 and args[0] == 'load': 801 nodelet_mngr = roslib.names.ns_join(n.namespace, args[2]) 802 if nodelet_mngr not in nodelets: 803 nodelets[nodelet_mngr] = [] 804 nodelets[nodelet_mngr].append(roslib.names.ns_join(n.namespace, n.name)) 805 for mngr, nlist in nodelets.iteritems(): 806 mngr_nodes = self.node_tree_model.getNode(mngr, self.masteruri) 807 for mn in mngr_nodes: 808 mn.nodelets = nlist 809 for nlet in nlist: 810 nlet_nodes = self.node_tree_model.getNode(nlet, self.masteruri) 811 for nn in nlet_nodes: 812 nn.nodelet_mngr = mngr 813 self._nodelets[launchfile] = nodelets 814 815 # print "MASTER:", launchConfig.Roscfg.master 816 # print "NODES_CORE:", launchConfig.Roscfg.nodes_core 817 # for n in launchConfig.Roscfg.nodes: 818 # n.__slots__ = [] 819 # print "NODES:", pickle.dumps(launchConfig.Roscfg.nodes) 820 # 821 # print "ROSLAUNCH_FILES:", launchConfig.Roscfg.roslaunch_files 822 # # list of resolved node names. This is so that we can check for naming collisions 823 # print "RESOLVED_NAMES:", launchConfig.Roscfg.resolved_node_names 824 # 825 # print "TESTS:", launchConfig.Roscfg.tests 826 # print "MACHINES:", launchConfig.Roscfg.machines 827 # print "PARAMS:", launchConfig.Roscfg.params 828 # print "CLEAR_PARAMS:", launchConfig.Roscfg.clear_params 829 # print "EXECS:", launchConfig.Roscfg.executables 830 # 831 # # for tools like roswtf 832 # print "ERRORS:", launchConfig.Roscfg.config_errors 833 # 834 # print "M:", launchConfig.Roscfg.m 835 if launchfile in self._start_nodes_after_load_cfg: 836 self.start_nodes_by_name(self._start_nodes_after_load_cfg[launchfile], launchfile, True) 837 del self._start_nodes_after_load_cfg[launchfile] 838 except Exception as e: 839 err_text = ''.join([os.path.basename(launchfile), ' loading failed!']) 840 err_details = ''.join([err_text, '\n\n', e.__class__.__name__, ": ", utf8(e)]) 841 rospy.logwarn("Loading launch file: %s", err_details) 842 MessageBox.warning(self, "Loading launch file", err_text, err_details) 843 import traceback 844 print traceback.format_exc(3) 845 except: 846 print traceback.format_exc(3) 847 self.update_robot_icon(True)
848
849 - def reload_global_parameter_at_next_start(self, launchfile):
850 try: 851 self.__configs[launchfile].global_param_done.remove(self.masteruri) 852 # self.on_node_selection_changed(None, None, True) 853 except Exception: 854 pass
855
856 - def question_reload_changed_file(self, changed, affected):
857 changed_res = "%s[%s]" % (os.path.basename(changed), utf8(package_name(os.path.dirname(changed))[0])) 858 self.message_frame.show_question(MessageFrame.TYPE_LAUNCH_FILE, 'Reload <b>%s</b>?<br>Changed files:' % os.path.basename(affected), MessageData(affected, [changed_res]))
859
860 - def question_transfer_changed_file(self, changed, affected):
861 self.message_frame.show_question(MessageFrame.TYPE_TRANSFER, 862 "Configuration file '%s' referenced by parameter in <b>%s</b> is changed.<br>Copy to remote host?" 863 "<br>Don\'t forget to restart the corresponding nodes!" % (changed, os.path.basename(affected)), MessageData(changed))
864
865 - def _get_nodelets(self, nodename, configname=''):
866 if configname and configname in self._nodelets: 867 if nodename in self._nodelets[configname]: 868 return self._nodelets[configname][nodename] 869 else: 870 for configname, mngrs in self._nodelets.iteritems(): 871 if nodename in mngrs: 872 return mngrs[nodename] 873 return []
874
875 - def _get_nodelet_manager(self, nodename, configname=''):
876 if configname and configname in self._nodelets: 877 for mngr, nodelets in self._nodelets[configname].iteritems(): 878 if nodename in nodelets: 879 return mngr 880 else: 881 for configname, mngrs in self._nodelets.iteritems(): 882 for mngr, nodelets in mngrs.iteritems(): 883 if nodename in nodelets: 884 return mngr 885 return None
886
887 - def _get_expanded_groups(self):
888 ''' 889 Returns a list of group names, which are expanded. 890 ''' 891 result = [] 892 try: 893 for r in range(self.masterTab.nodeTreeView.model().rowCount()): 894 index_host = self.masterTab.nodeTreeView.model().index(r, 0) 895 if index_host.isValid() and self.masterTab.nodeTreeView.isExpanded(index_host): 896 if self.masterTab.nodeTreeView.model().hasChildren(index_host): 897 for c in range(self.masterTab.nodeTreeView.model().rowCount(index_host)): 898 index_cap = self.masterTab.nodeTreeView.model().index(c, 0, index_host) 899 if index_cap.isValid() and self.masterTab.nodeTreeView.isExpanded(index_cap): 900 model_index = self.node_proxy_model.mapToSource(index_cap) 901 item = self.node_tree_model.itemFromIndex(model_index) 902 if isinstance(item, (GroupItem, HostItem)): 903 result.append(item.name) 904 except: 905 print traceback.format_exc(3) 906 return result
907
908 - def _expand_groups(self, groups=None):
909 ''' 910 Expands all groups, which are in the given list. If no list is given, 911 expands all groups of expanded hosts. 912 ''' 913 try: 914 for r in range(self.masterTab.nodeTreeView.model().rowCount()): 915 index_host = self.masterTab.nodeTreeView.model().index(r, 0) 916 if index_host.isValid() and self.masterTab.nodeTreeView.isExpanded(index_host): 917 if self.masterTab.nodeTreeView.model().hasChildren(index_host): 918 for c in range(self.masterTab.nodeTreeView.model().rowCount(index_host)): 919 index_cap = self.masterTab.nodeTreeView.model().index(c, 0, index_host) 920 if index_cap.isValid(): 921 model_index = self.node_proxy_model.mapToSource(index_cap) 922 item = self.node_tree_model.itemFromIndex(model_index) 923 if isinstance(item, (GroupItem, HostItem)): 924 if groups is None or item.name in groups: 925 self.masterTab.nodeTreeView.setExpanded(index_cap, True) 926 except: 927 print traceback.format_exc(3)
928
929 - def update_robot_icon(self, force=False):
930 ''' 931 Update the current robot icon. If the icon was changed a `robot_icon_updated` 932 signal will be emitted. 933 :return: the path to the current robot icon 934 :rtype: str 935 ''' 936 for l in self.__robot_icons: 937 try: 938 icon = self.__configs[l].get_robot_icon() 939 if icon: 940 if icon != self.__current_robot_icon or force: 941 self.__current_robot_icon = icon 942 self.robot_icon_updated.emit(self.masteruri, icon) 943 return icon 944 except: 945 pass 946 self.__current_robot_icon = self.__current_parameter_robot_icon 947 self.robot_icon_updated.emit(self.masteruri, utf8(self.__current_robot_icon)) 948 return self.__current_robot_icon
949
950 - def appendConfigToModel(self, launchfile, rosconfig):
951 ''' 952 Update the node view 953 @param launchfile: the launch file path 954 @type launchfile: C{str} 955 @param rosconfig: the configuration 956 @type rosconfig: L{LaunchConfig} 957 ''' 958 hosts = dict() # dict(addr : dict(node : [config]) ) 959 addr = nm.nameres().address(self.masteruri) 960 masteruri = self.masteruri 961 for n in rosconfig.nodes: 962 if n.machine_name and not n.machine_name == 'localhost': 963 if n.machine_name not in rosconfig.machines: 964 raise Exception(''.join(["ERROR: unknown machine [", n.machine_name, "]"])) 965 addr = rosconfig.machines[n.machine_name].address 966 masteruri = nm.nameres().masteruri(n.machine_name) 967 node = roslib.names.ns_join(n.namespace, n.name) 968 if (masteruri, addr) not in hosts: 969 hosts[(masteruri, addr)] = dict() 970 hosts[(masteruri, addr)][node] = launchfile 971 # add the configurations for each host separately 972 for ((masteruri, addr), nodes) in hosts.items(): 973 self.node_tree_model.appendConfigNodes(masteruri, addr, nodes) 974 self.updateButtons()
975
976 - def removeConfigFromModel(self, launchfile):
977 ''' 978 Update the node view after removed configuration. 979 @param launchfile: the launch file path 980 @type launchfile: C{str} 981 ''' 982 if isinstance(launchfile, tuple): 983 self.remove_config_signal.emit(launchfile[0]) 984 self.node_tree_model.removeConfigNodes(launchfile) 985 self.updateButtons()
986
987 - def updateDefaultConfigs(self, master_info):
988 ''' 989 Updates the default configuration view based on the current master information. 990 @param master_info: the mater information object 991 @type master_info: U{master_discovery_fkie.msg.MasterInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#module-master_discovery_fkie.master_info>} 992 ''' 993 if self.__master_info is None: 994 return 995 default_cfgs = [] 996 for name in self.__master_info.service_names: 997 if name.endswith('list_nodes'): 998 srv = self.__master_info.getService(name) 999 default_cfgs.append((roslib.names.namespace(name).rstrip(roslib.names.SEP), srv.uri, srv.masteruri)) 1000 # remove the node contained in default configuration form the view 1001 removed = list(set([c for c in self.__configs.keys() if isinstance(c, tuple)]) - set(default_cfgs)) 1002 if removed: 1003 for r in removed: 1004 self.node_tree_model.removeConfigNodes(r) 1005 # service = self.__master_info.getService(roslib.names.ns_join(r[0], 'list_nodes')) 1006 if r[2] == self.masteruri: 1007 self.remove_config_signal.emit(r[0]) 1008 del self.__configs[r] 1009 if len(self.__configs) == 0: 1010 address = nm.nameres().address(master_info.masteruri) 1011 tooltip = self.node_tree_model.updateHostDescription(master_info.masteruri, address, '', '', '') 1012 self.host_description_updated.emit(master_info.masteruri, address, tooltip) 1013 # request the nodes of new default configurations 1014 added = list(set(default_cfgs) - set(self.__configs.keys())) 1015 for (name, uri, _) in added: # _:= masteruri 1016 self.default_cfg_handler.requestNodeList(uri, roslib.names.ns_join(name, 'list_nodes')) 1017 # request the description 1018 descr_service = self.__master_info.getService(roslib.names.ns_join(name, 'description')) 1019 if descr_service is not None: 1020 self.default_cfg_handler.requestDescriptionList(descr_service.uri, descr_service.name) 1021 self.updateButtons()
1022
1023 - def on_default_cfg_nodes_retrieved(self, service_uri, config_name, nodes):
1024 ''' 1025 Handles the new list with nodes from default configuration service. 1026 @param service_uri: the URI of the service provided the default configuration 1027 @type service_uri: C{str} 1028 @param config_name: the name of default configuration service 1029 @type config_name: C{str} 1030 @param nodes: the name of the nodes with name spaces 1031 @type nodes: C{[str]} 1032 ''' 1033 # remove the current state 1034 masteruri = self.masteruri 1035 if self.__master_info is not None: 1036 service = self.__master_info.getService(config_name) 1037 if service is not None: 1038 masteruri = service.masteruri 1039 cfg_name = roslib.names.namespace(config_name).rstrip(roslib.names.SEP) 1040 key = (cfg_name, service_uri, masteruri) 1041 # if self.__configs.has_key(key): 1042 # self.node_tree_model.removeConfigNodes(key) 1043 # add the new config 1044 node_cfgs = dict() 1045 for n in nodes: 1046 node_cfgs[n] = key 1047 host = get_hostname(service_uri) 1048 host_addr = nm.nameres().address(host) 1049 if host_addr is None: 1050 host_addr = host 1051 self.node_tree_model.appendConfigNodes(masteruri, host_addr, node_cfgs) 1052 self.__configs[key] = nodes 1053 # start nodes in the queue 1054 if cfg_name in self._start_nodes_after_load_cfg: 1055 self.start_nodes_by_name(self._start_nodes_after_load_cfg[cfg_name], roslib.names.ns_join(cfg_name, 'run'), True) 1056 del self._start_nodes_after_load_cfg[cfg_name] 1057 self.updateButtons()
1058
1059 - def on_default_cfg_descr_retrieved(self, service_uri, config_name, items):
1060 ''' 1061 Handles the description list from default configuration service. 1062 Emits a Qt signal L{host_description_updated} to notify about a new host 1063 description and a Qt signal L{capabilities_update_signal} to notify about a capabilities 1064 update. 1065 @param service_uri: the URI of the service provided the default configuration 1066 @type service_uri: C{str} 1067 @param config_name: the name of default configuration service 1068 @type config_name: C{str} 1069 @param items: list with descriptions 1070 @type items: C{[U{multimaster_msgs_fkie.srv.ListDescription<http://docs.ros.org/api/multimaster_msgs_fkie/html/srv/ListDescription.html>} Response]} 1071 ''' 1072 if items: 1073 masteruri = self.masteruri 1074 if self.__master_info is not None: 1075 service = self.__master_info.getService(config_name) 1076 if service is not None: 1077 masteruri = service.masteruri 1078 key = (roslib.names.namespace(config_name).rstrip(roslib.names.SEP), service_uri, masteruri) 1079 host = get_hostname(service_uri) 1080 host_addr = nm.nameres().address(host) 1081 # add capabilities 1082 caps = dict() 1083 for c in items[0].capabilities: 1084 if c.namespace not in caps: 1085 caps[c.namespace] = dict() 1086 caps[c.namespace][c.name.decode(sys.getfilesystemencoding())] = {'type': c.type, 'images': [resolve_paths(i) for i in c.images], 'description': resolve_paths(c.description.replace("\\n ", "\n").decode(sys.getfilesystemencoding())), 'nodes': list(c.nodes)} 1087 if host_addr is None: 1088 host_addr = get_hostname(key[1]) 1089 self.node_tree_model.addCapabilities(masteruri, host_addr, key, caps) 1090 # set host description 1091 tooltip = self.node_tree_model.updateHostDescription(masteruri, host_addr, items[0].robot_type, items[0].robot_name.decode(sys.getfilesystemencoding()), resolve_paths(items[0].robot_descr.decode(sys.getfilesystemencoding()))) 1092 self.host_description_updated.emit(masteruri, host_addr, tooltip) 1093 self.capabilities_update_signal.emit(masteruri, host_addr, roslib.names.namespace(config_name).rstrip(roslib.names.SEP), items)
1094
1095 - def on_default_cfg_err(self, service_uri, service, msg):
1096 ''' 1097 Handles the error messages from default configuration service. 1098 @param service_uri: the URI of the service provided the default configuration 1099 @type service_uri: C{str} 1100 @param service: the name of default configuration service 1101 @type service: C{str} 1102 @param msg: the error message 1103 @type msg: C{str} 1104 ''' 1105 pass
1106 # MessageBox.warning(self, 'Error while call %s'%service, 1107 # utf8(msg), 1108 # buttons=MessageBox.Ok) 1109 1110 @property
1111 - def launch_servers(self):
1112 return self.__launch_servers
1113
1114 - def has_launch_server(self):
1115 ''' 1116 Returns `True` if the there are roslaunch server, which have no `master` as 1117 node or or have other nodes as `rosout-#` inside. 1118 ''' 1119 for _, (_, nodes) in self.__launch_servers.items(): # _:= uri, pid 1120 if not self._is_master_launch_server(nodes): 1121 return True 1122 return False
1123
1124 - def _is_master_launch_server(self, nodes):
1125 if 'master' in nodes and len(nodes) < 3: 1126 return True 1127 return False
1128
1129 - def on_launch_server_retrieved(self, serveruri, pid, nodes):
1130 ''' 1131 Handles the info about roslaunch server. 1132 Emits a Qt signal L{host_description_updated} to notify about a new host 1133 description and a Qt signal L{capabilities_update_signal} to notify about a capabilities 1134 update. 1135 @param serveruri: the URI of the roslaunch server 1136 @type serveruri: C{str} 1137 @param pid: the process id of the roslaunch server 1138 @type pid: C{str} 1139 @param nodes: list with nodes handled by the roslaunch server 1140 @type nodes: C{[L{str}]} 1141 ''' 1142 self.__launch_servers[serveruri] = (pid, nodes)
1143
1144 - def on_launch_server_err(self, serveruri, msg):
1145 ''' 1146 Handles the error messages from launch server hanlder. 1147 @param serveruri: the URI of the launch server 1148 @type serveruri: C{str} 1149 @param msg: the error message 1150 @type msg: C{str} 1151 ''' 1152 try: 1153 del self.__launch_servers[serveruri] 1154 except: 1155 pass
1156
1158 ''' 1159 Kill all running launch server. The coresponding URIS are removed by master_monitor. 1160 ''' 1161 for lsuri, (pid, nodes) in self.__launch_servers.items(): 1162 try: 1163 if not self._is_master_launch_server(nodes): 1164 self._progress_queue.add2queue(utf8(uuid.uuid4()), 1165 ''.join(['kill roslaunch ', lsuri, '(', utf8(pid), ')']), 1166 nm.starter().kill, 1167 (get_hostname(lsuri), pid, False, self.current_user)) 1168 self.launch_server_handler.updateLaunchServerInfo(lsuri, delayed_exec=3.0) 1169 except Exception as e: 1170 rospy.logwarn("Error while kill roslaunch %s: %s", utf8(lsuri), utf8(e)) 1171 raise DetailedError("Kill error", 1172 ''.join(['Error while kill roslaunch ', lsuri]), 1173 utf8(e)) 1174 self._start_queue(self._progress_queue)
1175 1176 # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1177 # %%%%%%%%%%%%% Handling of the view activities %%%%%%%% 1178 # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1179
1180 - def on_node_activated(self, index):
1181 ''' 1182 Depending of the state of the node, it will be run or the screen output will 1183 be open. 1184 @param index: The index of the activated node 1185 @type index: U{QtCore.QModelIndex<https://srinikom.github.io/pyside-docs/PySide/QtCore/QModelIndex.html>} 1186 ''' 1187 selectedNodes = [] 1188 if index.column() == 0: 1189 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes(), False) 1190 if not selectedNodes: 1191 return 1192 has_running = False 1193 has_stopped = False 1194 has_invalid = False 1195 for node in selectedNodes: 1196 if node.uri is not None: 1197 has_running = True 1198 if node.pid is None: 1199 has_invalid = True 1200 else: 1201 has_stopped = True 1202 if has_stopped: 1203 self.on_start_clicked() 1204 elif has_running and not has_invalid: 1205 self.on_io_clicked() 1206 else: 1207 self.on_log_clicked()
1208
1209 - def on_node_clicked(self, index):
1210 self.message_frame.hide_question([MessageFrame.TYPE_NODELET]) 1211 self.info_frame.hide_question([MessageFrame.TYPE_NOSCREEN]) 1212 if time.time() - self.__last_selection > 1.: 1213 self.on_node_selection_changed(None, None, True)
1214
1215 - def on_topic_activated(self, index):
1216 ''' 1217 @param index: The index of the activated topic 1218 @type index: U{QtCore.QModelIndex<https://srinikom.github.io/pyside-docs/PySide/QtCore/QModelIndex.html>} 1219 ''' 1220 model_index = self.topic_proxyModel.mapToSource(index) 1221 item = self.topic_model.itemFromIndex(model_index) 1222 if isinstance(item, TopicItem): 1223 self.on_topic_echo_clicked([item.topic])
1224
1225 - def on_topic_clicked(self, index):
1226 if time.time() - self.__last_selection > 1.: 1227 self.on_topic_selection_changed(None, None, True)
1228
1229 - def on_service_activated(self, index):
1230 ''' 1231 @param index: The index of the activated service 1232 @type index: U{QtCore.QModelIndex<https://srinikom.github.io/pyside-docs/PySide/QtCore/QModelIndex.html>} 1233 ''' 1234 model_index = self.service_proxyModel.mapToSource(index) 1235 item = self.service_model.itemFromIndex(model_index) 1236 if isinstance(item, ServiceItem): 1237 self.on_service_call_clicked([item.service])
1238
1239 - def on_service_clicked(self, index):
1240 if time.time() - self.__last_selection > 1.: 1241 self.on_service_selection_changed(None, None, True)
1242
1243 - def on_host_inserted(self, item):
1244 if item == (self.masteruri, nm.nameres().hostname(get_hostname(self.masteruri))): 1245 index = self.node_tree_model.indexFromItem(item) 1246 model_index = self.node_proxy_model.mapFromSource(index) 1247 if model_index.isValid(): 1248 self.masterTab.nodeTreeView.expand(model_index)
1249 # self.masterTab.nodeTreeView.expandAll() 1250
1251 - def on_node_collapsed(self, index):
1252 if not index.parent().isValid(): 1253 self.masterTab.nodeTreeView.selectionModel().clear()
1254
1255 - def on_node_expanded(self, index):
1256 pass
1257
1258 - def _create_html_list(self, title, items, list_type=None, name=''):
1259 ''' 1260 :param list_type: LAUNCH, TOPIC, NODE, SERVICE 1261 :type list_type: str 1262 ''' 1263 result = '' 1264 if items: 1265 result = '%s<b><u>%s</u></b>' % (result, title) 1266 if len(items) > 1: 1267 result = '%s <span style="color:gray;">[%d]</span>' % (result, len(items)) 1268 result = '%s<br><ul><span></span>' % result 1269 items.sort() 1270 for i in items: 1271 item = i 1272 # reduce the displayed name 1273 item_name = i 1274 if name: 1275 if item_name.startswith(name): 1276 item_name = item_name.replace('%s%s' % (name, roslib.names.SEP), '~', 1) 1277 ns = roslib.names.namespace(name) 1278 if item_name.startswith(ns) and ns != roslib.names.SEP: 1279 item_name = item_name.replace(ns, '', 1) 1280 if list_type in ['NODE']: 1281 item = '<a href="node://%s%s">%s</a>' % (self.mastername, i, item_name) 1282 elif list_type in ['TOPIC_PUB', 'TOPIC_SUB']: 1283 # determine the count of publisher or subscriber 1284 count = None 1285 try: 1286 tpc = self.__master_info.getTopic(i) 1287 if list_type == 'TOPIC_SUB': 1288 count = len(tpc.publisherNodes) 1289 if name not in tpc.subscriberNodes: 1290 count = None 1291 else: 1292 count = len(tpc.subscriberNodes) 1293 if name not in tpc.publisherNodes: 1294 count = None 1295 except: 1296 pass 1297 # add the count 1298 if count is not None: 1299 item = '<a href="topic://%s">%s</a>' % (i, item_name) 1300 item += ' <a href="topicecho://%s%s"><span style="color:gray;"><i>echo</i></span></a>' % (self.mastername, i) 1301 item = '<span style="color:gray;">_%d_/ </span>%s' % (count, item) 1302 else: 1303 item = '<a>%s</a>' % (item_name) 1304 item = '<span style="color:red;">!sync </span>%s' % (item) 1305 elif list_type == 'SERVICE': 1306 try: 1307 srv = self.__master_info.getService(i) 1308 if name in srv.serviceProvider: 1309 item = '<a href="service://%s%s">%s</a>' % (self.mastername, i, item_name) 1310 item += ' <a href="servicecall://%s%s"><span style="color:gray;"><i>call</i></span></a>' % (self.mastername, i) 1311 else: 1312 item = '<span style="color:red;">!sync </span>%s' % (item_name) 1313 except: 1314 item = '<span style="color:red;">!sync </span>%s' % (item_name) 1315 elif list_type == 'LAUNCH': 1316 item = '<a href="launch://%s">%s</a>' % (i, item_name) 1317 if i in self.__configs and self.masteruri in self.__configs[i].global_param_done: 1318 item += '%s<br><a href="reload-globals://%s"><font color="#339900">reload global parameter @next start</font></a>' % (item, i) 1319 result += '\n%s<br>' % (item) 1320 result += '</ul>' 1321 return result
1322
1323 - def on_tab_current_changed(self, index):
1324 tab_name = self.masterTab.tabWidget.currentWidget().objectName() 1325 if tab_name == 'tabTopics': 1326 # select the topics of the selected node in the "Topic" view 1327 selections = self.masterTab.nodeTreeView.selectionModel().selectedIndexes() 1328 selectedNodes = self.nodesFromIndexes(selections) 1329 if len(selectedNodes) == 1: 1330 node = selectedNodes[0] 1331 selected_topics = self.topic_model.index_from_names(node.published, node.subscribed) 1332 for s in selected_topics: 1333 self.masterTab.topicsView.selectionModel().select(self.topic_proxyModel.mapFromSource(s), QItemSelectionModel.Select) 1334 elif tab_name == 'tabServices': 1335 # select the services of the selected node in the "Services" view 1336 selections = self.masterTab.nodeTreeView.selectionModel().selectedIndexes() 1337 selectedNodes = self.nodesFromIndexes(selections) 1338 if len(selectedNodes) == 1: 1339 node = selectedNodes[0] 1340 selected_services = self.service_model.index_from_names(node.services) 1341 for s in selected_services: 1342 self.masterTab.servicesView.selectionModel().select(self.service_proxyModel.mapFromSource(s), QItemSelectionModel.Select)
1343
1344 - def _is_current_tab_name(self, tab_name):
1345 return (self.masterTab.tabWidget.currentWidget().objectName() == tab_name)
1346
1347 - def on_node_selection_changed(self, selected, deselected, force_emit=False, node_name=''):
1348 ''' 1349 updates the Buttons, create a description and emit L{description_signal} to 1350 show the description of host, group or node. 1351 ''' 1352 if selected is not None: 1353 # it is a workaround to avoid double updates a after click on an item 1354 self.__last_selection = time.time() 1355 selectedGroups = [] 1356 if node_name and self.master_info is not None: 1357 # get node by name 1358 selectedNodes = self.getNode(node_name) 1359 if not selectedNodes or selectedNodes[0] is None: 1360 if node_name: 1361 self.description_signal.emit(node_name, "<b>%s</b> not found" % node_name, True if selected or deselected or force_emit else False) 1362 return 1363 selectedHosts = [] 1364 selections = [] 1365 else: 1366 # get node by selected items 1367 if not self._is_current_tab_name('tabNodes'): 1368 return 1369 selections = self.masterTab.nodeTreeView.selectionModel().selectedIndexes() 1370 selectedHosts = self.hostsFromIndexes(selections) 1371 selectedNodes = self.nodesFromIndexes(selections) 1372 selectedGroups = self.groupsFromIndexes(selections) 1373 self.masterTab.topicsView.selectionModel().clear() 1374 self.masterTab.servicesView.selectionModel().clear() 1375 name = '' 1376 text = '' 1377 # add control buttons for more then one selected node 1378 if len(selectedNodes) > 1 or selectedGroups > 0: 1379 restartable_nodes = [sn for sn in selectedNodes if len(sn.cfgs) > 0 and not self._is_in_ignore_list(sn.name)] 1380 restartable_nodes_with_launchfiles = [sn for sn in selectedNodes if sn.has_launch_cfgs(sn.cfgs) > 0 and not self._is_in_ignore_list(sn.name)] 1381 killable_nodes = [sn for sn in selectedNodes if sn.node_info.pid is not None and not self._is_in_ignore_list(sn.name)] 1382 unregisterble_nodes = [sn for sn in selectedNodes if sn.node_info.pid is None and sn.node_info.uri is not None and sn.node_info.isLocal and not self._is_in_ignore_list(sn.name)] 1383 # add description for multiple selected nodes 1384 if restartable_nodes or killable_nodes or unregisterble_nodes: 1385 text += '<b>Selected nodes:</b><br>' 1386 if restartable_nodes: 1387 text += '<a href="restart-node://all_selected_nodes" title="Restart %s selected nodes"><img src=":icons/sekkyumu_restart_24.png" alt="restart">[%d]</a>' % (len(restartable_nodes), len(restartable_nodes)) 1388 text += '&nbsp;<a href="restart-node-g://all_selected_nodes" title="Reload global parameter and restart %s selected nodes"><img src=":icons/sekkyumu_restart_g_24.png" alt="restart">[%d]</a>' % (len(restartable_nodes), len(restartable_nodes)) 1389 if killable_nodes: 1390 # text += '&nbsp;<a href="kill-node://all_selected_nodes" title="Kill %s selected nodes"><img src=":icons/sekkyumu_kill_24.png" alt="kill">[%d]</a>' % (len(killable_nodes), len(killable_nodes)) 1391 text += '&nbsp;<a href="kill-screen://all_selected_nodes" title="Kill %s screens of selected nodes"><img src=":icons/sekkyumu_kill_screen_24.png" alt="killscreen">[%d]</a>' % (len(killable_nodes), len(killable_nodes)) 1392 if restartable_nodes_with_launchfiles: 1393 text += '&nbsp;<a href="start-node-at-host://all_selected_nodes" title="Start %s nodes at another host"><img src=":icons/sekkyumu_start_athost_24.png" alt="start@host">[%d]</a>' % (len(restartable_nodes_with_launchfiles), len(restartable_nodes_with_launchfiles)) 1394 text += '&nbsp;<a href="start-node-adv://all_selected_nodes" title="Start %s nodes with additional options, e.g. loglevel"><img src=":icons/sekkyumu_play_alt_24.png" alt="play alt">[%d]</a>' % (len(restartable_nodes_with_launchfiles), len(restartable_nodes_with_launchfiles)) 1395 if unregisterble_nodes: 1396 text += '<br><a href="unregister-node://all_selected_nodes">unregister [%d]</a>' % len(unregisterble_nodes) 1397 # add host description, if only the one host is selected 1398 if len(selectedHosts) == 1: # and len(selections) / 2 == 1: 1399 host = selectedHosts[0] 1400 name = '%s - Robot' % host.name 1401 text += host.generateDescription() 1402 text += '<br>' 1403 else: 1404 # add group description, if only the one group is selected 1405 if len(selectedGroups) == 1 and len(selections) / 2 == 1: 1406 group = selectedGroups[0] 1407 name = '%s - Group' % group.name 1408 text += group.generateDescription() 1409 text += '<br>' 1410 # add node description for one selected node 1411 if len(selectedHosts) != 1 and len(selectedNodes) == 1 and len(selectedGroups) == 0: 1412 node = selectedNodes[0] 1413 text = self.get_node_description(node_name, node) 1414 name = node.name 1415 if (self._is_current_tab_name('tabNodes') and self.__last_info_text != text) or force_emit: 1416 self.__last_info_text = text 1417 self.description_signal.emit(name, text, True if selected or deselected or force_emit else False) 1418 self.updateButtons(selectedNodes)
1419
1420 - def get_node_description(self, node_name, node=None):
1421 text = '' 1422 if node_name and node is None and self.master_info is not None: 1423 # get node by name 1424 selectedNodes = self.getNode(node_name) 1425 if len(selectedNodes) == 1: 1426 node = selectedNodes[0] 1427 # add node description for one selected node 1428 if node is not None: 1429 # create description for a node 1430 ns, sep, name = node.name.rpartition(rospy.names.SEP) 1431 text = '<font size="+1"><b><span style="color:gray;">%s%s</span><b>%s</b></font><br>' % (ns, sep, name) 1432 launches = [c for c in node.cfgs if not isinstance(c, tuple)] 1433 default_cfgs = [c[0] for c in node.cfgs if isinstance(c, tuple)] 1434 if launches or default_cfgs: 1435 text += '<a href="restart-node://%s" title="Restart node"><img src=":icons/sekkyumu_restart_24.png" alt="restart"></a>' % node.name # height="24" width="24" 1436 text += '&nbsp;<a href="restart-node-g://%s" title="Reload global parameter and restart node"><img src=":icons/sekkyumu_restart_g_24.png" alt="restart"></a>' % node.name # height="24" width="24" 1437 # text += '&nbsp; <a href="kill-node://%s" title="Kill node with pid %s"><img src=":icons/sekkyumu_kill_24.png" alt="kill"></a>' % (node.name, node.pid) 1438 text += '&nbsp; <a href="kill-screen://%s" title="Kill screen of the node"><img src=":icons/sekkyumu_kill_screen_24.png" alt="killscreen"></a>' % node.name 1439 if launches: 1440 text += '&nbsp; <a href="start-node-at-host://%s" title="Start node at another host"><img src=":icons/sekkyumu_start_athost_24.png" alt="start@host"></a>' % node.name 1441 # if node.node_info.pid is None or node.node_info.uri is None: 1442 text += '&nbsp; <a href="start-node-adv://%s" title="Start node with additional options, e.g. loglevel"><img src=":icons/sekkyumu_play_alt_24.png" alt="play alt"></a>' % node.name 1443 text += '&nbsp; <a href="copy-log-path://%s" title="copy log path to clipboard"><img src=":icons/crystal_clear_copy_log_path_24.png" alt="copy_log_path"></a>' % node.name 1444 text += '<dl>' 1445 text += '<dt><b>URI</b>: %s</dt>' % node.node_info.uri 1446 text += '<dt><b>PID</b>: %s</dt>' % node.node_info.pid 1447 text += '<dt><b>ORG.MASTERURI</b>: %s</dt>' % node.node_info.masteruri 1448 if not is_legal_name(node.name): 1449 text += '<dt><font color="#FF6600"><b>This node has an illegal <node> name.<br><a href="http://ros.org/wiki/Names">http://ros.org/wiki/Names</a><br>This will likely cause problems with other ROS tools.</b></font></dt>' 1450 if node.is_ghost: 1451 if node.name.endswith('master_sync') or node.name.endswith('node_manager'): 1452 text += '<dt><font color="#FF9900"><b>This node is not synchronized by default. To get info about this node select the related host.</b></font></dt>' 1453 else: 1454 text += '<dt><font color="#FF9900"><b>The node is running on remote host, but is not synchronized, because of filter or errors while sync, see log of <i>master_sync</i></b></font></dt>' 1455 text += '<dt><font color="#FF9900"><i>Are you use the same ROS packages?</i></font></dt>' 1456 if node.has_running and node.node_info.pid is None and node.node_info.uri is None: 1457 text += '<dt><font color="#FF9900"><b>There 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!)</b></font></dt>' 1458 if node.node_info.uri is not None and node.node_info.masteruri != self.masteruri: 1459 text += '<dt><font color="#339900"><b>synchronized</b></font></dt>' 1460 if node.node_info.pid is None and node.node_info.uri is not None: 1461 if not node.node_info.isLocal: 1462 text += '<dt><font color="#FF9900"><b>remote nodes will not be ping, so they are always marked running</b></font>' 1463 else: 1464 text += '<dt><font color="#CC0000"><b>the node does not respond: </b></font>' 1465 text += '<a href="unregister-node://%s">unregister</a></dt>' % node.name 1466 if node.diagnostic_array and node.diagnostic_array[-1].level > 0: 1467 diag_status = node.diagnostic_array[-1] 1468 level_str = self.DIAGNOSTIC_LEVELS[diag_status.level] 1469 diag_color = '#FF6600' 1470 if diag_status.level == 2: 1471 diag_color = '#CC0000' 1472 elif diag_status.level == 3: 1473 diag_color = '#FFCC00' 1474 elif diag_status.level > 3: 1475 diag_color = '#0000CC' 1476 text += '<dt><font color="%s"><b>%s: %s</b></font></dt>' % (diag_color, level_str, node.diagnostic_array[-1].message) 1477 # if len(node.diagnostic_array) > 1: 1478 # text += '<dt><font color="#FF6600"><a href="view_diagnostics://%s">view recent %d items</a></font></dt>'%(node.name, len(node.diagnostic_array)) 1479 text += '</dl>' 1480 if node.nodelet_mngr: 1481 text += '<dt><b>Nodelet manager</b>: %s</dt>' % self._create_html_list('', [node.nodelet_mngr], 'NODE') 1482 if node.nodelets: 1483 text += '<dt>Manager for <b>%d</b> nodelets</dt>' % len(node.nodelets) 1484 if nm.settings().transpose_pub_sub_descr: 1485 text += self._create_html_list('Subscribed Topics:', node.subscribed, 'TOPIC_SUB', node.name) 1486 text += self._create_html_list('Published Topics:', node.published, 'TOPIC_PUB', node.name) 1487 else: 1488 text += self._create_html_list('Published Topics:', node.published, 'TOPIC_PUB', node.name) 1489 text += self._create_html_list('Subscribed Topics:', node.subscribed, 'TOPIC_SUB', node.name) 1490 text += self._create_html_list('Services:', node.services, 'SERVICE', node.name) 1491 # set loaunch file paths 1492 text += self._create_html_list('Loaded Launch Files:', launches, 'LAUNCH') 1493 text += self._create_html_list('Default Configurations:', default_cfgs, 'NODE') 1494 # text += '<dt><a href="copy-log-path://%s">copy log path to clipboard</a></dt>'%node.name 1495 return text
1496
1497 - def on_topic_selection_changed(self, selected, deselected, force_emit=False, topic_name=''):
1498 ''' 1499 updates the Buttons, create a description and emit L{description_signal} to 1500 show the description of selected topic 1501 ''' 1502 if selected is not None: 1503 # it is a workaround to avoid double updates a after click on an item 1504 self.__last_selection = time.time() 1505 selectedTopics = [] 1506 if topic_name and self.master_info is not None: 1507 selectedTopics = [self.master_info.getTopic("%s" % topic_name)] 1508 if len(selectedTopics) == 0: 1509 return 1510 else: 1511 if not self._is_current_tab_name('tabTopics'): 1512 return 1513 selectedTopics = self.topicsFromIndexes(self.masterTab.topicsView.selectionModel().selectedIndexes()) 1514 topics_selected = (len(selectedTopics) > 0) 1515 self.masterTab.echoTopicButton.setEnabled(topics_selected) 1516 self.masterTab.hzTopicButton.setEnabled(topics_selected) 1517 self.masterTab.hzSshTopicButton.setEnabled(topics_selected) 1518 self.masterTab.pubStopTopicButton.setEnabled(topics_selected) 1519 if len(selectedTopics) == 1: 1520 try: 1521 topic = selectedTopics[0] 1522 text = self.get_topic_description(topic_name, topic) 1523 info_text = '<div>%s</div>' % text 1524 if (self._is_current_tab_name('tabTopics') and self.__last_info_text != info_text) or force_emit: 1525 self.__last_info_text = info_text 1526 self.description_signal.emit(topic.name, info_text, True if selected or deselected or force_emit else False) 1527 except Exception as _: 1528 pass
1529
1530 - def get_topic_description(self, topic_name, topic=None):
1531 text = '' 1532 if topic is None: 1533 selectedTopics = [] 1534 if topic_name and self.master_info is not None: 1535 selectedTopics = [self.master_info.getTopic("%s" % topic_name)] 1536 else: 1537 selectedTopics = self.topicsFromIndexes(self.masterTab.topicsView.selectionModel().selectedIndexes()) 1538 if len(selectedTopics) == 1: 1539 topic = selectedTopics[0] 1540 if topic is not None: 1541 ns, sep, name = topic.name.rpartition(rospy.names.SEP) 1542 text = '<font size="+1"><b><span style="color:gray;">%s%s</span><b>%s</b></font><br>' % (ns, sep, name) 1543 text += '&nbsp;<a href="topicecho://%s%s" title="Show the content of the topic"><img src=":icons/sekkyumu_topic_echo_24.png" alt="echo"></a>' % (self.mastername, topic.name) 1544 text += '&nbsp;<a href="topichz://%s%s" title="Show only the receive rate of the topic.<br>All data is sent through the network"><img src=":icons/sekkyumu_topic_hz_24.png" alt="hz"></a>' % (self.mastername, topic.name) 1545 text += '&nbsp;<a href="topichzssh://%s%s" title="Show only the receive rate of the topic.<br>Uses an SSH connection to execute `rostopic hz` on remote host."><img src=":icons/sekkyumu_topic_echo_ssh_hz_24.png" alt="sshhz"></a>' % (self.mastername, topic.name) 1546 text += '&nbsp;<a href="topicpub://%s%s" title="Start a publisher for selected topic"><img src=":icons/sekkyumu_topic_pub_24.png" alt="pub"></a>' % (self.mastername, topic.name) 1547 if topic.name in self.__republish_params: 1548 text += '&nbsp;<a href="topicrepub://%s%s" title="Start a publisher with last parameters"><img src=":icons/sekkyumu_topic_repub_24.png" alt="repub"></a>' % (self.mastername, topic.name) 1549 topic_publisher = [] 1550 topic_prefix = '/rostopic_pub%s_' % topic.name 1551 node_names = self.master_info.node_names 1552 for n in node_names: 1553 if n.startswith(topic_prefix): 1554 topic_publisher.append(n) 1555 if topic_publisher: 1556 text += '&nbsp;<a href="topicstop://%s%s"><img src=":icons/sekkyumu_topic_pub_stop_24.png" alt="stop"> [%d]</a>' % (self.mastername, topic.name, len(topic_publisher)) 1557 text += '<p>' 1558 if nm.settings().transpose_pub_sub_descr: 1559 text += self._create_html_list('Subscriber:', topic.subscriberNodes, 'NODE') 1560 text += self._create_html_list('Publisher:', topic.publisherNodes, 'NODE') 1561 else: 1562 text += self._create_html_list('Publisher:', topic.publisherNodes, 'NODE') 1563 text += self._create_html_list('Subscriber:', topic.subscriberNodes, 'NODE') 1564 text += '<b><u>Type:</u></b> %s' % self._href_from_msgtype(topic.type) 1565 text += '<dl>' 1566 try: 1567 mclass = roslib.message.get_message_class(topic.type) 1568 if mclass is not None: 1569 for f in mclass.__slots__: 1570 idx = mclass.__slots__.index(f) 1571 idtype = mclass._slot_types[idx] 1572 # base_type = roslib.msgs.base_msg_type(idtype) 1573 # primitive = "unknown" 1574 # if base_type in roslib.msgs.PRIMITIVE_TYPES: 1575 # primitive = "primitive" 1576 # else: 1577 # try: 1578 # primitive =roslib.message.get_message_class(base_type) 1579 # # primitive = "class", list_msg_class.__slots__ 1580 # except ValueError: 1581 # pass 1582 text += '%s: <span style="color:gray;">%s</span><br>' % (f, idtype) 1583 text += '<br>' 1584 constants = {} 1585 for m in dir(mclass): 1586 if not m.startswith('_'): 1587 if type(getattr(mclass, m)) in [str, int, bool, float]: 1588 constants[m] = getattr(mclass, m) 1589 if constants: 1590 text += '<b><u>Constants:</u></b><br>' 1591 for n in sorted(constants.iterkeys()): 1592 text += '%s: <span style="color:gray;">%s</span><br>' % (n, constants[n]) 1593 except ValueError: 1594 pass 1595 text += '</dl>' 1596 return text
1597
1598 - def _href_from_msgtype(self, msg_type):
1599 result = msg_type 1600 if msg_type: 1601 result = '<a href="http://ros.org/doc/api/%s.html">%s</a>' % (msg_type.replace('/', '/html/msg/'), msg_type) 1602 return result
1603
1604 - def on_service_selection_changed(self, selected, deselected, force_emit=False, service_name=''):
1605 ''' 1606 updates the Buttons, create a description and emit L{description_signal} to 1607 show the description of selected service 1608 ''' 1609 if selected is not None: 1610 # it is a workaround to avoid double updates a after click on an item 1611 self.__last_selection = time.time() 1612 if service_name and self.master_info is not None: 1613 # get service by name 1614 selectedServices = [self.master_info.getService("%s" % service_name)] 1615 if selectedServices[0] is None: 1616 return 1617 else: 1618 # get service by selected items 1619 selectedServices = self.servicesFromIndexes(self.masterTab.servicesView.selectionModel().selectedIndexes()) 1620 self.masterTab.callServiceButton.setEnabled(len(selectedServices) > 0) 1621 if not self._is_current_tab_name('tabServices'): 1622 return 1623 if len(selectedServices) == 1: 1624 service = selectedServices[0] 1625 ns, sep, name = service.name.rpartition(rospy.names.SEP) 1626 text = '<font size="+1"><b><span style="color:gray;">%s%s</span><b>%s</b></font><br>' % (ns, sep, name) 1627 text += '<a href="servicecall://%s%s" title="call service"><img src=":icons/sekkyumu_call_service_24.png" alt="call"></a>' % (self.mastername, service.name) 1628 text += '<dl>' 1629 text += '<dt><b>URI</b>: %s</dt>' % service.uri 1630 text += '<dt><b>ORG.MASTERURI</b>: %s</dt>' % service.masteruri 1631 text += self._create_html_list('Provider:', service.serviceProvider, 'NODE') 1632 if service.masteruri != self.masteruri: 1633 text += '<dt><font color="#339900"><b>synchronized</b></font></dt>' 1634 text += '</dl>' 1635 try: 1636 service_class = service.get_service_class(nm.is_local(get_hostname(service.uri))) 1637 text += '<h4>%s</h4>' % self._href_from_svrtype(service_class._type) 1638 text += '<b><u>Request:</u></b>' 1639 text += '<dl><dt>%s</dt></dl>' % service_class._request_class.__slots__ 1640 text += '<b><u>Response:</u></b>' 1641 text += '<dl><dt>%s</dt></dl>' % service_class._response_class.__slots__ 1642 except: 1643 text += '<h4><font color=red>Unknown service type</font></h4>' 1644 if service.isLocal: 1645 text += '<font color=red>Unable to communicate with service, is provider node running?</font>' 1646 else: 1647 text += '<font color=red>Try to refresh <b>all</b> hosts. Is provider node running?</font>' 1648 info_text = '<div>%s</div>' % text 1649 self._is_current_tab_name('tabServices') 1650 if (self._is_current_tab_name('tabServices') and self.__last_info_text != info_text) or force_emit: 1651 self.__last_info_text = info_text 1652 self.description_signal.emit(service.name, info_text, True if selected or deselected or force_emit else False)
1653
1654 - def _href_from_svrtype(self, srv_type):
1655 result = srv_type 1656 if srv_type: 1657 result = '<a href="http://ros.org/doc/api/%s.html">%s</a>' % (srv_type.replace('/', '/html/srv/'), srv_type) 1658 return result
1659
1660 - def on_parameter_selection_changed(self, selected, deselected):
1661 selectedParameter = self.parameterFromIndexes(self.masterTab.parameterView.selectionModel().selectedIndexes()) 1662 self.masterTab.deleteParameterButton.setEnabled(len(selectedParameter) > 0) 1663 self.masterTab.saveParameterButton.setEnabled(len(selectedParameter) > 0)
1664
1665 - def hostsFromIndexes(self, indexes, recursive=True):
1666 result = [] 1667 for index in indexes: 1668 if index.column() == 0: 1669 model_index = self.node_proxy_model.mapToSource(index) 1670 item = self.node_tree_model.itemFromIndex(model_index) 1671 if item is not None: 1672 if isinstance(item, HostItem): 1673 result.append(item) 1674 return result
1675
1676 - def groupsFromIndexes(self, indexes, recursive=True):
1677 result = [] 1678 for index in indexes: 1679 if index.column() == 0 and index.parent().isValid(): 1680 model_index = self.node_proxy_model.mapToSource(index) 1681 item = self.node_tree_model.itemFromIndex(model_index) 1682 if item is not None: 1683 if isinstance(item, GroupItem): 1684 result.append(item) 1685 return result
1686
1687 - def nodesFromIndexes(self, indexes, recursive=True):
1688 result = [] 1689 for index in indexes: 1690 if index.column() == 0: 1691 model_index = self.node_proxy_model.mapToSource(index) 1692 item = self.node_tree_model.itemFromIndex(model_index) 1693 res = self._nodesFromItems(item, recursive) 1694 for r in res: 1695 if r not in result: 1696 result.append(r) 1697 return result
1698
1699 - def _nodesFromItems(self, item, recursive):
1700 result = [] 1701 if item is not None: 1702 if isinstance(item, (GroupItem, HostItem)): 1703 if recursive: 1704 for j in range(item.rowCount()): 1705 result[len(result):] = self._nodesFromItems(item.child(j), recursive) 1706 elif isinstance(item, NodeItem): 1707 if item not in result: 1708 result.append(item) 1709 return result
1710
1711 - def topicsFromIndexes(self, indexes):
1712 result = [] 1713 for index in indexes: 1714 model_index = self.topic_proxyModel.mapToSource(index) 1715 item = self.topic_model.itemFromIndex(model_index) 1716 if item is not None: 1717 if isinstance(item, TopicItem): 1718 result.append(item.topic) 1719 elif isinstance(item, TopicGroupItem): 1720 for titem in item.get_topic_items(): 1721 result.append(titem.topic) 1722 return result
1723
1724 - def servicesFromIndexes(self, indexes):
1725 result = [] 1726 for index in indexes: 1727 model_index = self.service_proxyModel.mapToSource(index) 1728 item = self.service_model.itemFromIndex(model_index) 1729 if item is not None: 1730 if isinstance(item, ServiceItem): 1731 result.append(item.service) 1732 elif isinstance(item, ServiceGroupItem): 1733 for sitem in item.get_service_items(): 1734 result.append(sitem.service) 1735 return result
1736
1737 - def parameterFromIndexes(self, indexes):
1738 result = [] 1739 for index in indexes: 1740 model_index = self.parameter_proxyModel.mapToSource(index) 1741 item = self.parameter_model.itemFromIndex(model_index) 1742 if item is not None and isinstance(item, ParameterValueItem): 1743 result.append((item.name, item.value)) 1744 return result
1745 1746 # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1747 # %%%%%%%%%%%%% Handling of the button activities %%%%%%%% 1748 # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 1749
1750 - def on_start_clicked(self):
1751 ''' 1752 Starts the selected nodes. If for a node more then one configuration is 1753 available, the selection dialog will be show. 1754 ''' 1755 cursor = self.cursor() 1756 self.masterTab.startButton.setEnabled(False) 1757 self.setCursor(Qt.WaitCursor) 1758 try: 1759 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 1760 self.start_nodes(selectedNodes) 1761 finally: 1762 self.setCursor(cursor) 1763 self.masterTab.startButton.setEnabled(True)
1764
1765 - def on_start_alt_clicked(self):
1766 ''' 1767 Starts the selected nodes with additional options. 1768 ''' 1769 cursor = self.cursor() 1770 self.setCursor(Qt.WaitCursor) 1771 try: 1772 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 1773 self.start_nodes(selectedNodes, force=True, use_adv_cfg=True) 1774 finally: 1775 self.setCursor(cursor)
1776
1777 - def start_node(self, node, force, config, force_host=None, logging=None):
1778 1779 if node is None: 1780 raise DetailedError("Start error", 'None is not valid node name!') 1781 if node.pid is None or force: 1782 # start the node using launch configuration 1783 if config is None: 1784 raise DetailedError("Start error", 1785 'Error while start %s:\nNo configuration found!' % node.name) 1786 if isinstance(config, LaunchConfig): 1787 try: 1788 nm.starter().runNode(AdvRunCfg(node.name, config, force_host, self.masteruri, logging=logging, user=self.current_user)) 1789 except socket.error as se: 1790 rospy.logwarn("Error while start '%s': %s\n\n Start canceled!", node.name, utf8(se)) 1791 raise DetailedError("Start error", 1792 'Error while start %s\n\nStart canceled!' % node.name, 1793 '%s' % utf8(se)) 1794 return False 1795 except nm.InteractionNeededError as _: 1796 raise 1797 except (Exception, nm.StartException) as e: 1798 print type(e) 1799 print traceback.format_exc(3) 1800 rospy.logwarn("Error while start '%s': %s" % (node.name, utf8(e))) 1801 raise DetailedError("Start error", 'Error while start %s' % node.name, '%s' % utf8(e)) 1802 elif isinstance(config, (str, unicode)): 1803 # start with default configuration 1804 from multimaster_msgs_fkie.srv import Task 1805 try: 1806 nm.starter().callService(self.master_info.getService(config).uri, config, Task, [node.name]) 1807 except (Exception, nm.StartException) as e: 1808 rospy.logwarn("Error while call a service of node '%s': %s" % (node.name, utf8(e))) 1809 raise DetailedError("Service error", 1810 'Error while call a service of node %s [%s]' % (node.name, self.master_info.getService(config).uri), 1811 '%s' % utf8(e))
1812
1813 - def start_nodes(self, nodes, force=False, force_host=None, use_adv_cfg=False, check_nodelets=True):
1814 ''' 1815 Internal method to start a list with nodes 1816 @param nodes: the list with nodes to start 1817 @type nodes: C{[L{NodeItem}, ...]} 1818 @param force: force the start of the node, also if it is already started. 1819 @type force: C{bool} 1820 @param force_host: force the start of the node at specified host. 1821 @type force_host: C{str} 1822 ''' 1823 cfg_choices = dict() 1824 cfg_nodes = dict() 1825 has_launch_files = False 1826 for node in nodes: 1827 # do not start node, if it is in ingnore list and multiple nodes are selected 1828 if (node.pid is None or (node.pid is not None and force)) and not node.is_ghost: 1829 # test for duplicate nodes 1830 if node.uri is None and node.has_running: 1831 ret = MessageBox.question(self, 'Question', ''.join(['Some nodes, e.g. ', node.name, ' are already running on another host. If you start this node the other node will be terminated.\n Do you want proceed?']), buttons=MessageBox.Yes | MessageBox.No) 1832 if ret == MessageBox.No: 1833 return 1834 # determine the used configuration 1835 if node.next_start_cfg is not None: 1836 lcfg = node.next_start_cfg 1837 if node.next_start_cfg in self.launchfiles: 1838 lcfg = self.launchfiles[node.next_start_cfg] 1839 cfg_nodes[node.name] = lcfg 1840 node.launched_cfg = lcfg 1841 node.next_start_cfg = None 1842 else: 1843 choices = self._getCfgChoises(node) 1844 ch_keys = choices.keys() 1845 if ch_keys: 1846 ch_keys.sort() 1847 choises_str = utf8(ch_keys) 1848 if choises_str not in cfg_choices.keys(): 1849 choice, ok = self._getUserCfgChoice(choices, node.name) 1850 if choice is not None: 1851 cfg_choices[choises_str] = choices[choice] 1852 cfg_nodes[node.name] = choices[choice] 1853 node.launched_cfg = choices[choice] 1854 if isinstance(choices[choice], LaunchConfig): 1855 has_launch_files = True 1856 elif ok: 1857 MessageBox.warning(self, "Start error", 1858 'Error while start %s:\nNo configuration selected!' % node.name) 1859 else: 1860 break 1861 else: 1862 cfg_nodes[node.name] = cfg_choices[choises_str] 1863 node.launched_cfg = cfg_choices[choises_str] 1864 1865 # get the advanced configuration 1866 logging = None 1867 diag_canceled = False 1868 if use_adv_cfg and has_launch_files: 1869 log_params = {'Level': ('string', nm.settings().logging.get_alternatives('loglevel')), 1870 'Level (roscpp)': ('string', nm.settings().logging.get_alternatives('loglevel_roscpp')), 1871 'Level (super)': ('string', nm.settings().logging.get_alternatives('loglevel_superdebug')), 1872 'Format': ('string', nm.settings().logging.get_alternatives('console_format')) 1873 } 1874 params = {'Logging': ('dict', log_params)} 1875 dia = ParameterDialog(params) 1876 dia.setFilterVisible(False) 1877 dia.setWindowTitle('Start with parameters') 1878 dia.resize(480, 120) 1879 dia.setFocusField('Level') 1880 diag_canceled = not dia.exec_() 1881 if not diag_canceled: 1882 try: 1883 params = dia.getKeywords() 1884 nm.settings().logging.loglevel = params['Logging']['Level'] 1885 nm.settings().logging.loglevel_roscpp = params['Logging']['Level (roscpp)'] 1886 nm.settings().logging.loglevel_superdebug = params['Logging']['Level (super)'] 1887 nm.settings().logging.console_format = params['Logging']['Format'] 1888 nm.settings().store_logging() 1889 logging = nm.settings().logging 1890 except Exception, e: 1891 diag_canceled = True 1892 MessageBox.warning(self, "Get advanced start parameter", 1893 'Error while parse parameter', 1894 utf8(e)) 1895 if not diag_canceled: 1896 # check for nodelets 1897 if check_nodelets: 1898 self._check_for_nodelets(nodes) 1899 # put into the queue and start 1900 for node in nodes: 1901 if node.name in cfg_nodes: 1902 self._progress_queue.add2queue(utf8(uuid.uuid4()), 1903 ''.join(['start ', node.node_info.name]), 1904 self.start_node, 1905 (node.node_info, force, cfg_nodes[node.node_info.name], force_host, logging)) 1906 self._start_queue(self._progress_queue)
1907
1908 - def _check_for_nodelets(self, nodes):
1909 self._restart_nodelets = {} 1910 nodenames = [n.name for n in nodes] 1911 nodelet_mngr = '' 1912 nlmngr = '' 1913 for node in nodes: 1914 try: 1915 cfg_name = node.launched_cfg 1916 if isinstance(node.launched_cfg, LaunchConfig): 1917 cfg_name = node.launched_cfg.Filename 1918 nodelets = self._get_nodelets(node.name, cfg_name) 1919 if nodelets: 1920 nodelets = self._get_nodelets(node.name, cfg_name) 1921 r_nn = [] 1922 for nn in nodelets: 1923 if nn not in nodenames: 1924 r_nn.append(nn) 1925 if cfg_name not in self._restart_nodelets: 1926 self._restart_nodelets[cfg_name] = [] 1927 self._restart_nodelets[cfg_name].append(nn) 1928 if self._restart_nodelets: 1929 nlmngr = node.name 1930 else: 1931 nodelet_mngr = self._get_nodelet_manager(node.name, cfg_name) 1932 if nodelet_mngr: 1933 if nodelet_mngr not in nodenames: 1934 if cfg_name not in self._restart_nodelets: 1935 self._restart_nodelets[cfg_name] = [] 1936 self._restart_nodelets[cfg_name].append(nodelet_mngr) 1937 nodelets = self._get_nodelets(nodelet_mngr, cfg_name) 1938 r_nn = [] 1939 for nn in nodelets: 1940 if nn not in nodenames: 1941 r_nn.append(nn) 1942 self._restart_nodelets[cfg_name].append(nn) 1943 nodelet_mngr = nodelet_mngr 1944 except Exception as err: 1945 rospy.logwarn("Error while test for nodelets: %s" % utf8(err)) 1946 if nm.settings().check_for_nodelets_at_start: 1947 if nodelet_mngr and nodelet_mngr not in nodenames: 1948 self.message_frame.show_question(MessageFrame.TYPE_NODELET, "Nodelet manager '%s' not in current list. (Re)Start nodelet manager and all nodelets?" % nodelet_mngr, MessageData(self._restart_nodelets)) 1949 elif self._restart_nodelets: 1950 self.message_frame.show_question(MessageFrame.TYPE_NODELET, "Not all nodelets of manager '%s' are in the start list. (Re)Start these?" % nlmngr, MessageData(self._restart_nodelets))
1951
1952 - def start_nodes_by_name(self, nodes, cfg, force=False, check_nodelets=True):
1953 ''' 1954 Start nodes given in a list by their names. 1955 @param nodes: a list with full node names 1956 @type nodes: C{[str]} 1957 ''' 1958 result = [] 1959 if self.master_info is not None: 1960 for n in nodes: 1961 node_items = self.getNode(n) 1962 if node_items: 1963 node_item = node_items[0] 1964 # node_item.addConfig(cfg) 1965 if isinstance(cfg, tuple): 1966 node_item.next_start_cfg = cfg[0] 1967 else: 1968 node_item.next_start_cfg = cfg 1969 elif cfg: 1970 node_info = NodeInfo(n, self.masteruri) 1971 node_item = NodeItem(node_info) 1972 # node_item.addConfig(cfg) 1973 if isinstance(cfg, tuple): 1974 node_item.next_start_cfg = cfg[0] 1975 else: 1976 node_item.next_start_cfg = cfg 1977 if node_item is not None: 1978 result.append(node_item) 1979 self.start_nodes(result, force, check_nodelets=check_nodelets)
1980
1981 - def start_nodes_after_load_cfg(self, cfg_name, nodes, force=False):
1982 ''' 1983 Start nodes after the given configuration is loaded and applied to the model. 1984 :param cfg_name: the name of the cnofiguration 1985 :type cfg_name: str 1986 :param nodes: the list of node names 1987 :type nodes: list of strings 1988 ''' 1989 if cfg_name not in self._start_nodes_after_load_cfg: 1990 self._start_nodes_after_load_cfg[cfg_name] = set(nodes) 1991 else: 1992 self._start_nodes_after_load_cfg[cfg_name].update(set(nodes))
1993
1995 ''' 1996 Clears the list with nodes which should be startet after a launch file is loaded. 1997 ''' 1998 self._start_nodes_after_load_cfg = dict()
1999
2000 - def on_force_start_nodes(self, reset_global_param=False):
2001 ''' 2002 Starts the selected nodes (also if it already running). If for a node more then one configuration is 2003 available, the selection dialog will be show. 2004 ''' 2005 cursor = self.cursor() 2006 self.masterTab.startButton.setEnabled(False) 2007 self.setCursor(Qt.WaitCursor) 2008 try: 2009 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 2010 self.stop_nodes(selectedNodes) 2011 if reset_global_param: 2012 # reset config to load global parameter 2013 for node in selectedNodes: 2014 for cfg in node.cfgs: 2015 if cfg in self.launchfiles: 2016 self.reload_global_parameter_at_next_start(cfg) 2017 self.start_nodes(selectedNodes, True) 2018 finally: 2019 self.setCursor(cursor) 2020 self.masterTab.startButton.setEnabled(True)
2021
2022 - def on_start_nodes_at_host(self):
2023 ''' 2024 Starts the selected nodes on an another host. 2025 ''' 2026 cursor = self.cursor() 2027 self.masterTab.startButton.setEnabled(False) 2028 params = {'Host': ('string', 'localhost')} 2029 dia = ParameterDialog(params) 2030 dia.setFilterVisible(False) 2031 dia.setWindowTitle('Start node on...') 2032 dia.resize(350, 120) 2033 dia.setFocusField('host') 2034 if dia.exec_(): 2035 try: 2036 params = dia.getKeywords() 2037 host = params['Host'] 2038 self.setCursor(Qt.WaitCursor) 2039 try: 2040 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 2041 self.start_nodes(selectedNodes, True, host) 2042 finally: 2043 self.setCursor(cursor) 2044 except Exception, e: 2045 MessageBox.warning(self, "Start error", 2046 'Error while parse parameter', 2047 utf8(e)) 2048 self.masterTab.startButton.setEnabled(True)
2049
2050 - def _getDefaultCfgChoises(self, node):
2051 result = {} 2052 for c in node.cfgs: 2053 if isinstance(c, tuple): 2054 result[' '.join(['[default]', c[0]])] = roslib.names.ns_join(c[0], 'run') 2055 return result
2056
2057 - def _getCfgChoises(self, node, ignore_defaults=False):
2058 result = {} 2059 for c in node.cfgs: 2060 if c: 2061 if not isinstance(c, tuple): 2062 launch = self.launchfiles[c] 2063 result[''.join([utf8(launch.LaunchName), ' [', utf8(launch.PackageName), ']'])] = self.launchfiles[c] 2064 elif not ignore_defaults: 2065 result[' '.join(['[default]', c[0]])] = roslib.names.ns_join(c[0], 'run') 2066 return result
2067
2068 - def _getUserCfgChoice(self, choices, nodename):
2069 value = None 2070 ok = False 2071 # Open selection 2072 if len(choices) == 1: 2073 value = choices.keys()[0] 2074 ok = True 2075 elif len(choices) > 0: 2076 items, ok = SelectDialog.getValue('Configuration selection', 'Select configuration to launch <b>%s</b>' % nodename, choices.keys(), True) 2077 if items: 2078 value = items[0] 2079 return value, ok
2080
2081 - def on_stop_clicked(self):
2082 ''' 2083 Stops the selected and running nodes. If the node can't be stopped using his 2084 RPC interface, it will be unregistered from the ROS master using the masters 2085 RPC interface. 2086 ''' 2087 key_mod = QApplication.keyboardModifiers() 2088 if (key_mod & Qt.ShiftModifier or key_mod & Qt.ControlModifier): 2089 self.masterTab.stopButton.showMenu() 2090 else: 2091 cursor = self.cursor() 2092 self.setCursor(Qt.WaitCursor) 2093 try: 2094 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 2095 self.stop_nodes(selectedNodes) 2096 finally: 2097 self.setCursor(cursor)
2098
2099 - def stop_node(self, node, force=False):
2100 if node is not None and node.uri is not None and (not self._is_in_ignore_list(node.name) or force): 2101 try: 2102 rospy.loginfo("Stop node '%s'[%s]", utf8(node.name), utf8(node.uri)) 2103 nm.filewatcher().rem_binary(node.name) 2104 # 'print "STOP set timeout", node 2105 socket.setdefaulttimeout(10) 2106 # 'print "STOP create xmlrpc", node 2107 p = xmlrpclib.ServerProxy(node.uri) 2108 # 'print "STOP send stop", node 2109 p.shutdown(rospy.get_name(), ''.join(['[node manager] request from ', self.mastername])) 2110 # 'print "STOP stop finished", node 2111 except Exception, e: 2112 rospy.logwarn("Error while stop node '%s': %s", utf8(node.name), utf8(e)) 2113 if utf8(e).find(' 111') == 1: 2114 raise DetailedError("Stop error", 2115 ''.join(['Error while stop node ', node.name]), 2116 utf8(e)) 2117 finally: 2118 socket.setdefaulttimeout(None) 2119 elif isinstance(node, NodeItem) and node.is_ghost: 2120 # since for ghost nodes no info is available, emit a signal to handle the 2121 # stop message in other master_view_proxy 2122 self.stop_nodes_signal.emit(node.masteruri, [node.name]) 2123 return True
2124
2125 - def stop_nodes(self, nodes, force=False):
2126 ''' 2127 Internal method to stop a list with nodes 2128 @param nodes: the list with nodes to stop 2129 @type nodes: C{[U{master_discovery_fkie.NodeInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.NodeInfo>}, ...]} 2130 ''' 2131 # put into the queue and start the que handling 2132 for node in nodes: 2133 self._progress_queue.add2queue(utf8(uuid.uuid4()), 2134 'stop %s' % node.name, 2135 self.stop_node, 2136 (node, (len(nodes) == 1) or force)) 2137 self._start_queue(self._progress_queue)
2138
2139 - def stop_nodes_by_name(self, nodes, force=False, ignore=[]):
2140 ''' 2141 Stop nodes given in a list by their names. 2142 @param nodes: a list with full node names 2143 @type nodes: C{[str]} 2144 ''' 2145 result = [] 2146 if self.master_info is not None: 2147 for n in nodes: 2148 if n not in ignore: 2149 node = self.master_info.getNode(n) 2150 if node is not None: 2151 result.append(node) 2152 self.stop_nodes(result, force)
2153
2154 - def kill_node(self, node, force=False):
2155 if node is not None and node.uri is not None and (not self._is_in_ignore_list(node.name) or force): 2156 pid = node.pid 2157 if pid is None: 2158 # try to get the process id of the node 2159 try: 2160 socket.setdefaulttimeout(10) 2161 rpc_node = xmlrpclib.ServerProxy(node.uri) 2162 _, _, pid = rpc_node.getPid(rospy.get_name()) # _:=code, msg 2163 except: 2164 pass 2165 finally: 2166 socket.setdefaulttimeout(None) 2167 # kill the node 2168 if pid is not None: 2169 try: 2170 self._progress_queue.add2queue(utf8(uuid.uuid4()), 2171 ''.join(['kill ', node.name, '(', utf8(pid), ')']), 2172 nm.starter().kill, 2173 (self.getHostFromNode(node), pid, False, self.current_user)) 2174 self._start_queue(self._progress_queue) 2175 except Exception as e: 2176 rospy.logwarn("Error while kill the node %s: %s", utf8(node.name), utf8(e)) 2177 raise DetailedError("Kill error", 2178 ''.join(['Error while kill the node ', node.name]), 2179 utf8(e)) 2180 return True
2181
2182 - def killall_roscore(self):
2183 host = get_hostname(self.masteruri) 2184 if host: 2185 try: 2186 if not nm.is_local(self.mastername): 2187 self._progress_queue.add2queue(utf8(uuid.uuid4()), 2188 'killall roscore on %s' % host, 2189 nm.starter().killall_roscore, 2190 (host, self.current_user)) 2191 self._start_queue(self._progress_queue) 2192 else: 2193 nm.starter().killall_roscore(host, self.current_user) 2194 except Exception as e: 2195 rospy.logwarn("Error while killall roscore on %s: %s" % (host, utf8(e))) 2196 raise DetailedError("Killall roscore error", 2197 'Error while killall roscore', 2198 '%s' % utf8(e)) 2199 return True
2200
2201 - def on_kill_nodes(self):
2202 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 2203 2204 # put into the queue and start the que handling 2205 for node in selectedNodes: 2206 self._progress_queue.add2queue(utf8(uuid.uuid4()), 2207 ''.join(['kill ', node.name]), 2208 self.kill_node, 2209 (node, (len(selectedNodes) == 1))) 2210 self._start_queue(self._progress_queue)
2211
2212 - def unregister_node(self, node, force=False):
2213 if node is not None and node.uri is not None and (not self._is_in_ignore_list(node.name) or force): 2214 # stop the node? 2215 # try: 2216 # p = xmlrpclib.ServerProxy(node.uri) 2217 # p.shutdown(rospy.get_name(), ''.join(['[node manager] request from ', self.hostname])) 2218 # except Exception, e: 2219 # rospy.logwarn("Error while stop node '%s': %s", utf8(node.name), utf8(e)) 2220 # self.masterTab.stopButton.setEnabled(False) 2221 # unregister all entries of the node from ROS master 2222 try: 2223 socket.setdefaulttimeout(10) 2224 master = xmlrpclib.ServerProxy(node.masteruri) 2225 master_multi = xmlrpclib.MultiCall(master) 2226 # master_multi.deleteParam(node.name, node.name) 2227 for p in node.published: 2228 rospy.loginfo("unregister publisher '%s' [%s] from ROS master: %s", p, node.name, node.masteruri) 2229 master_multi.unregisterPublisher(node.name, p, node.uri) 2230 for t in node.subscribed: 2231 rospy.loginfo("unregister subscriber '%s' [%s] from ROS master: %s", t, node.name, node.masteruri) 2232 master_multi.unregisterSubscriber(node.name, t, node.uri) 2233 if self.master_state is not None: 2234 for s in node.services: 2235 rospy.loginfo("unregister service '%s' [%s] from ROS master: %s", s, node.name, node.masteruri) 2236 service = self.master_info.getService(s) 2237 if not (service is None): 2238 master_multi.unregisterService(node.name, s, service.uri) 2239 r = master_multi() 2240 for code, msg, _ in r: 2241 if code != 1: 2242 rospy.logwarn("unregistration failed: %s", msg) 2243 except Exception, e: 2244 rospy.logwarn("Error while unregister node %s: %s", utf8(node.name), utf8(e)) 2245 raise DetailedError("Unregister error", 2246 ''.join(['Error while Unregister node ', node.name]), 2247 utf8(e)) 2248 finally: 2249 socket.setdefaulttimeout(None) 2250 return True
2251
2252 - def on_unregister_nodes(self):
2253 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 2254 # put into the queue and start the que handling 2255 for node in selectedNodes: 2256 if node.pid is None or len(selectedNodes) == 1: 2257 self._progress_queue.add2queue(utf8(uuid.uuid4()), 2258 ''.join(['unregister node ', node.name]), 2259 self.unregister_node, 2260 (node, (len(selectedNodes) == 1))) 2261 self._start_queue(self._progress_queue)
2262
2263 - def on_stop_context_toggled(self, state):
2264 menu = QMenu(self) 2265 self.killAct = QAction("&Kill Node", self, shortcut=QKeySequence.New, statusTip="Kill selected node", triggered=self.kill_nodes) 2266 self.unregAct = QAction("&Unregister Nodes...", self, shortcut=QKeySequence.Open, statusTip="Open an existing file", triggered=self.unreg_nodes) 2267 menu.addAction(self.killAct) 2268 menu.addAction(self.unregAct) 2269 menu.exec_(self.masterTab.stopContextButton.pos())
2270
2271 - def getHostFromNode(self, node):
2272 ''' 2273 If the node is running the host the node URI will be returned. Otherwise 2274 tries to get the host from the launch configuration. If the configuration 2275 contains no machine assignment for this node the host of the ROS master URI 2276 will be used. 2277 @param node: 2278 @type node: U{master_discovery_fkie.NodeInfo<http://docs.ros.org/kinetic/api/master_discovery_fkie/html/modules.html#master_discovery_fkie.master_info.NodeInfo>} 2279 ''' 2280 if node.uri is not None: 2281 return get_hostname(node.uri) 2282 # try to get it from the configuration 2283 for c in node.cfgs: 2284 if not isinstance(c, tuple): 2285 launch_config = self.__configs[c] 2286 item = launch_config.getNode(node.name) 2287 if item is not None and item.machine_name and not item.machine_name == 'localhost': 2288 return launch_config.Roscfg.machines[item.machine_name].address 2289 # return the host of the assigned ROS master 2290 return get_hostname(node.masteruri)
2291
2292 - def on_io_clicked(self):
2293 ''' 2294 Shows IO of the selected nodes. 2295 ''' 2296 # key_mod = QApplication.keyboardModifiers() 2297 # use_mod = key_mod & Qt.ShiftModifier or key_mod & Qt.ControlModifier 2298 # if (key_mod & Qt.ShiftModifier or key_mod & Qt.ControlModifier): 2299 # self.masterTab.ioButton.showMenu() 2300 # else: 2301 cursor = self.cursor() 2302 self.setCursor(Qt.WaitCursor) 2303 try: 2304 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 2305 if selectedNodes: 2306 ret = True 2307 if len(selectedNodes) > 5: 2308 ret = MessageBox.question(self, "Show IO", "You are going to open the IO of " + utf8(len(selectedNodes)) + " nodes at once\nContinue?", buttons=MessageBox.Ok | MessageBox.Cancel) 2309 ret = (ret == MessageBox.Ok) 2310 if ret: 2311 queue = self._progress_queue_prio 2312 # we use normal queue, if there are not a lot of processes 2313 if self._progress_queue.count() < 5: 2314 queue = self._progress_queue 2315 for node in selectedNodes: 2316 queue.add2queue(utf8(uuid.uuid4()), 2317 'show IO of %s' % node.name, 2318 nm.screen().openScreen, 2319 (node.name, self.getHostFromNode(node), False, self.current_user)) 2320 self._start_queue(queue) 2321 else: 2322 self.on_show_all_screens() 2323 finally: 2324 self.setCursor(cursor)
2325
2326 - def _on_no_screen_error(self, node, host):
2327 msg = nm.NoScreenOpenLogRequest(node, host).msg() 2328 rospy.logwarn("%s" % msg) 2329 muri = nm.nameres().masterurisbyaddr(host) 2330 if muri: 2331 nodes = self.node_tree_model.getNode(node, muri[0]) 2332 for node in nodes: 2333 node.has_screen = False 2334 if nm.settings().show_noscreen_error: 2335 self.info_frame.show_info(MessageFrame.TYPE_NOSCREEN, 'No screens found! See log for details!<br>The following nodes are affected:', MessageData('', [node.name]))
2336
2337 - def on_kill_screens(self):
2338 ''' 2339 Kills selected screens, if some available. 2340 ''' 2341 cursor = self.cursor() 2342 self.setCursor(Qt.WaitCursor) 2343 try: 2344 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 2345 for node in selectedNodes: 2346 self._progress_queue.add2queue(utf8(uuid.uuid4()), 2347 ''.join(['kill screen of ', node.name]), 2348 nm.screen().killScreens, 2349 (node.name, self.getHostFromNode(node), False, self.current_user)) 2350 self._start_queue(self._progress_queue) 2351 finally: 2352 self.setCursor(cursor)
2353
2354 - def on_show_all_screens(self):
2355 ''' 2356 Shows all available screens. 2357 ''' 2358 cursor = self.cursor() 2359 self.setCursor(Qt.WaitCursor) 2360 try: 2361 host = get_hostname(self.masteruri) 2362 sel_screen = [] 2363 try: 2364 screens = nm.screen().getActiveScreens(host, auto_pw_request=True, user=self.current_user) 2365 sel_screen, _ = SelectDialog.getValue('Open screen', '', screens, False, False, self) # _:=ok 2366 except Exception, e: 2367 rospy.logwarn("Error while get screen list: %s", utf8(e)) 2368 MessageBox.warning(self, "Screen list error", 2369 ''.join(['Error while get screen list from ', host]), 2370 utf8(e)) 2371 for screen in sel_screen: 2372 try: 2373 if not nm.screen().openScreenTerminal(host, screen, screen, self.current_user): 2374 pass 2375 except Exception, e: 2376 rospy.logwarn("Error while show IO for %s: %s", utf8(screen), utf8(e)) 2377 MessageBox.warning(self, "Show IO error", 2378 ''.join(['Error while show IO ', screen, ' on ', host]), 2379 utf8(e)) 2380 finally: 2381 self.setCursor(cursor)
2382
2383 - def on_log_clicked(self):
2384 ''' 2385 Shows log files of the selected nodes. 2386 ''' 2387 try: 2388 only_screen = True 2389 key_mod = QApplication.keyboardModifiers() 2390 if (key_mod & Qt.ShiftModifier or key_mod & Qt.ControlModifier): 2391 only_screen = False 2392 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 2393 ret = True 2394 if len(selectedNodes) > 5: 2395 ret = MessageBox.question(self, "Show Log", "You are going to open the logs of " + utf8(len(selectedNodes)) + " nodes at once\nContinue?", buttons=MessageBox.Ok | MessageBox.Cancel) 2396 ret = (ret == MessageBox.Ok) 2397 if ret: 2398 for node in selectedNodes: 2399 self._progress_queue_prio.add2queue(utf8(uuid.uuid4()), 2400 ''.join(['show log of ', node.name]), 2401 nm.starter().openLog, 2402 (node.name, self.getHostFromNode(node), self.current_user, only_screen)) 2403 self._start_queue(self._progress_queue_prio) 2404 except Exception, e: 2405 print traceback.format_exc(1) 2406 rospy.logwarn("Error while show log: %s", utf8(e)) 2407 MessageBox.warning(self, "Show log error", 2408 'Error while show Log', 2409 utf8(e))
2410
2411 - def on_log_path_copy(self):
2412 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 2413 nodenames = [] 2414 for n in selectedNodes: 2415 nodenames.append(n.name) 2416 try: 2417 host = get_hostname(self.masteruri) 2418 path_on_host = nm.starter().get_log_path(host, nodenames, True) 2419 QApplication.clipboard().setText(''.join([getpass.getuser() if self.is_local else self.current_user, '@', host, ':', path_on_host])) 2420 except Exception as e: 2421 MessageBox.warning(self, "Get log path", 2422 'Error while get log path', 2423 utf8(e))
2424 # self._progress_queue.add2queue(utf8(uuid.uuid4()), 2425 # 'Get log path', 2426 # nm.starter().get_log_path, 2427 # (get_hostname(self.masteruri), nodenames)) 2428 # self._start_queue(self._progress_queue) 2429 2430 # def on_log_show_selected(self): 2431 # try: 2432 # nm.screen().LOG_PATH. 2433 # screens = nm.screen().getActiveScreens(host, auto_pw_request=True) 2434 # sel_screen, ok = SelectDialog.getValue('Open log', '', screens, False, self) 2435 # except Exception, e: 2436 # rospy.logwarn("Error while get screen list: %s", utf8(e)) 2437 # MessageBox.warning(self, "Screen list error", 2438 # ''.join(['Error while get screen list from ', host]), 2439 # utf8(e)) 2440
2441 - def on_log_delete_clicked(self):
2442 ''' 2443 Deletes log files of the selected nodes. 2444 ''' 2445 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 2446 for node in selectedNodes: 2447 self._progress_queue_prio.add2queue(utf8(uuid.uuid4()), 2448 ''.join(['delete Log of ', node.name]), 2449 nm.starter().deleteLog, 2450 (node.name, self.getHostFromNode(node), False, self.current_user)) 2451 self._start_queue(self._progress_queue_prio)
2452
2453 - def on_dynamic_config_clicked(self):
2454 ''' 2455 Opens the dynamic configuration dialogs for selected nodes. 2456 ''' 2457 if self.master_info is not None: 2458 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 2459 for n in selectedNodes: 2460 try: 2461 nodes = sorted([srv_name[:-len('/set_parameters')] for srv_name, srv in self.master_info.services.items() if (srv_name.endswith('/set_parameters') and n.name in srv.serviceProvider)]) 2462 items = [] 2463 if len(nodes) == 1: 2464 items = nodes 2465 elif len(nodes) > 1: 2466 items, _ = SelectDialog.getValue('Dynamic configuration selection', '', [i for i in nodes]) 2467 if items is None: 2468 items = [] 2469 if len(items) > 3: 2470 ret = MessageBox.question(self, 'Start dynamic reconfigure', 'It will starts %s dynamic reconfigure nodes?\n\n Are you sure?' % utf8(len(items)), buttons=MessageBox.Yes | MessageBox.No) 2471 if ret != MessageBox.Yes: 2472 return 2473 for node in items: 2474 env = dict(os.environ) 2475 env["ROS_MASTER_URI"] = utf8(self.master_info.masteruri) 2476 rospy.loginfo("Start dynamic reconfiguration for '%s'" % node) 2477 _ = SupervisedPopen(['rosrun', 'node_manager_fkie', 'dynamic_reconfigure', node, '__ns:=dynamic_reconfigure'], env=env, object_id=node, description='Start dynamic reconfiguration for %s failed' % node) 2478 except Exception, e: 2479 rospy.logwarn("Start dynamic reconfiguration for '%s' failed: %s" % (n.name, utf8(e))) 2480 MessageBox.warning(self, "Start dynamic reconfiguration error", 2481 'Start dynamic reconfiguration for %s failed!' % n.name, 2482 utf8(e))
2483
2484 - def on_edit_config_clicked(self):
2485 ''' 2486 ''' 2487 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 2488 for node in selectedNodes: 2489 choices = self._getCfgChoises(node, True) 2490 choice, ok = self._getUserCfgChoice(choices, node.name) 2491 config = choices[choice] if choices and choice else '' 2492 if ok and isinstance(config, LaunchConfig): 2493 # get the file, which include the node and the main configuration file 2494 node_cfg = config.getNode(node.name) 2495 files = [config.Filename] 2496 if node_cfg.filename not in files: 2497 files.append(node_cfg.filename) 2498 self.request_xml_editor.emit(files, ''.join(['name="', os.path.basename(node.name), '"']))
2499
2500 - def on_edit_rosparam_clicked(self):
2501 ''' 2502 ''' 2503 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 2504 for node in selectedNodes: 2505 # set the parameter in the ROS parameter server 2506 try: 2507 inputDia = MasterParameterDialog(node.masteruri if node.masteruri is not None else self.masteruri, ''.join([node.name, roslib.names.SEP]), parent=self) 2508 inputDia.setWindowTitle(' - '.join([os.path.basename(node.name), "parameter"])) 2509 if node.has_launch_cfgs(node.cfgs): 2510 inputDia.add_warning("The changes may not have any effect, because the launch file was also loaded as not 'default' and the parameter in the launch file will be reloaded on start of the ROS node.") 2511 inputDia.show() 2512 except: 2513 rospy.logwarn("Error on retrieve parameter for %s: %s", utf8(node.name), traceback.format_exc(1))
2514
2515 - def on_save_clicked(self):
2516 # save the profile 2517 self.save_profile_signal.emit('')
2518
2519 - def on_close_clicked(self):
2520 ''' 2521 Opens a dialog to select configurations to close or stop all nodes 2522 (with roscore) or shutdown the host. 2523 ''' 2524 choices = dict() 2525 2526 for path, _ in self.__configs.items(): 2527 if isinstance(path, tuple): 2528 if path[2] == self.masteruri: 2529 choices['DEFAULT CFG: %s' % path[0]] = path 2530 else: 2531 package = utf8(package_name(os.path.dirname(path))[0]) 2532 choices['%s [%s]' % (os.path.basename(path), package)] = path 2533 cfg_items = choices.keys() 2534 cfg_items.sort() 2535 res = SelectDialog.getValue('Close/Stop/Shutdown', '', 2536 cfg_items, False, False, 2537 self, checkitem1='stop ROS', 2538 checkitem2='shutdown host') 2539 cfgs, _, stop_nodes, shutdown = res[0], res[1], res[2], res[3] 2540 # close configurations 2541 for config in cfgs: 2542 self._close_cfg(choices[config]) 2543 if stop_nodes: 2544 self._on_stop_kill_roscore = True 2545 # stop all nodes, system nodes at the end 2546 ignore_nodes = [rospy.get_name(), '/master_discovery', '/rosout'] 2547 self.stop_nodes_by_name(self.getRunningNodesIfLocal(), True, ignore_nodes) 2548 if shutdown: 2549 self.poweroff() 2550 else: 2551 self.stop_nodes_by_name(['/master_discovery'], True) 2552 self.stop_nodes_by_name(['/node_manager'], True) 2553 elif shutdown: 2554 self.poweroff() 2555 self.updateButtons() 2556 self.update_robot_icon()
2557
2558 - def poweroff(self):
2559 try: 2560 if nm.is_local(self.mastername): 2561 ret = MessageBox.warning(self, "ROS Node Manager", 2562 "Do you really want to shutdown localhost?", 2563 buttons=MessageBox.Ok | MessageBox.Cancel) 2564 if ret == MessageBox.Cancel: 2565 return 2566 self._on_stop_poweroff = True 2567 # on shutdown stop only the /master_dsicovery node to remove it from lists 2568 # in other remote nodes 2569 self.stop_nodes_by_name(['/master_discovery'], True) 2570 self._progress_queue.add2queue(utf8(uuid.uuid4()), 2571 'poweroff `%s`' % self.mastername, 2572 nm.starter().poweroff, 2573 ('%s' % self.mastername,)) 2574 self._start_queue(self._progress_queue) 2575 except (Exception, nm.StartException), emsg: 2576 rospy.logwarn("Error while poweroff %s: %s", self.mastername, utf8(emsg)) 2577 MessageBox.warning(self, "Run error", 2578 'Error while poweroff %s' % self.mastername, 2579 '%s' % utf8(emsg))
2580
2581 - def _close_cfg(self, cfg):
2582 try: 2583 self.removeConfigFromModel(cfg) 2584 if isinstance(cfg, tuple): 2585 if self.master_info is not None: 2586 # close default configuration: stop the default_cfg node 2587 node = self.master_info.getNode(cfg[0]) 2588 if node is not None: 2589 self.stop_nodes([node]) 2590 else: 2591 # remove from name resolution 2592 try: 2593 for _, machine in self.__configs[cfg].Roscfg.machines.items(): # _:=name 2594 if machine.name: 2595 nm.nameres().remove_info(machine.name, machine.address) 2596 except: 2597 pass 2598 del self.__configs[cfg] 2599 except: 2600 print traceback.format_exc(1) 2601 pass
2602
2603 - def on_topic_echo_clicked(self, topics=[]):
2604 ''' 2605 Shows the output of the topic in a terminal. 2606 ''' 2607 self._show_topic_output(False, use_ssh=False, topics=topics)
2608
2609 - def on_topic_hz_clicked(self):
2610 ''' 2611 Shows the hz of the topic in a terminal. 2612 ''' 2613 self._show_topic_output(True)
2614
2615 - def on_topic_hz_ssh_clicked(self):
2616 ''' 2617 Shows the hz of the topic using ssh. 2618 ''' 2619 self._show_topic_output(True, use_ssh=True)
2620
2621 - def on_topic_pub_clicked(self):
2622 selectedTopics = self.topicsFromIndexes(self.masterTab.topicsView.selectionModel().selectedIndexes()) 2623 if len(selectedTopics) > 0: 2624 for topic in selectedTopics: 2625 if not self._start_publisher(topic.name, topic.type): 2626 break 2627 else: # create a new topic 2628 # fill the input fields 2629 # determine the list all available message types 2630 root_paths = [os.path.normpath(p) for p in os.getenv("ROS_PACKAGE_PATH").split(':')] 2631 packages = {} 2632 msg_types = [] 2633 for p in root_paths: 2634 ret = get_packages(p) 2635 packages = dict(ret.items() + packages.items()) 2636 for (p, direc) in packages.items(): 2637 import rosmsg 2638 for f in rosmsg._list_types('%s/msg' % direc, 'msg', rosmsg.MODE_MSG): 2639 msg_types.append("%s/%s" % (p, f)) 2640 msg_types.sort() 2641 fields = {'Type': ('string', msg_types), 'Name': ('string', [''])} 2642 2643 # create a dialog 2644 dia = ParameterDialog(fields, parent=self) 2645 dia.setWindowTitle('Publish to topic') 2646 dia.setFilterVisible(False) 2647 dia.resize(300, 95) 2648 dia.setFocusField('Name') 2649 if dia.exec_(): 2650 params = dia.getKeywords() 2651 try: 2652 if params['Name'] and params['Type']: 2653 try: 2654 self._start_publisher(params['Name'], params['Type']) 2655 except Exception, e: 2656 print traceback.format_exc(1) 2657 rospy.logwarn("Publish topic '%s' failed: %s", utf8(params['Name']), utf8(e)) 2658 MessageBox.warning(self, "Publish topic error", 2659 ''.join(['Publish topic ', params['Name'], ' failed!']), 2660 utf8(e)) 2661 else: 2662 MessageBox.warning(self, "Invalid name or type", 2663 "Can't publish to topic '%s' with type '%s'!" % (params['Name'], params['Type'])) 2664 except (KeyError, ValueError), e: 2665 MessageBox.warning(self, "Warning", 2666 'Error while add a parameter to the ROS parameter server', 2667 utf8(e))
2668
2669 - def start_publisher(self, topic_name, republish=False):
2670 ''' 2671 Starts a publisher to given topic. 2672 ''' 2673 if self.master_info is not None: 2674 topic = self.master_info.getTopic("%s" % topic_name) 2675 if topic is not None: 2676 self._start_publisher(topic.name, topic.type, republish) 2677 else: 2678 rospy.logwarn("Error while start publisher, topic not found: %s" % topic_name)
2679
2680 - def _start_publisher(self, topic_name, topic_type, republish=False):
2681 try: 2682 topic_name = roslib.names.ns_join(roslib.names.SEP, topic_name) 2683 mclass = roslib.message.get_message_class(topic_type) 2684 if mclass is None: 2685 MessageBox.warning(self, "Publish error", 2686 'Error while publish to %s' % topic_name, 2687 ''.join(['invalid message type: ', topic_type, '.\nIf this is a valid message type, perhaps you need to run "rosmake"'])) 2688 return 2689 slots = mclass.__slots__ 2690 types = mclass._slot_types 2691 default_topic_values = {} 2692 rate_values = ['once', 'latch', '1'] 2693 if republish and topic_name in self.__republish_params: 2694 default_topic_values = self.__republish_params[topic_name][topic_type] 2695 rate_values = self.__republish_params[topic_name]['! Publish rate'] 2696 args = ServiceDialog._params_from_slots(slots, types, default_topic_values) 2697 p = {'! Publish rate': ('string', rate_values), topic_type: ('dict', args)} 2698 dia = ParameterDialog(p) 2699 dia.setWindowTitle(''.join(['Publish to ', topic_name])) 2700 dia.showLoadSaveButtons() 2701 dia.resize(450, 300) 2702 dia.setFocusField('! Publish rate') 2703 2704 if dia.exec_(): 2705 params = dia.getKeywords() 2706 # store params for republish 2707 self.__republish_params[topic_name] = params 2708 rate = params['! Publish rate'] 2709 opt_str = '' 2710 opt_name_suf = '__latch_' 2711 if rate == 'latch': 2712 opt_str = '' 2713 elif rate == 'once' or rate == '-1': 2714 opt_str = '--once' 2715 opt_name_suf = '__once_' 2716 else: 2717 try: 2718 i = 0 2719 try: 2720 i = int(rate) 2721 except: 2722 i = float(rate) 2723 if i > 0: 2724 opt_str = ''.join(['-r ', rate]) 2725 opt_name_suf = '__%sHz_' % (utf8(rate).replace('.', '_')) 2726 except: 2727 pass 2728 # remove empty lists 2729 topic_params = dict() 2730 if topic_type in params: 2731 topic_params = self._rem_empty_lists(params[topic_type]) 2732 pub_cmd = ' '.join(['pub', topic_name, topic_type, '"', str(topic_params), '"', opt_str]) 2733 self._progress_queue.add2queue(utf8(uuid.uuid4()), 2734 'start publisher for %s' % topic_name, 2735 nm.starter().runNodeWithoutConfig, 2736 (nm.nameres().address(self.masteruri), 'rostopic', 'rostopic', 'rostopic_pub%s%s%s' % (topic_name, opt_name_suf, str(rospy.Time.now())), [pub_cmd], self.masteruri, False, self.current_user)) 2737 self._start_queue(self._progress_queue) 2738 return True 2739 else: 2740 return False 2741 except Exception, e: 2742 rospy.logwarn("Publish topic '%s' failed: %s", utf8(topic_name), utf8(e)) 2743 MessageBox.warning(self, "Publish topic error", 2744 ''.join(['Publish topic ', topic_name, ' failed!']), 2745 utf8(e)) 2746 print utf8(traceback.format_exc(1)) 2747 return False
2748
2749 - def _rem_empty_lists(self, param_dict):
2750 result = dict() 2751 for key, value in param_dict.iteritems(): 2752 if isinstance(value, dict): 2753 result[key] = self._rem_empty_lists(value) 2754 elif not (isinstance(value, list) and not value): 2755 result[key] = value 2756 return result
2757
2758 - def on_topic_pub_stop_clicked(self, topic_name=''):
2759 topic_names = [] 2760 if topic_name: 2761 topic_names.append(topic_name) 2762 else: 2763 selectedTopics = self.topicsFromIndexes(self.masterTab.topicsView.selectionModel().selectedIndexes()) 2764 topic_names = ['%s' % topic.name for topic in selectedTopics] 2765 if self.master_info is not None: 2766 nodes2stop = [] 2767 for topic in topic_names: 2768 topic_prefix = '/rostopic_pub%s_' % topic 2769 node_names = self.master_info.node_names 2770 for n in node_names: 2771 if n.startswith(topic_prefix): 2772 nodes2stop.append(n) 2773 self.stop_nodes_by_name(nodes2stop)
2774
2775 - def _show_topic_output(self, show_hz_only, use_ssh=False, topics=[]):
2776 ''' 2777 Shows the output of the topic in a terminal. 2778 ''' 2779 selected_topics = topics 2780 if not selected_topics: 2781 selected_topics = self.topicsFromIndexes(self.masterTab.topicsView.selectionModel().selectedIndexes()) 2782 ret = True 2783 if len(selected_topics) > 5: 2784 ret = MessageBox.question(self, "Show echo", "You are going to open the echo of " + utf8(len(selected_topics)) + " topics at once\nContinue?", buttons=MessageBox.Ok | MessageBox.Cancel) 2785 ret = (ret == MessageBox.Ok) 2786 if ret: 2787 for topic in selected_topics: 2788 self._add_topic_output2queue(topic, show_hz_only, use_ssh)
2789
2790 - def show_topic_output(self, topic_name, show_hz_only, use_ssh=False):
2791 ''' 2792 Shows the topic output in a new window. 2793 ''' 2794 if self.master_info is not None: 2795 topic = self.master_info.getTopic("%s" % topic_name) 2796 if topic is not None: 2797 self._add_topic_output2queue(topic, show_hz_only, use_ssh) 2798 else: 2799 rospy.logwarn("topic not found: %s" % topic_name)
2800
2801 - def _add_topic_output2queue(self, topic, show_hz_only, use_ssh=False):
2802 try: 2803 # connect to topic on remote host 2804 import shlex 2805 env = dict(os.environ) 2806 env["ROS_MASTER_URI"] = utf8(self.masteruri) 2807 namespace = rospy.names.namespace(topic.name) 2808 nodename = os.path.basename(topic.name) 2809 namespace = 'echo_%s%s%s%s' % ('hz_' if show_hz_only else '', 'ssh_' if use_ssh else '', utf8(get_hostname(self.masteruri)), namespace) 2810 cmd = 'rosrun node_manager_fkie node_manager --echo %s %s %s %s __name:=%s __ns:=%s' % (topic.name, topic.type, '--hz' if show_hz_only else '', '--ssh' if use_ssh else '', nodename, namespace) 2811 rospy.loginfo("Echo topic: %s" % cmd) 2812 ps = SupervisedPopen(shlex.split(cmd), env=env, stderr=None, close_fds=True, object_id=topic.name, description='Echo topic: %s' % topic.name) 2813 ps.finished.connect(self._topic_dialog_closed) 2814 self.__echo_topics_dialogs[topic.name] = ps 2815 except Exception, e: 2816 rospy.logwarn("Echo topic '%s' failed: %s" % (topic.name, utf8(e))) 2817 MessageBox.warning(self, "Echo of topic error", 2818 'Echo of topic %s failed!' % topic.name, 2819 '%s' % utf8(e))
2820
2821 - def _topic_dialog_closed(self, topic_name):
2822 if topic_name in self.__echo_topics_dialogs: 2823 del self.__echo_topics_dialogs[topic_name]
2824
2825 - def on_service_call_clicked(self, services=[]):
2826 ''' 2827 calls a service. 2828 ''' 2829 selected_services = services 2830 if not selected_services: 2831 selected_services = self.servicesFromIndexes(self.masterTab.servicesView.selectionModel().selectedIndexes()) 2832 try: 2833 for service in selected_services: 2834 param = ServiceDialog(service, self) 2835 param.show() 2836 except Exception, e: 2837 rospy.logwarn("Call service '%s' failed: %s" % (service.name, utf8(e))) 2838 MessageBox.warning(self, "Call service error", 2839 'Call service %s failed!' % service.name, 2840 '%s' % utf8(e))
2841
2842 - def service_call(self, service_name):
2843 service = self.master_info.getService(utf8(service_name)) 2844 if service is not None: 2845 try: 2846 param = ServiceDialog(service, self) 2847 param.show() 2848 except Exception, e: 2849 rospy.logwarn("Call service '%s' failed: %s" % (service.name, utf8(e))) 2850 MessageBox.warning(self, "Call service error", 2851 'Call service %s failed!' % service.name, 2852 '%s' % utf8(e))
2853
2854 - def _restore_expand_state(self, tree_view, proxy_model):
2855 ''' 2856 Expand the first item and all selected items. 2857 ''' 2858 tree_view.collapseAll() 2859 for selected in tree_view.selectionModel().selectedIndexes(): 2860 index = selected 2861 while index is not None and index.isValid(): 2862 item = proxy_model.sourceModel().itemFromIndex(index) 2863 if type(item) in [TopicGroupItem, ServiceGroupItem, GroupItem] and not tree_view.isExpanded(index): 2864 tree_view.setExpanded(index, True) 2865 tree_view.setExpanded(index, True) 2866 index = index.parent() 2867 # expand the root item. NodesView has on sync also other hosts. In this case only local host will expanded. 2868 for root_idx in range(proxy_model.sourceModel().rowCount()): 2869 source_index = proxy_model.sourceModel().index(root_idx, 0) 2870 item = proxy_model.sourceModel().itemFromIndex(source_index) 2871 if type(item) in [HostItem] and not item._local: 2872 continue 2873 mapped_index = proxy_model.mapFromSource(source_index) 2874 tree_view.setExpanded(mapped_index, True)
2875
2876 - def on_node_filter_changed(self, text):
2877 ''' 2878 Filter the displayed nodes 2879 ''' 2880 self.node_proxy_model.setFilterRegExp(QRegExp(text, Qt.CaseInsensitive, QRegExp.Wildcard)) 2881 if text: 2882 self.masterTab.nodeTreeView.expandAll() 2883 else: 2884 self._restore_expand_state(self.masterTab.nodeTreeView, self.node_proxy_model)
2885
2886 - def on_topic_filter_changed(self, text):
2887 ''' 2888 Filter the displayed topics 2889 ''' 2890 self.topic_proxyModel.setFilterRegExp(QRegExp(text, Qt.CaseInsensitive, QRegExp.Wildcard)) 2891 if text: 2892 self.masterTab.topicsView.expandAll() 2893 else: 2894 self._restore_expand_state(self.masterTab.topicsView, self.topic_proxyModel)
2895
2896 - def on_service_filter_changed(self, text):
2897 ''' 2898 Filter the displayed services 2899 ''' 2900 self.service_proxyModel.setFilterRegExp(QRegExp(text, Qt.CaseInsensitive, QRegExp.Wildcard)) 2901 if text: 2902 self.masterTab.servicesView.expandAll() 2903 else: 2904 self._restore_expand_state(self.masterTab.servicesView, self.service_proxyModel)
2905
2906 - def on_parameter_filter_changed(self, text):
2907 ''' 2908 Filter the displayed parameter 2909 ''' 2910 self.parameter_proxyModel.setFilterRegExp(QRegExp(text, Qt.CaseInsensitive, QRegExp.Wildcard))
2911
2912 - def on_get_parameter_clicked(self):
2913 ''' 2914 Requests parameter list from the ROS parameter server. 2915 ''' 2916 self.parameterHandler.requestParameterList(self.masteruri)
2917
2918 - def on_add_parameter_clicked(self):
2919 ''' 2920 Adds a parameter to the ROS parameter server. 2921 ''' 2922 selectedParameter = self.parameterFromIndexes(self.masterTab.parameterView.selectionModel().selectedIndexes()) 2923 ns = '/' 2924 if selectedParameter: 2925 ns = roslib.names.namespace(selectedParameter[0][0]) 2926 fields = {'name': ('string', ns), 'type': ('string', ['string', 'int', 'float', 'bool', 'list']), 'value': ('string', '')} 2927 newparamDia = ParameterDialog(fields, parent=self) 2928 newparamDia.setWindowTitle('Add new parameter') 2929 newparamDia.setFilterVisible(False) 2930 newparamDia.resize(400, 120) 2931 newparamDia.accepted.connect(self._on_add_parameter_accepted) 2932 newparamDia.setFocusField('name') 2933 newparamDia.show() 2934 newparamDia.raise_() 2935 newparamDia.activateWindow()
2936
2937 - def _on_add_parameter_accepted(self):
2938 if isinstance(self.sender(), ParameterDialog): 2939 params = self.sender().getKeywords() 2940 try: 2941 if params['type'] == 'int': 2942 value = int(params['value']) 2943 elif params['type'] == 'float': 2944 value = float(params['value']) 2945 elif params['type'] == 'bool': 2946 value = bool(params['value'].lower() in ("yes", "true", "t", "1")) 2947 elif params['type'] == 'list': 2948 try: 2949 import yaml 2950 value = [yaml.load(params['value'])] 2951 # if there is no YAML, load() will return an 2952 # empty string. We want an empty dictionary instead 2953 # for our representation of empty. 2954 if value is None: 2955 value = [] 2956 except yaml.MarkedYAMLError, e: 2957 MessageBox.warning(self, self.tr("Warning"), "yaml error: %s" % utf8(e), buttons=MessageBox.Ok) 2958 return 2959 else: 2960 value = params['value'] 2961 self.parameterHandler.deliverParameter(self.masteruri, {params['name']: value}) 2962 self.parameterHandler.requestParameterList(self.masteruri) 2963 self.sender().close() 2964 except (KeyError, ValueError), e: 2965 MessageBox.warning(self, "Warning", 2966 'Error while add a parameter to the ROS parameter server', 2967 utf8(e))
2968
2970 ''' 2971 Deletes the parameter from the ROS parameter server. 2972 ''' 2973 selectedParameter = self.parameterFromIndexes(self.masterTab.parameterView.selectionModel().selectedIndexes()) 2974 try: 2975 socket.setdefaulttimeout(15) 2976 name = rospy.get_name() 2977 master = xmlrpclib.ServerProxy(self.masteruri) 2978 master_multi = xmlrpclib.MultiCall(master) 2979 for (key, _) in selectedParameter: # _ := value 2980 master_multi.deleteParam(name, key) 2981 r = master_multi() 2982 for code, msg, parameter in r: 2983 if code != 1: 2984 rospy.logwarn("Error on delete parameter '%s': %s", parameter, msg) 2985 except: 2986 rospy.logwarn("Error on delete parameter: %s", utf8(traceback.format_exc(1))) 2987 MessageBox.warning(self, "Warning", 2988 'Error while delete a parameter to the ROS parameter server', 2989 utf8(traceback.format_exc(1))) 2990 else: 2991 self.on_get_parameter_clicked() 2992 finally: 2993 socket.setdefaulttimeout(None)
2994
2995 - def on_save_parameter_clicked(self):
2996 ''' 2997 Stores selected parameter to a file. 2998 ''' 2999 selectedParameter = self.parameterFromIndexes(self.masterTab.parameterView.selectionModel().selectedIndexes()) 3000 if selectedParameter: 3001 # (fileName, filter) 3002 (fileName, _) = QFileDialog.getSaveFileName(self, 3003 "Save parameter", 3004 nm.settings().current_dialog_path, 3005 "YAML files (*.yaml);;All files (*)") 3006 if fileName: 3007 nm.settings().current_dialog_path = os.path.dirname(fileName) 3008 try: 3009 with open(fileName, 'w+') as f: 3010 values = dict() 3011 # convert ROS namespaces of parameters to YAML namespaces 3012 for (key, value) in selectedParameter: 3013 keys = key.strip(rospy.names.SEP).split(rospy.names.SEP) 3014 curr_v = values 3015 for k in keys: 3016 if k in curr_v: 3017 curr_v = curr_v[k] 3018 elif k != keys[-1]: 3019 curr_v[k] = dict() 3020 curr_v = curr_v[k] 3021 else: 3022 curr_v[k] = value 3023 import yaml 3024 # print yaml.dump(values, default_flow_style=False) 3025 f.write(yaml.dump(values, default_flow_style=False)) 3026 except Exception as e: 3027 print utf8(traceback.format_exc(1)) 3028 MessageBox.warning(self, "Save parameter Error", 3029 'Error while save parameter', 3030 utf8(e))
3031
3032 - def _replaceDoubleSlash(self, liste):
3033 ''' 3034 used to avoid the adding of \\ for each \ in a string of a list 3035 ''' 3036 if liste and isinstance(liste, list): 3037 result = [] 3038 for l in liste: 3039 val = l 3040 if isinstance(l, (str, unicode)): 3041 val = l.replace("\\n", "\n") 3042 # result.append("".join([val])) 3043 elif isinstance(l, list): 3044 val = self._replaceDoubleSlash(l) 3045 result.append(val) 3046 return result 3047 return liste
3048
3049 - def _on_parameter_item_changed(self, item):
3050 ''' 3051 add changes to the ROS parameter server 3052 ''' 3053 if isinstance(item, ParameterValueItem): 3054 try: 3055 if isinstance(item.value, bool): 3056 value = bool(item.text().lower() in ("yes", "true", "t", "1")) 3057 elif isinstance(item.value, int): 3058 value = int(item.text()) 3059 elif isinstance(item.value, float): 3060 value = float(item.text()) 3061 elif isinstance(item.value, list): 3062 try: 3063 import yaml 3064 value = yaml.load(item.text()) 3065 # if there is no YAML, load() will return an 3066 # empty string. We want an empty dictionary instead 3067 # for our representation of empty. 3068 if value is None: 3069 value = [] 3070 value = self._replaceDoubleSlash(value) 3071 except yaml.MarkedYAMLError, e: 3072 MessageBox.warning(self, self.tr("Warning"), "yaml error: %s" % utf8(e), buttons=MessageBox.Ok) 3073 item.setText(utf8(item.value)) 3074 return 3075 else: 3076 value = item.text() 3077 self.parameterHandler.deliverParameter(self.masteruri, {item.name: value}) 3078 item.value = value 3079 except ValueError, e: 3080 MessageBox.warning(self, "Warning", 3081 'Error while add changes to the ROS parameter server', 3082 utf8(e)) 3083 item.setText(item.value)
3084
3085 - def _on_param_list(self, masteruri, code, msg, params):
3086 ''' 3087 @param masteruri: The URI of the ROS parameter server 3088 @type masteruri: C{str} 3089 @param code: The return code of the request. If not 1, the message is set and the list can be ignored. 3090 @type code: C{int} 3091 @param msg: The message of the result. 3092 @type msg: C{str} 3093 @param params: The list the parameter names. 3094 @type params: C{[str]} 3095 ''' 3096 if code == 1: 3097 params.sort() 3098 self.parameterHandler.requestParameterValues(masteruri, params)
3099
3100 - def _on_param_values(self, masteruri, code, msg, params):
3101 ''' 3102 @param masteruri: The URI of the ROS parameter server 3103 @type masteruri: C{str} 3104 @param code: The return code of the request. If not 1, the message is set and the list can be ignored. 3105 @type code: C{int} 3106 @param msg: The message of the result. 3107 @type msg: C{str} 3108 @param params: The dictionary the parameter names and request result. 3109 @type params: C{dict(paramName : (code, statusMessage, parameterValue))} 3110 ''' 3111 if code == 1: 3112 result = {} 3113 for p, (code_n, _, val) in params.items(): # _ := msg_n 3114 if code_n == 1: 3115 result[p] = val 3116 else: 3117 result[p] = '' 3118 if p == '/use_sim_time': 3119 self.__use_sim_time = (code_n == 1 and val) 3120 self.parameter_model.updateModelData(result) 3121 else: 3122 rospy.logwarn("Error on retrieve parameter from %s: %s", utf8(masteruri), utf8(msg))
3123
3124 - def _on_delivered_values(self, masteruri, code, msg, params):
3125 ''' 3126 @param masteruri: The URI of the ROS parameter server 3127 @type masteruri: C{str} 3128 @param code: The return code of the request. If not 1, the message is set and the list can be ignored. 3129 @type code: C{int} 3130 @param msg: The message of the result. 3131 @type msg: C{str} 3132 @param params: The dictionary the parameter names and request result. 3133 @type params: C{dict(paramName : (code, statusMessage, parameterValue))} 3134 ''' 3135 errmsg = '' 3136 if code == 1: 3137 for p, (code_n, msg, _) in params.items(): # _ := value 3138 if code_n != 1: 3139 errmsg = '%s: %s\n%s' % (p, errmsg, msg) 3140 else: 3141 errmsg = msg if msg else 'Unknown error on set parameter' 3142 if errmsg: 3143 MessageBox.warning(self, "Warning", 3144 'Error while delivering parameter to the ROS parameter server', 3145 utf8(errmsg))
3146
3147 - def _on_sim_param_values(self, masteruri, code, msg, params):
3148 ''' 3149 @param masteruri: The URI of the ROS parameter server 3150 @type masteruri: C{str} 3151 @param code: The return code of the request. If not 1, the message is set and the list can be ignored. 3152 @type code: C{int} 3153 @param msg: The message of the result. 3154 @type msg: C{str} 3155 @param params: The dictionary the parameter names and request result. 3156 @type params: C{dict(paramName : (code, statusMessage, parameterValue))} 3157 ''' 3158 robot_icon_found = False 3159 if code == 1: 3160 for p, (code_n, _, val) in params.items(): # _ := msg_n 3161 if p == '/use_sim_time': 3162 self.__use_sim_time = (code_n == 1 and val) 3163 elif p == '/robot_icon': 3164 robot_icon_found = True 3165 self.__current_parameter_robot_icon = val if code_n == 1 else '' 3166 self.update_robot_icon() 3167 elif p.startswith('/roslaunch/uris'): 3168 if code_n == 1: 3169 for _, value in val.items(): 3170 self.launch_server_handler.updateLaunchServerInfo(value) 3171 elif p == "/run_id": 3172 if self.__run_id != val: 3173 self.__run_id = val 3174 # you have to launch global parameter 3175 for _, launch_cfg in self.__configs.items(): 3176 try: 3177 launch_cfg.global_param_done.remove(masteruri) 3178 except ValueError: 3179 pass 3180 else: 3181 rospy.logwarn("Error on retrieve sim parameter value from %s: %s", utf8(masteruri), utf8(msg)) 3182 if not robot_icon_found: 3183 self.__current_parameter_robot_icon = '' 3184 self.update_robot_icon()
3185
3186 - def _get_nm_masteruri(self):
3187 ''' 3188 Requests the ROS master URI from the ROS master through the RPC interface and 3189 returns it. The 'materuri' attribute will be set to the requested value. 3190 @return: ROS master URI 3191 @rtype: C{str} or C{None} 3192 ''' 3193 if not hasattr(self, '_nm_materuri') or self._nm_materuri is None: 3194 masteruri = masteruri_from_ros() 3195 master = xmlrpclib.ServerProxy(masteruri) 3196 _, _, self._nm_materuri = master.getUri(rospy.get_name()) # reuslt: code, message, self._nm_materuri 3197 return self._nm_materuri
3198
3199 - def append_diagnostic(self, diagnostic_status):
3200 nodes = self.getNode(diagnostic_status.name) 3201 for node in nodes: 3202 node.append_diagnostic_status(diagnostic_status) 3203 if nodes: 3204 # get node by selected items 3205 if self._is_current_tab_name('Nodes'): 3206 return 3207 selections = self.masterTab.nodeTreeView.selectionModel().selectedIndexes() 3208 selectedNodes = self.nodesFromIndexes(selections) 3209 if len(selectedNodes) == 1: 3210 node = selectedNodes[0] 3211 if node.name == diagnostic_status.name: 3212 self.on_node_selection_changed(None, None)
3213 3214 # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3215 # %%%%%%%%%%%%% Nodelet handling %%%%%%%% 3216 # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3217
3218 - def _on_question_cancel(self, questionid, data):
3219 pass
3220
3221 - def _on_question_ok(self, questionid, data):
3222 if questionid == MessageFrame.TYPE_NODELET: 3223 try: 3224 for cfgs, nodes in data.data.iteritems(): 3225 self.stop_nodes_by_name(nodes) 3226 self.start_nodes_by_name(nodes, cfgs, force=True, check_nodelets=False) 3227 except Exception as err: 3228 rospy.logwarn("Error while start nodelets: %s" % utf8(err)) 3229 elif questionid == MessageFrame.TYPE_LAUNCH_FILE: 3230 try: 3231 self.launchfiles = data.data 3232 except Exception as err: 3233 rospy.logwarn("Error while reload launch file %s: %s" % (data.data, utf8(err))) 3234 MessageBox.warning(self, "Loading launch file", data.data, '%s' % utf8(err)) 3235 elif questionid == MessageFrame.TYPE_TRANSFER: 3236 try: 3237 host = '%s' % get_hostname(self.masteruri) 3238 username = self.current_user 3239 self.main_window.launch_dock.progress_queue.add2queue(utf8(uuid.uuid4()), 3240 'transfer %s to %s' % (host, data.data), 3241 nm.starter().transfer_files, 3242 (host, data.data, False, username)) 3243 self.main_window.launch_dock.progress_queue.start() 3244 except Exception as err: 3245 rospy.logwarn("Error while transfer changed files %s: %s" % (data.data, utf8(err))) 3246 MessageBox.warning(self, "Loading launch file", data.data, '%s' % utf8(err))
3247
3248 - def _on_info_ok(self, questionid, data):
3249 pass
3250 3251 # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3252 # %%%%%%%%%%%%% Shortcuts handling %%%%%%%% 3253 # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3254
3255 - def select_host_block(self, index):
3256 ''' 3257 Selects all nodes of a host with given index 3258 @param index: the index of the host in the tree model 3259 @type index: C{int} 3260 ''' 3261 root = self.masterTab.nodeTreeView.model().index(index, 0) 3262 if not root.isValid(): 3263 return 3264 self.masterTab.nodeTreeView.expand(root) 3265 # firstChild = root.child(0, 0) 3266 last_row_index = len(self.node_tree_model.header) - 1 3267 # lastChild = root.child(0, last_row_index) 3268 i = 0 3269 selection = QItemSelection() 3270 while root.child(i, 0).isValid(): 3271 index = root.child(i, 0) 3272 model_index = self.node_proxy_model.mapToSource(index) 3273 item = self.node_tree_model.itemFromIndex(model_index) 3274 if item is not None and not self._is_in_ignore_list(item.name): 3275 selection.append(QItemSelectionRange(index, root.child(i, last_row_index))) 3276 i = i + 1 3277 # selection = QItemSelection(firstChild, lastChild) 3278 self.masterTab.nodeTreeView.selectionModel().select(selection, QItemSelectionModel.ClearAndSelect)
3279
3280 - def _is_in_ignore_list(self, name):
3281 for i in self._stop_ignores: 3282 if name.endswith(i): 3283 return True 3284 return False
3285
3286 - def on_shortcut1_activated(self):
3287 self.select_host_block(0)
3288
3289 - def on_shortcut2_activated(self):
3290 self.select_host_block(1)
3291
3292 - def on_shortcut3_activated(self):
3293 self.select_host_block(2)
3294
3295 - def on_shortcut4_activated(self):
3296 self.select_host_block(3)
3297
3298 - def on_shortcut5_activated(self):
3299 self.select_host_block(4)
3300
3301 - def on_shortcut_collapse_all(self):
3302 self.masterTab.nodeTreeView.selectionModel().clearSelection() 3303 self.masterTab.nodeTreeView.collapseAll()
3304
3305 - def on_copy_c_pressed(self):
3306 result = '' 3307 if self.masterTab.nodeTreeView.hasFocus(): 3308 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 3309 for node in selectedNodes: 3310 try: 3311 result = '%s %s' % (result, node.name) 3312 except Exception: 3313 pass 3314 elif self.masterTab.topicsView.hasFocus(): 3315 selectedTopics = self.topicsFromIndexes(self.masterTab.topicsView.selectionModel().selectedIndexes()) 3316 for topic in selectedTopics: 3317 try: 3318 result = '%s %s' % (result, topic.name) 3319 except Exception: 3320 pass 3321 elif self.masterTab.servicesView.hasFocus(): 3322 selectedServices = self.servicesFromIndexes(self.masterTab.servicesView.selectionModel().selectedIndexes()) 3323 for service in selectedServices: 3324 try: 3325 result = '%s %s' % (result, service.name) 3326 except Exception: 3327 pass 3328 elif self.masterTab.parameterView.hasFocus(): 3329 selectedParameter = self.parameterFromIndexes(self.masterTab.parameterView.selectionModel().selectedIndexes()) 3330 for (name, _value) in selectedParameter: 3331 try: 3332 result = '%s %s' % (result, name) 3333 except Exception: 3334 pass 3335 QApplication.clipboard().setText(result.strip())
3336
3337 - def on_copy_x_pressed(self):
3338 result = '' 3339 if self.masterTab.nodeTreeView.hasFocus(): 3340 selectedNodes = self.nodesFromIndexes(self.masterTab.nodeTreeView.selectionModel().selectedIndexes()) 3341 for node in selectedNodes: 3342 try: 3343 result = '%s %s' % (result, node.pid) 3344 except Exception: 3345 pass 3346 elif self.masterTab.topicsView.hasFocus(): 3347 selectedTopics = self.topicsFromIndexes(self.masterTab.topicsView.selectionModel().selectedIndexes()) 3348 for topic in selectedTopics: 3349 try: 3350 result = '%s %s' % (result, topic.type) 3351 except Exception: 3352 pass 3353 elif self.masterTab.servicesView.hasFocus(): 3354 selectedServices = self.servicesFromIndexes(self.masterTab.servicesView.selectionModel().selectedIndexes()) 3355 for service in selectedServices: 3356 try: 3357 result = '%s %s' % (result, service.type) 3358 except Exception: 3359 pass 3360 elif self.masterTab.parameterView.hasFocus(): 3361 selectedParameter = self.parameterFromIndexes(self.masterTab.parameterView.selectionModel().selectedIndexes()) 3362 for (_, value) in selectedParameter: 3363 try: 3364 result = '%s %s' % (result, value) 3365 except Exception: 3366 pass 3367 QApplication.clipboard().setText(result.strip())
3368
3369 # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3370 # %%%%%%%%%%%%% Filter handling %%%%%%%%%%% 3371 # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3372 3373 3374 -class NodesSortFilterProxyModel(QSortFilterProxyModel):
3375
3376 - def filterAcceptsRow(self, sourceRow, sourceParent):
3377 ''' 3378 Perform filtering on column 0 (Name) 3379 ''' 3380 if not self.filterRegExp().pattern(): 3381 return True 3382 if (self.filterAcceptsRowItself(sourceRow, sourceParent)): 3383 return True 3384 # # accept if any of the parents is accepted on it's own merits 3385 # parent = sourceParent 3386 # while (parent.isValid()): 3387 # if (self.filterAcceptsRowItself(parent.row(), parent.parent())): 3388 # return True 3389 # parent = parent.parent() 3390 # accept if any of the children is accepted on it's own merits 3391 if (self.hasAcceptedChildren(sourceRow, sourceParent)): 3392 return True 3393 return False
3394
3395 - def hasAcceptedChildren(self, sourceRow, sourceParent):
3396 index = self.sourceModel().index(sourceRow, 0, sourceParent) 3397 if not index.isValid(): 3398 return False 3399 # check if there are children 3400 childCount = index.model().rowCount(index) 3401 if childCount == 0: 3402 return False 3403 for i in range(childCount): 3404 if (self.filterAcceptsRowItself(i, index)): 3405 return True 3406 # recursive call -> NOTICE that this is depth-first searching, you're probably better off with breadth first search... 3407 if (self.hasAcceptedChildren(i, index)): 3408 return True 3409 return False
3410
3411 - def filterAcceptsRowItself(self, sourceRow, sourceParent):
3412 index0 = self.sourceModel().index(sourceRow, 0, sourceParent) 3413 item = self.sourceModel().data(index0) 3414 if item is not None: 3415 # skip groups 3416 if '{' not in item: 3417 regex = self.filterRegExp() 3418 return (regex.indexIn(self.sourceModel().data(index0)) != -1) 3419 return False
3420
3421 3422 -class TopicsSortFilterProxyModel(QSortFilterProxyModel):
3423
3424 - def filterAcceptsRow(self, sourceRow, sourceParent):
3425 ''' 3426 Perform filtering on columns 0 and 3 (Name, Type) 3427 ''' 3428 result = True 3429 index0 = self.sourceModel().index(sourceRow, 0, sourceParent) 3430 regex = self.filterRegExp() 3431 item = self.sourceModel().itemFromIndex(index0) 3432 if type(item) == TopicItem: 3433 result = (regex.indexIn(item.topic.name) != -1 or regex.indexIn(item.topic_type_str) != -1) 3434 elif type(item) == TopicGroupItem: 3435 result = True 3436 if regex.indexIn(item.name) != -1: 3437 result = True 3438 else: 3439 sitems = item.get_topic_items() 3440 for sitem in sitems: 3441 result = (regex.indexIn(sitem.topic.name) != -1 or regex.indexIn(sitem.topic_type_str) != -1) 3442 if result: 3443 break 3444 return result
3445
3446 3447 -class ServicesSortFilterProxyModel(QSortFilterProxyModel):
3448
3449 - def filterAcceptsRow(self, sourceRow, sourceParent):
3450 ''' 3451 Perform filtering on columns 0 and 1 (Name, Type) 3452 ''' 3453 index0 = self.sourceModel().index(sourceRow, 0, sourceParent) 3454 regex = self.filterRegExp() 3455 item = self.sourceModel().itemFromIndex(index0) 3456 if type(item) == ServiceItem: 3457 return (regex.indexIn(item.service.name) != -1 or regex.indexIn(item.service_type_str) != -1) 3458 elif type(item) == ServiceGroupItem: 3459 if regex.indexIn(item.name) != -1: 3460 return True 3461 grp_res = True 3462 sitems = item.get_service_items() 3463 for sitem in sitems: 3464 res = (regex.indexIn(sitem.service.name) != -1 or regex.indexIn(sitem.service_type_str) != -1) 3465 if res: 3466 return True 3467 grp_res = res 3468 return grp_res 3469 return True
3470
3471 3472 -class ParameterSortFilterProxyModel(QSortFilterProxyModel):
3473
3474 - def filterAcceptsRow(self, sourceRow, sourceParent):
3475 ''' 3476 Perform filtering on columns 0 and 1 (Name, Value) 3477 ''' 3478 index0 = self.sourceModel().index(sourceRow, 0, sourceParent) 3479 # index1 = self.sourceModel().index(sourceRow, 1, sourceParent) 3480 index2 = self.sourceModel().index(sourceRow, 2, sourceParent) 3481 regex = self.filterRegExp() 3482 return (regex.indexIn(self.sourceModel().data(index0, ParameterNameItem.NAME_ROLE)) != -1 or 3483 regex.indexIn(self.sourceModel().data(index2, ParameterValueItem.VALUE_ROLE)) != -1)
3484
3485 3486 -class IconsDelegate(QItemDelegate):
3487
3488 - def __init__(self, parent=None, *args):
3489 QItemDelegate.__init__(self, parent, *args) 3490 self._idx_icon = 1 3491 self.IMAGES = {'launchfile': QImage(':/icons/crystal_clear_launch_file.png').scaled(15, 15, Qt.IgnoreAspectRatio, Qt.SmoothTransformation), 3492 'defaultcfg': QImage(":/icons/default_cfg.png").scaled(15, 15, Qt.IgnoreAspectRatio, Qt.SmoothTransformation), 3493 'nodelet': QImage(":/icons/crystal_clear_nodelet.png").scaled(15, 15, Qt.IgnoreAspectRatio, Qt.SmoothTransformation), 3494 'nodelet_mngr': QImage(":/icons/crystal_clear_nodelet_mngr.png").scaled(15, 15, Qt.IgnoreAspectRatio, Qt.SmoothTransformation), 3495 'warning': QImage(':/icons/crystal_clear_warning.png').scaled(15, 15, Qt.IgnoreAspectRatio, Qt.SmoothTransformation), 3496 'noscreen': QImage(':/icons/crystal_clear_no_io.png').scaled(15, 15, Qt.IgnoreAspectRatio, Qt.SmoothTransformation), 3497 'misc': QImage(':/icons/crystal_clear_miscellaneous.png').scaled(15, 15, Qt.IgnoreAspectRatio, Qt.SmoothTransformation), 3498 'group': QImage(':/icons/crystal_clear_group.png').scaled(15, 15, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) 3499 }
3500
3501 - def paint(self, painter, option, index):
3502 painter.save() 3503 self._idx_icon = 1 3504 model_index = index.model().mapToSource(index) 3505 item = model_index.model().itemFromIndex(model_index) 3506 if isinstance(item, CellItem): 3507 if isinstance(item.item, NodeItem): 3508 if not item.item.has_screen: 3509 rect = self.calcDecorationRect(option.rect) 3510 painter.drawImage(rect, self.IMAGES['noscreen']) 3511 lcfgs = item.item.count_launch_cfgs() 3512 if lcfgs > 0: 3513 rect = self.calcDecorationRect(option.rect) 3514 painter.drawImage(rect, self.IMAGES['launchfile']) 3515 if lcfgs > 1: 3516 painter.drawText(rect, Qt.AlignCenter, str(lcfgs)) 3517 dcfgs = item.item.count_default_cfgs() 3518 if dcfgs > 0: 3519 rect = self.calcDecorationRect(option.rect) 3520 painter.drawImage(rect, self.IMAGES['defaultcfg']) 3521 if dcfgs > 1: 3522 painter.drawText(rect, Qt.AlignCenter, str(dcfgs)) 3523 if item.item.nodelets: 3524 rect = self.calcDecorationRect(option.rect) 3525 painter.drawImage(rect, self.IMAGES['nodelet_mngr']) 3526 if item.item.nodelet_mngr: 3527 rect = self.calcDecorationRect(option.rect) 3528 painter.drawImage(rect, self.IMAGES['nodelet']) 3529 if item.item.nodelets: 3530 item.setToolTip("This is a nodelet manager") 3531 elif item.item.nodelet_mngr: 3532 item.setToolTip("This is a nodelet for %s" % item.item.nodelet_mngr) 3533 else: 3534 item.setToolTip("") 3535 elif isinstance(item.item, GroupItem): 3536 llcfgs, ddcfgs = item.item.get_configs() 3537 lcfgs = len(llcfgs) 3538 dcfgs = len(ddcfgs) 3539 rect = self.calcDecorationRect(option.rect) 3540 painter.drawImage(rect, self.IMAGES['group']) 3541 count_nodes = item.item.count_nodes() 3542 if count_nodes > 1: 3543 painter.drawText(rect, Qt.AlignCenter, str(count_nodes)) 3544 if lcfgs > 0: 3545 rect = self.calcDecorationRect(option.rect) 3546 painter.drawImage(rect, self.IMAGES['launchfile']) 3547 if lcfgs > 1: 3548 painter.drawText(rect, Qt.AlignCenter, str(lcfgs)) 3549 if dcfgs > 0: 3550 rect = self.calcDecorationRect(option.rect) 3551 painter.drawImage(rect, self.IMAGES['defaultcfg']) 3552 if dcfgs > 1: 3553 painter.drawText(rect, Qt.AlignCenter, str(dcfgs)) 3554 painter.restore()
3555
3556 - def calcDecorationRect(self, main_rect):
3557 rect = QRect() 3558 rect.setX(main_rect.x() + self._idx_icon) 3559 rect.setY(main_rect.y() + 1) 3560 rect.setWidth(15) 3561 rect.setHeight(15) 3562 self._idx_icon += 17 3563 return rect
3564