Package rocon_conductor_graph :: Module conductor_graph
[frames] | no frames]

Source Code for Module rocon_conductor_graph.conductor_graph

  1  #!/usr/bin/env python 
  2  # 
  3  # License: BSD 
  4  #   https://raw.github.com/robotics-in-concert/rocon_multimaster/master/rocon_gateway_graph/LICENSE 
  5  # 
  6  ############################################################################## 
  7  # Imports 
  8  ############################################################################## 
  9   
 10  from __future__ import division 
 11  import os 
 12   
 13  from python_qt_binding import loadUi 
 14  from python_qt_binding.QtCore import QFile, QIODevice, Qt, Signal, QAbstractListModel, pyqtSignal, pyqtSlot,SIGNAL,SLOT 
 15  from python_qt_binding.QtGui import QFileDialog, QGraphicsScene, QIcon, QImage, QPainter, QWidget,QLabel, QComboBox, QSizePolicy,QTextEdit ,QCompleter, QBrush,QDialog, QColor, QPen, QPushButton, QTabWidget, QPlainTextEdit,QGridLayout, QVBoxLayout, QHBoxLayout, QMessageBox 
 16  from python_qt_binding.QtSvg import QSvgGenerator 
 17   
 18  import rosgraph.impl.graph 
 19  import rosservice 
 20  import rostopic 
 21  import rospkg 
 22   
 23  ###################### 
 24  #dwlee 
 25  import rosnode 
 26  import roslib 
 27  import rospy 
 28  from concert_msgs.msg import ConcertClients 
 29  from rocon_app_manager_msgs.srv import GetPlatformInfo, Status, Invite, StartApp, StopApp 
 30  from rocon_app_manager_msgs.msg import PlatformInfo 
 31  ########################### 
 32   
 33  from .dotcode import RosGraphDotcodeGenerator 
 34  from .interactive_graphics_view import InteractiveGraphicsView 
 35  from qt_dotgraph.dot_to_qt import DotToQtGenerator 
 36  from qt_gui.plugin import Plugin 
 37   
 38  # pydot requires some hacks 
 39  from qt_dotgraph.pydotfactory import PydotFactory 
 40  # TODO: use pygraphviz instead, but non-deterministic layout will first be resolved in graphviz 2.30 
 41  # from qtgui_plugin.pygraphvizfactory import PygraphvizFactory 
 42   
 43  from rocon_gateway import Graph 
 44  from conductor_graph_info import ConductorGraphInfo 
 45   
 46   
 47   
 48  ############################################################################## 
 49  # Utility Classes 
 50  ############################################################################## 
 51   
 52   
