00001
00002
00003
00004
00005
00006
00007
00008
00009 from __future__ import division
00010 import os
00011
00012 from python_qt_binding import loadUi
00013 from python_qt_binding.QtCore import Qt, QAbstractListModel, Signal
00014 from python_qt_binding.QtGui import QGraphicsScene, QIcon, QWidget, QLabel, QComboBox
00015 from python_qt_binding.QtGui import QSizePolicy, QTextEdit, QCompleter, QColor, QPushButton
00016 from python_qt_binding.QtGui import QVBoxLayout, QHBoxLayout, QPlainTextEdit
00017 from python_qt_binding.QtGui import QGridLayout, QTextCursor, QDialog
00018
00019 import rospkg
00020 import concert_msgs.msg as concert_msgs
00021
00022
00023
00024 import threading
00025 threading._DummyThread._Thread__stop = lambda x: 42
00026
00027
00028
00029 from .interactive_graphics_view import InteractiveGraphicsView
00030 from qt_dotgraph.dot_to_qt import DotToQtGenerator
00031 from qt_gui.plugin import Plugin
00032
00033
00034 from qt_dotgraph.pydotfactory import PydotFactory
00035
00036
00037
00038 from concert_utilities.conductor_graph import ConductorGraphDotcodeGenerator
00039 from concert_utilities.conductor_graph import ConductorGraphInfo
00040
00041
00042
00043
00044
00045
00046 class GraphEventHandler():
00047
00048 def __init__(self, tabWidget, item, callback_func):
00049 self._tabWidget = tabWidget
00050 self._callback_func = callback_func
00051 self._item = item
00052
00053 def NodeEvent(self, event):
00054 self._callback_func(event)
00055 for k in range(self._tabWidget.count()):
00056 if self._tabWidget.tabText(k) == self._item._label.text():
00057 self._tabWidget.setCurrentIndex(k)
00058
00059 def EdgeEvent(self, event):
00060 self._callback_func(event)
00061 self._item.set_node_color(QColor(0, 0, 255))
00062
00063
00064
00065
00066
00067
00068
00069 class DynamicArgumentLayer():
00070 def __init__(self, dialog_layout, name='', add=False, params=[]):
00071 self.dlg_layout = dialog_layout
00072 self.name = name
00073 self.add = add
00074 self.params = params
00075 self.params_list = []
00076
00077 params_item = []
00078 for k in self.params:
00079 param_name = k[0]
00080 param_type = k[1]
00081 param_widget = None
00082 params_item.append([param_name, param_widget, param_type])
00083 self.params_list.append(params_item)
00084
00085 print "DAL: %s" % (self.params_list)
00086
00087 self.arg_ver_sub_widget = QWidget()
00088 self.arg_ver_layout = QVBoxLayout(self.arg_ver_sub_widget)
00089 self.arg_ver_layout.setContentsMargins(0, 0, 0, 0)
00090 self._create_layout()
00091
00092 def _create_layout(self):
00093 name_hor_sub_widget = QWidget()
00094 name_hor_layout = QHBoxLayout(name_hor_sub_widget)
00095
00096 name_widget = QLabel(self.name + ": ")
00097 name_hor_layout.addWidget(name_widget)
00098 if self.add == True:
00099 btn_add = QPushButton("+", name_hor_sub_widget)
00100
00101 btn_add.clicked.connect(self._push_param)
00102 btn_add.clicked.connect(self._update_item)
00103 name_hor_layout.addWidget(btn_add)
00104
00105 btn_subtract = QPushButton("-", name_hor_sub_widget)
00106 btn_subtract.clicked.connect(self._pop_param)
00107 btn_subtract.clicked.connect(self._update_item)
00108 name_hor_layout.addWidget(btn_subtract)
00109 pass
00110
00111 self.arg_ver_layout.addWidget(name_hor_sub_widget)
00112 self.dlg_layout.addWidget(self.arg_ver_sub_widget)
00113 self._update_item()
00114
00115 def _update_item(self):
00116 widget_layout = self.arg_ver_layout
00117 item_list = self.params_list
00118
00119 widget_list = widget_layout.parentWidget().children()
00120 while len(widget_list) > 2:
00121 added_arg_widget = widget_list.pop()
00122 widget_layout.removeWidget(added_arg_widget)
00123 added_arg_widget.setParent(None)
00124 added_arg_widget.deleteLater()
00125
00126
00127 dialog_widget = widget_layout.parentWidget().parentWidget()
00128 dialog_widget.resize(dialog_widget.minimumSize())
00129 for l in item_list:
00130 params_hor_sub_widget = QWidget()
00131 params_hor_layout = QHBoxLayout(params_hor_sub_widget)
00132 for k in l:
00133 param_name = k[0]
00134 param_type = k[2]
00135 name_widget = QLabel(param_name + ": ")
00136 if param_type == 'string' or param_type == 'int':
00137 k[1] = QTextEdit()
00138 k[1].setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored)
00139 k[1].setMinimumSize(0, 30)
00140 k[1].append("")
00141 elif param_type == 'bool':
00142 k[1] = QTextEdit()
00143 k[1] = QComboBox()
00144 k[1].setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored)
00145 k[1].setMinimumSize(0, 30)
00146
00147 k[1].addItem("True", True)
00148 k[1].addItem("False", False)
00149
00150 params_hor_layout.addWidget(name_widget)
00151 params_hor_layout.addWidget(k[1])
00152 widget_layout.addWidget(params_hor_sub_widget)
00153
00154 def _push_param(self):
00155 params_item = []
00156 for k in self.params:
00157 param_name = k[0]
00158 param_type = k[1]
00159 param_widget = None
00160 params_item.append([param_name, param_widget, param_type])
00161 self.params_list.append(params_item)
00162
00163 def _pop_param(self):
00164 if len(self.params_list) > 1:
00165 self.params_list.pop()
00166 else:
00167 pass
00168
00169 def _get_param_list(self):
00170 return self.params_list
00171 pass
00172
00173
00174
00175
00176
00177
00178 class ConductorGraph(Plugin):
00179
00180
00181 signal_deferred_fit_in_view = Signal()
00182 signal_update_conductor_graph = Signal()
00183
00184
00185
00186
00187 link_strength_colours = {'very_strong': QColor("lime"), 'strong': QColor("chartreuse"), 'normal': QColor("yellow"), 'weak': QColor("orange"), 'very_weak': QColor("red"), 'missing': QColor("powderblue")}
00188
00189 def __init__(self, context):
00190 self._context = context
00191 super(ConductorGraph, self).__init__(context)
00192 self.initialised = False
00193 self.setObjectName('Conductor Graph')
00194 self._node_items = None
00195 self._edge_items = None
00196 self._node_item_events = {}
00197 self._edge_item_events = {}
00198 self._client_info_list = {}
00199 self._widget = QWidget()
00200 self.cur_selected_client_name = ""
00201 self.pre_selected_client_name = ""
00202
00203 self.dotcode_factory = PydotFactory()
00204
00205 self.dotcode_generator = ConductorGraphDotcodeGenerator()
00206 self.dot_to_qt = DotToQtGenerator()
00207
00208 self._graph = ConductorGraphInfo(self._update_conductor_graph_relay, self._set_network_statisics)
00209
00210 rospack = rospkg.RosPack()
00211 ui_file = os.path.join(rospack.get_path('concert_conductor_graph'), 'ui', 'conductor_graph.ui')
00212 loadUi(ui_file, self._widget, {'InteractiveGraphicsView': InteractiveGraphicsView})
00213 self._widget.setObjectName('ConductorGraphUi')
00214
00215 if context.serial_number() > 1:
00216 self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))
00217
00218 self._scene = QGraphicsScene()
00219 self._scene.setBackgroundBrush(Qt.white)
00220 self._widget.graphics_view.setScene(self._scene)
00221
00222 self._widget.highlight_connections_check_box.toggled.connect(self._redraw_graph_view)
00223 self._widget.auto_fit_graph_check_box.toggled.connect(self._redraw_graph_view)
00224 self._widget.clusters_check_box.toggled.connect(self._redraw_graph_view)
00225
00226 self.signal_deferred_fit_in_view.connect(self._fit_in_view, Qt.QueuedConnection)
00227 self.signal_deferred_fit_in_view.emit()
00228
00229 self._widget.tabWidget.currentChanged.connect(self._change_client_tab)
00230 self.signal_update_conductor_graph.connect(self._update_conductor_graph)
00231
00232 context.add_widget(self._widget)
00233
00234 def restore_settings(self, plugin_settings, instance_settings):
00235 self.initialised = True
00236 self._update_conductor_graph()
00237
00238 def shutdown_plugin(self):
00239 self._graph.shutdown()
00240
00241 def _update_conductor_graph(self):
00242 if self.initialised:
00243 self._redraw_graph_view()
00244 self._update_client_tab()
00245
00246 def _update_conductor_graph_relay(self):
00247 """
00248 This seems a bit obtuse, but we can't just dump the _update_conductor_graph callback on the underlying
00249 conductor graph info and trigger it from there since that trigger will operate from a ros thread and pyqt
00250 will crash trying to co-ordinate gui changes from an external thread. We need to relay via a signal.
00251 """
00252 self.signal_update_conductor_graph.emit()
00253
00254 def _update_client_tab(self):
00255
00256 self.pre_selected_client_name = self.cur_selected_client_name
00257 self._widget.tabWidget.clear()
00258
00259 for k in self._graph.concert_clients.values():
00260
00261 if k.state not in [concert_msgs.ConcertClientState.AVAILABLE, concert_msgs.ConcertClientState.MISSING, concert_msgs.ConcertClientState.UNINVITED]:
00262 continue
00263
00264 main_widget = QWidget()
00265
00266 ver_layout = QVBoxLayout(main_widget)
00267
00268 ver_layout.setContentsMargins(9, 9, 9, 9)
00269 ver_layout.setSizeConstraint(ver_layout.SetDefaultConstraint)
00270
00271
00272 sub_widget = QWidget()
00273 sub_widget.setAccessibleName('sub_widget')
00274
00275 ver_layout.addWidget(sub_widget)
00276
00277
00278 context_label = QLabel()
00279 context_label.setText("Client information")
00280 ver_layout.addWidget(context_label)
00281
00282 app_context_widget = QPlainTextEdit()
00283 app_context_widget.setObjectName(k.concert_alias + '_' + 'app_context_widget')
00284 app_context_widget.setAccessibleName('app_context_widget')
00285 app_context_widget.appendHtml(k.get_rapp_context())
00286 app_context_widget.setReadOnly(True)
00287
00288 cursor = app_context_widget.textCursor()
00289 cursor.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor, 0)
00290 app_context_widget.setTextCursor(cursor)
00291 ver_layout.addWidget(app_context_widget)
00292
00293
00294 path = ""
00295 if k.is_new:
00296
00297 path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../resources/images/new.gif")
00298
00299
00300 self._widget.tabWidget.addTab(main_widget, QIcon(path), k.concert_alias)
00301
00302
00303 for k in range(self._widget.tabWidget.count()):
00304 tab_text = self._widget.tabWidget.tabText(k)
00305 if tab_text == self.pre_selected_client_name:
00306 self._widget.tabWidget.setCurrentIndex(k)
00307
00308 def _change_client_tab(self, index):
00309 self.cur_selected_client_name = self._widget.tabWidget.tabText(self._widget.tabWidget.currentIndex())
00310
00311 def _set_network_statisics(self):
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321 pass
00322
00323 def _redraw_graph_view(self):
00324
00325
00326 current_dotcode = self.dotcode_generator.generate_dotcode(
00327 conductor_graph_instance=self._graph,
00328 dotcode_factory=self.dotcode_factory,
00329 clusters=self._widget.clusters_check_box.isChecked()
00330 )
00331
00332 self._scene.clear()
00333 self._node_item_events = {}
00334 self._edge_item_events = {}
00335 self._node_items = None
00336 self._edge_items = None
00337
00338 highlight_level = 3 if self._widget.highlight_connections_check_box.isChecked() else 1
00339
00340
00341 (nodes, edges) = self.dot_to_qt.dotcode_to_qt_items(current_dotcode,
00342 highlight_level=highlight_level,
00343 same_label_siblings=True)
00344 self._node_items = nodes
00345 self._edge_items = edges
00346
00347
00348 for node_item in nodes.itervalues():
00349
00350
00351
00352 self._node_item_events[node_item._label.text()] = GraphEventHandler(self._widget.tabWidget, node_item, node_item.hoverEnterEvent)
00353 node_item.hoverEnterEvent = self._node_item_events[node_item._label.text()].NodeEvent
00354
00355 self._scene.addItem(node_item)
00356
00357
00358 for edge_items in edges.itervalues():
00359 for edge_item in edge_items:
00360
00361
00362 self._edge_item_events[edge_item._label.text()] = GraphEventHandler(self._widget.tabWidget, edge_item, edge_item._label.hoverEnterEvent)
00363 edge_item._label.hoverEnterEvent = self._edge_item_events[edge_item._label.text()].EdgeEvent
00364
00365
00366
00367
00368 edge_item.add_to_scene(self._scene)
00369
00370
00371 edge_dst_name = edge_item.to_node._label.text()
00372 if edge_dst_name in self._graph.concert_clients.keys():
00373 link_strength_colour = ConductorGraph.link_strength_colours[self._graph.concert_clients[edge_dst_name].get_connection_strength()]
00374 edge_item._default_color = link_strength_colour
00375 edge_item.set_node_color(link_strength_colour)
00376
00377 edge_item.setToolTip(str(self._graph.concert_clients[edge_dst_name].msg.conn_stats))
00378
00379 self._scene.setSceneRect(self._scene.itemsBoundingRect())
00380
00381 if self._widget.auto_fit_graph_check_box.isChecked():
00382 self._fit_in_view()
00383
00384 def _fit_in_view(self):
00385 self._widget.graphics_view.fitInView(self._scene.itemsBoundingRect(), Qt.KeepAspectRatio)