53 -class RepeatedWordCompleter(QCompleter):
54 """A completer that completes multiple times from a list""" 55
56 - def init(self, parent=None):
57 QCompleter.init(self, parent)
58
59 - def pathFromIndex(self, index):
60 path = QCompleter.pathFromIndex(self, index) 61 lst = str(self.widget().text()).split(',') 62 if len(lst) > 1: 63 path = '%s, %s' % (','.join(lst[:-1]), path) 64 return path
65
66 - def splitPath(self, path):
67 path = str(path.split(',')[-1]).lstrip(' ') 68 return [path]
69 70
71 -class NodeEventHandler():
72 - def __init__(self,tabWidget,node_item,callback_func):
73 self._tabWidget = tabWidget 74 self._callback_func = callback_func 75 self._node_item = node_item
76 77
78 - def NodeEvent(self,event):
79 for k in range(self._tabWidget.count()): 80 if self._tabWidget.tabText(k) == self._node_item._label.text(): 81 self._tabWidget.setCurrentIndex (k)
82 83
84 -class NamespaceCompletionModel(QAbstractListModel):
85 """Ros package and stacknames"""
86 - def __init__(self, linewidget, topics_only):
87 super(QAbstractListModel, self).__init__(linewidget) 88 self.names = []
89
90 - def refresh(self, names):
91 namesset = set() 92 for n in names: 93 namesset.add(str(n).strip()) 94 namesset.add("-%s" % (str(n).strip())) 95 self.names = sorted(namesset)
96
97 - def rowCount(self, parent):
98 return len(self.names)
99
100 - def data(self, index, role):
101 if index.isValid() and (role == Qt.DisplayRole or role == Qt.EditRole): 102 return self.names[index.row()] 103 return None
104 105 ############################################################################## 106 # Gateway Classes 107 ############################################################################## 108 109
110 -class ConductorGraph(Plugin):
111 112 _deferred_fit_in_view = Signal() 113 _client_list_update_signal = Signal() 114
115 - def __init__(self, context):
116 self._context = context 117 super(ConductorGraph, self).__init__(context) 118 self.initialised = False 119 self.setObjectName('Conductor Graph') 120 self._current_dotcode = None 121 122 self._node_item_events = {} 123 self._client_info_list = {} 124 self._widget = QWidget() 125 126 127 # factory builds generic dotcode items 128 self.dotcode_factory = PydotFactory() 129 # self.dotcode_factory = PygraphvizFactory() 130 self.dotcode_generator = RosGraphDotcodeGenerator() 131 self.dot_to_qt = DotToQtGenerator() 132 self._graph = ConductorGraphInfo() 133 134 rospack = rospkg.RosPack() 135 ui_file = os.path.join(rospack.get_path('rocon_conductor_graph'), 'ui', 'conductor_graph.ui') 136 #ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ui', 'conductor_graph.ui') 137 loadUi(ui_file, self._widget, {'InteractiveGraphicsView': InteractiveGraphicsView}) 138 self._widget.setObjectName('ConductorGraphUi') 139 140 if context.serial_number() > 1: 141 self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number())) 142 143 self._scene = QGraphicsScene() 144 self._scene.setBackgroundBrush(Qt.white) 145 self._widget.graphics_view.setScene(self._scene) 146 147 #self._widget.refresh_graph_push_button.setIcon(QIcon.fromTheme('view-refresh')) 148 self._widget.refresh_graph_push_button.setIcon(QIcon.fromTheme('window-new')) 149 self._widget.refresh_graph_push_button.pressed.connect(self._update_conductor_graph) 150 151 self._widget.highlight_connections_check_box.toggled.connect(self._redraw_graph_view) 152 self._widget.auto_fit_graph_check_box.toggled.connect(self._redraw_graph_view) 153 self._widget.fit_in_view_push_button.setIcon(QIcon.fromTheme('zoom-original')) 154 self._widget.fit_in_view_push_button.pressed.connect(self._fit_in_view) 155 156 self._update_conductor_graph() 157 self._deferred_fit_in_view.connect(self._fit_in_view, Qt.QueuedConnection) 158 self._deferred_fit_in_view.emit() 159 160 #add by dwlee 161 self._widget.tabWidget.currentChanged.connect(lambda index: self._test_function(index)); 162 self._client_list_update_signal.connect(self._update_conductor_graph) 163 rospy.Subscriber("/concert/list_concert_clients", ConcertClients, self._update_client_list) 164 165 context.add_widget(self._widget)
166
167 - def _test_function(self, index):
168 169 # get tab widget handle 170 service_text_widget = None 171 cur_tab_widget = self._widget.tabWidget.currentWidget() 172 173 if cur_tab_widget == None: 174 return 175 176 object_name = 'services_text_widget' 177 for k in cur_tab_widget.children(): 178 if k.objectName().count(object_name) >= 1 : 179 service_text_widget = k 180 break 181 182 if service_text_widget == None: 183 return 184 185 service_text_widget.clear()
186 187
188 - def restore_settings(self, plugin_settings, instance_settings):
189 self.initialised = True 190 self._refresh_rosgraph()
191
192 - def shutdown_plugin(self):
193 pass
194
195 - def _update_conductor_graph(self):
196 # re-enable controls customizing fetched ROS graph 197 198 self._refresh_rosgraph() 199 self._update_client_tab()
200
201 - def _refresh_rosgraph(self):
202 if not self.initialised: 203 return 204 self._update_graph_view(self._generate_dotcode())
205
206 - def _generate_dotcode(self):
207 return self.dotcode_generator.generate_dotcode(rosgraphinst=self._graph, 208 dotcode_factory=self.dotcode_factory, 209 orientation='LR' 210 )
211 - def _update_graph_view(self, dotcode):
212 if dotcode == self._current_dotcode: 213 return 214 self._current_dotcode = dotcode 215 self._redraw_graph_view()
216
217 - def _update_client_list(self,data):
218 self._client_list_update_signal.emit() 219 pass
220
221 - def _start_service(self,node_name,service_name):
222 223 # get tab widget handle 224 service_text_widget = None 225 226 cur_tab_widget = self._widget.tabWidget.currentWidget() 227 if cur_tab_widget == None: 228 return 229 230 object_name = 'services_text_widget' 231 for k in cur_tab_widget.children(): 232 if k.objectName().count(object_name) >= 1 : 233 service_text_widget = k 234 break 235 236 if service_text_widget == None: 237 return 238 239 service_text_widget.clear() 240 241 service = self._graph._client_info_list[node_name]['gateway_name']+"/"+service_name 242 info_text = '' 243 244 if service_name == 'status': 245 service_handle = rospy.ServiceProxy(service, Status) 246 call_result = service_handle() 247 248 info_text = "<html>" 249 info_text += "<p>-------------------------------------------</p>" 250 info_text += "<p><b>application_namespace: </b>" +call_result.application_namespace+"</p>" 251 info_text += "<p><b>remote_controller: </b>" +call_result.remote_controller+"</p>" 252 info_text += "<p><b>app_status: </b>" +call_result.app_status+"</p>" 253 info_text +="</html>" 254 255 elif service_name == 'platform_info': 256 257 service_handle = rospy.ServiceProxy(service, GetPlatformInfo) 258 call_result = service_handle() 259 260 info_text = "<html>" 261 info_text += "<p>-------------------------------------------</p>" 262 info_text += "<p><b>platform: </b>" +call_result.platform_info.platform+"</p>" 263 info_text += "<p><b>system: </b>" +call_result.platform_info.system+"</p>" 264 info_text += "<p><b>robot: </b>" +call_result.platform_info.robot+"</p>" 265 info_text += "<p><b>name: </b>" +call_result.platform_info.name+"</p>" 266 info_text +="</html>" 267 268 elif service_name == 'invite': 269 #sesrvice 270 service_handle = rospy.ServiceProxy(service, Invite) 271 #dialog 272 dlg = QDialog(self._widget) 273 274 dlg.setSizePolicy(QSizePolicy.MinimumExpanding,QSizePolicy.Ignored) 275 dlg.setMinimumSize(500,0) 276 dlg_rect = dlg.geometry() 277 278 #dialog layout 279 ver_layout = QVBoxLayout(dlg) 280 ver_layout.setContentsMargins (9,9,9,9) 281 282 #param layout 283 text_grid_sub_widget = QWidget() 284 text_grid_layout = QGridLayout(text_grid_sub_widget) 285 text_grid_layout.setColumnStretch (1, 0) 286 text_grid_layout.setRowStretch (2, 0) 287 288 #param 1 289 remote_target_name =u"" 290 title_widget1 = QLabel("remote_target_name: ") 291 context_widget1 = QTextEdit() 292 context_widget1.setSizePolicy(QSizePolicy.MinimumExpanding,QSizePolicy.Ignored) 293 context_widget1.setMinimumSize(0,30) 294 context_widget1.append("") 295 296 #param 2 297 application_namespace=u"" 298 title_widget2 = QLabel("application_namespace: ") 299 context_widget2 = QTextEdit() 300 context_widget2.setSizePolicy(QSizePolicy.MinimumExpanding,QSizePolicy.Ignored) 301 context_widget2.setMinimumSize(0,30) 302 context_widget2.append("") 303 304 #param 3 305 cancel=False 306 title_widget3 = QLabel("cancel: ") 307 context_widget3 = QComboBox() 308 context_widget3.setSizePolicy(QSizePolicy.MinimumExpanding,QSizePolicy.Ignored) 309 context_widget3.setMinimumSize(0,30) 310 311 context_widget3.addItem("True",True) 312 context_widget3.addItem("False",False) 313 314 #add param 315 text_grid_layout.addWidget(title_widget1) 316 text_grid_layout.addWidget(context_widget1) 317 318 text_grid_layout.addWidget(title_widget2) 319 text_grid_layout.addWidget(context_widget2) 320 321 text_grid_layout.addWidget(title_widget3) 322 text_grid_layout.addWidget(context_widget3) 323 324 #add param layout 325 ver_layout.addWidget(text_grid_sub_widget) 326 327 #button layout 328 button_hor_sub_widget = QWidget() 329 button_hor_layout = QHBoxLayout(button_hor_sub_widget) 330 331 #button 332 btn_call = QPushButton("Call") 333 btn_cancel = QPushButton("cancel") 334 335 btn_call.clicked.connect(lambda: dlg.done(0)) 336 btn_call.clicked.connect(lambda : service_handle(context_widget1.toPlainText(), 337 context_widget2.toPlainText(), 338 context_widget3.itemData(context_widget3.currentIndex()) 339 )) 340 341 btn_cancel.clicked.connect(lambda: dlg.done(0)) 342 343 #add button 344 button_hor_layout.addWidget(btn_call) 345 button_hor_layout.addWidget(btn_cancel) 346 347 #add button layout 348 ver_layout.addWidget(button_hor_sub_widget) 349 350 dlg.setVisible(True) 351 352 elif service_name == 'start_app': 353 #sesrvice 354 service_handle = rospy.ServiceProxy(service, StartApp) 355 #dialog 356 dlg = QDialog(self._widget) 357 358 dlg.setSizePolicy(QSizePolicy.MinimumExpanding,QSizePolicy.Ignored) 359 dlg.setMinimumSize(500,0) 360 dlg_rect = dlg.geometry() 361 362 #dialog layout 363 ver_layout = QVBoxLayout(dlg) 364 ver_layout.setContentsMargins (9,9,9,9) 365 366 #param layout 367 text_grid_sub_widget = QWidget() 368 text_grid_layout = QGridLayout(text_grid_sub_widget) 369 text_grid_layout.setColumnStretch (1, 0) 370 text_grid_layout.setRowStretch (2, 0) 371 372 #param 1 373 name =u"" 374 title_widget1 = QLabel("name: ") 375 context_widget1 = QTextEdit() 376 context_widget1.setSizePolicy(QSizePolicy.MinimumExpanding,QSizePolicy.Ignored) 377 context_widget1.setMinimumSize(0,30) 378 context_widget1.append("") 379 380 #param 2 381 cancel=False 382 title_widget2 = QLabel("remappings: ") 383 context_widget2 = QTextEdit() 384 context_widget2.setSizePolicy(QSizePolicy.MinimumExpanding,QSizePolicy.Ignored) 385 context_widget2.setMinimumSize(0,30) 386 context_widget2.append("") 387 388 #add param 389 text_grid_layout.addWidget(title_widget1) 390 text_grid_layout.addWidget(context_widget1) 391 392 text_grid_layout.addWidget(title_widget2) 393 text_grid_layout.addWidget(context_widget2) 394 395 #add param layout 396 ver_layout.addWidget(text_grid_sub_widget) 397 398 #button layout 399 button_hor_sub_widget = QWidget() 400 button_hor_layout = QHBoxLayout(button_hor_sub_widget) 401 402 #button 403 btn_call = QPushButton("Call") 404 btn_cancel = QPushButton("cancel") 405 406 btn_call.clicked.connect(lambda: dlg.done(0)) 407 btn_call.clicked.connect(lambda : service_handle(context_widget1.toPlainText(), 408 [])) 409 410 btn_cancel.clicked.connect(lambda: dlg.done(0)) 411 412 #add button 413 button_hor_layout.addWidget(btn_call) 414 button_hor_layout.addWidget(btn_cancel) 415 416 #add button layout 417 ver_layout.addWidget(button_hor_sub_widget) 418 419 dlg.setVisible(True) 420 421 print 'start app' 422 423 elif service_name == 'stop_app': 424 service_handle = rospy.ServiceProxy(service, StopApp) 425 call_result = service_handle() 426 print 'stop app' 427 else: 428 print 'has no service' 429 430 service_text_widget.appendHtml(info_text)
431 432
433 - def _update_client_tab(self):
434 self._widget.tabWidget.clear() 435 for k in self._graph._client_info_list.values(): 436 main_widget=QWidget() 437 438 ver_layout = QVBoxLayout(main_widget) 439 440 ver_layout.setContentsMargins (9,9,9,9) 441 ver_layout.setSizeConstraint (ver_layout.SetDefaultConstraint) 442 443 sub_widget = QWidget() 444 sub_widget.setAccessibleName('sub_widget') 445 btn_grid_layout = QGridLayout(sub_widget) 446 447 btn_grid_layout.setContentsMargins (9,9,9,9) 448 449 btn_grid_layout.setColumnStretch (1, 0) 450 btn_grid_layout.setRowStretch (2, 0) 451 452 btn_invite = QPushButton("invite") 453 btn_platform_info = QPushButton("platform_info") 454 btn_status = QPushButton("status") 455 btn_start_app = QPushButton("start_app") 456 btn_stop_app = QPushButton("stop_app") 457 458 btn_invite.clicked.connect(lambda: self._start_service(self._widget.tabWidget.tabText(self._widget.tabWidget.currentIndex()),"invite")) 459 btn_platform_info.clicked.connect(lambda: self._start_service(self._widget.tabWidget.tabText(self._widget.tabWidget.currentIndex()),"platform_info")) 460 btn_status.clicked.connect(lambda: self._start_service(self._widget.tabWidget.tabText(self._widget.tabWidget.currentIndex()),"status")) 461 btn_start_app.clicked.connect(lambda: self._start_service(self._widget.tabWidget.tabText(self._widget.tabWidget.currentIndex()),"start_app")) 462 btn_stop_app.clicked.connect(lambda: self._start_service(self._widget.tabWidget.tabText(self._widget.tabWidget.currentIndex()),"stop_app")) 463 464 btn_grid_layout.addWidget(btn_invite) 465 btn_grid_layout.addWidget(btn_platform_info) 466 btn_grid_layout.addWidget(btn_status) 467 btn_grid_layout.addWidget(btn_start_app) 468 btn_grid_layout.addWidget(btn_stop_app) 469 470 ver_layout.addWidget(sub_widget) 471 472 app_context_widget = QPlainTextEdit() 473 app_context_widget.setObjectName(k["app_name"]+'_'+'app_context_widget') 474 app_context_widget.setAccessibleName('app_context_widget') 475 app_context_widget.appendHtml(k["app_context"]) 476 477 ver_layout.addWidget(app_context_widget) 478 479 services_text_widget = QPlainTextEdit() 480 services_text_widget.setObjectName(k["app_name"]+'_'+'services_text_widget') 481 ver_layout.addWidget(services_text_widget) 482 483 # new icon 484 path = "" 485 if k["isNew"] == True: 486 path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"../../resources/images/new.gif") 487 488 #add tab 489 self._widget.tabWidget.addTab(main_widget,QIcon(path), k["app_name"]);
490
491 - def _redraw_graph_view(self):
492 self._scene.clear() 493 self._node_item_events = {} 494 495 if self._widget.highlight_connections_check_box.isChecked(): 496 highlight_level = 3 497 else: 498 highlight_level = 1 499 500 # layout graph and create qt items 501 (nodes, edges) = self.dot_to_qt.dotcode_to_qt_items(self._current_dotcode, 502 highlight_level=highlight_level, 503 same_label_siblings=True) 504 # if we wish to make special nodes, do that here (maybe subclass GraphItem, just like NodeItem does) 505 #node 506 for node_item in nodes.itervalues(): 507 # set the color of conductor to orange 508 if node_item._label.text() == self._graph._concert_conductor_name: 509 royal_blue = QColor(65, 105, 255) 510 node_item._default_color = royal_blue 511 node_item.set_color(royal_blue) 512 513 #set the uuid 514 515 #print self._graph._client_info_list 516 if self._graph._client_info_list.has_key(str(node_item._label.text())): 517 node_item.setToolTip(self._graph._client_info_list[node_item._label.text()]['uuid']) 518 519 # redefine mouse event 520 self._node_item_events[node_item._label.text()] = NodeEventHandler(self._widget.tabWidget,node_item,node_item.mouseDoubleClickEvent); 521 node_item.mouseDoubleClickEvent = self._node_item_events[node_item._label.text()].NodeEvent; 522 523 self._scene.addItem(node_item) 524 525 #edge 526 for edge_items in edges.itervalues(): 527 for edge_item in edge_items: 528 edge_item.add_to_scene(self._scene) 529 #set the color of node as connection strength one of red, yellow, green 530 edge_dst_name = edge_item.to_node._label.text() 531 if self._graph._client_info_list.has_key(edge_dst_name): 532 connection_strength = self._graph._client_info_list[edge_dst_name]['connection_strength'] 533 if connection_strength == 'very_strong': 534 green = QColor(0, 255, 0) 535 edge_item._default_color = green 536 edge_item.set_color(green) 537 538 elif connection_strength == 'strong': 539 green_yellow = QColor(125, 255,0) 540 edge_item._default_color = green_yellow 541 edge_item.set_color(green_yellow) 542 543 elif connection_strength == 'normal': 544 yellow = QColor(238, 238,0) 545 edge_item._default_color = yellow 546 edge_item.set_color(yellow) 547 548 elif connection_strength == 'weak': 549 yellow_red = QColor(255, 125,0) 550 edge_item._default_color = yellow_red 551 edge_item.set_color(yellow_red) 552 553 elif connection_strength == 'very_weak': 554 red = QColor(255, 0,0) 555 edge_item._default_color = red 556 edge_item.set_color(red) 557 558 self._scene.setSceneRect(self._scene.itemsBoundingRect()) 559 560 if self._widget.auto_fit_graph_check_box.isChecked(): 561 self._fit_in_view()
562
563 - def _fit_in_view(self):
564 self._widget.graphics_view.fitInView(self._scene.itemsBoundingRect(), Qt.KeepAspectRatio)
565