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

Source Code for Module node_manager_fkie.sync_dialog

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2012, Fraunhofer FKIE/US, Alexander Tiderko 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Fraunhofer nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 32   
 33  from python_qt_binding.QtCore import QObject, Qt, Signal 
 34  from python_qt_binding.QtGui import QIcon 
 35  import os 
 36  import threading 
 37   
 38  import rospy 
 39   
 40  from node_manager_fkie.common import is_package, utf8 
 41  from node_manager_fkie.detailed_msg_box import MessageBox 
 42  from node_manager_fkie.editor.yaml_highlighter import YamlHighlighter 
 43  import node_manager_fkie as nm 
 44   
 45  from .editor import TextEdit 
 46  try: 
 47      from python_qt_binding.QtGui import QApplication, QVBoxLayout, QSizePolicy 
 48      from python_qt_binding.QtGui import QComboBox, QDialog, QDialogButtonBox, QFileDialog, QToolButton 
 49  except: 
 50      from python_qt_binding.QtWidgets import QApplication, QVBoxLayout, QSizePolicy 
 51      from python_qt_binding.QtWidgets import QComboBox, QDialog, QDialogButtonBox, QFileDialog, QToolButton 
52 53 54 -class SyncHighlighter(YamlHighlighter):
55 ''' 56 Enabled the syntax highlightning for the sync interface. 57 ''' 58
59 - def __init__(self, parent=None):
60 YamlHighlighter.__init__(self, parent) 61 tagList = ["\\bignore_hosts\\b", "\\bsync_hosts\\b", 62 "\\bignore_nodes\\b", "\\bsync_nodes\\b", 63 "\\bignore_topics\\b", "\\bignore_publishers\\b", 64 "\\bignore_topics\\b", "\\bsync_topics\\b", 65 "\\bignore_subscribers\\b", "\\bsync_services\\b", 66 "\\bsync_topics_on_demand\\b", "\\bsync_remote_nodes\\b", 67 "\\bignore_services\\b", "\\bdo_not_sync\\b", 68 "\\bresync_on_reconnect\\b", "\\bresync_on_reconnect_timeout\\b", 69 ] 70 for tag in tagList: 71 self.rules.append((self._create_regexp(tag), self._create_format(Qt.darkBlue))) 72 self.rules.append((self._create_regexp("\\b\\*|\\*\\B|\\/\\*"), self._create_format(Qt.darkGreen, 'bold')))
73
74 75 -class SyncDialog(QDialog):
76 ''' 77 A dialog to set the sync options. 78 ''' 79
80 - def __init__(self, parent=None):
81 QDialog.__init__(self, parent) 82 # self.host = host 83 self.setWindowIcon(QIcon(":/icons/irondevil_sync.png")) 84 self.setWindowTitle('Sync') 85 self.verticalLayout = QVBoxLayout(self) 86 self.verticalLayout.setObjectName("verticalLayout") 87 self.resize(350, 190) 88 89 self.toolButton_SyncAll = QToolButton(self) 90 sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 91 sizePolicy.setHorizontalStretch(0) 92 sizePolicy.setVerticalStretch(2) 93 sizePolicy.setHeightForWidth(self.toolButton_SyncAll.sizePolicy().hasHeightForWidth()) 94 self.toolButton_SyncAll.setSizePolicy(sizePolicy) 95 self.toolButton_SyncAll.setObjectName("toolButton_SyncAll") 96 self.verticalLayout.addWidget(self.toolButton_SyncAll) 97 self.toolButton_SyncAll.setText(self._translate("Sync All")) 98 self.toolButton_SyncAll.clicked.connect(self._on_sync_all_clicked) 99 100 # self.toolButton_SyncAllAnyMsg = QToolButton(self) 101 # sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 102 # sizePolicy.setHorizontalStretch(0) 103 # sizePolicy.setVerticalStretch(1) 104 # sizePolicy.setHeightForWidth(self.toolButton_SyncAllAnyMsg.sizePolicy().hasHeightForWidth()) 105 # self.toolButton_SyncAllAnyMsg.setSizePolicy(sizePolicy) 106 # self.toolButton_SyncAllAnyMsg.setObjectName("toolButton_SyncAllAnyMsg") 107 # self.verticalLayout.addWidget(self.toolButton_SyncAllAnyMsg) 108 # self.toolButton_SyncAllAnyMsg.setText(self._translate("Sync all (+AnyMsg)")) 109 # self.toolButton_SyncAllAnyMsg.clicked.connect(self._on_sync_all_anymsg_clicked) 110 111 self.toolButton_SyncTopicOnDemand = QToolButton(self) 112 sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 113 sizePolicy.setHorizontalStretch(0) 114 sizePolicy.setVerticalStretch(1) 115 sizePolicy.setHeightForWidth(self.toolButton_SyncTopicOnDemand.sizePolicy().hasHeightForWidth()) 116 self.toolButton_SyncTopicOnDemand.setSizePolicy(sizePolicy) 117 self.toolButton_SyncTopicOnDemand.setObjectName("toolButton_SyncTopicOnDemand") 118 self.verticalLayout.addWidget(self.toolButton_SyncTopicOnDemand) 119 self.toolButton_SyncTopicOnDemand.setText(self._translate("Sync only topics on demand")) 120 self.toolButton_SyncTopicOnDemand.clicked.connect(self._on_sync_topics_on_demand_clicked) 121 122 self.toolButton_SelectInterface = QToolButton(self) 123 sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 124 sizePolicy.setHorizontalStretch(0) 125 sizePolicy.setVerticalStretch(1) 126 sizePolicy.setHeightForWidth(self.toolButton_SelectInterface.sizePolicy().hasHeightForWidth()) 127 self.toolButton_SelectInterface.setSizePolicy(sizePolicy) 128 self.toolButton_SelectInterface.setObjectName("toolButton_SelectInterface") 129 self.verticalLayout.addWidget(self.toolButton_SelectInterface) 130 self.toolButton_SelectInterface.setText(self._translate("Select an interface")) 131 self.toolButton_SelectInterface.clicked.connect(self._on_select_interface_clicked) 132 133 self.interface_field = QComboBox(self) 134 self.interface_field.setInsertPolicy(QComboBox.InsertAlphabetically) 135 self.interface_field.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) 136 self.interface_field.setEditable(True) 137 self.interface_field.setVisible(False) 138 self.interface_field.setObjectName("interface_field") 139 self.verticalLayout.addWidget(self.interface_field) 140 self.interface_field.currentIndexChanged[str].connect(self._on_interface_selected) 141 142 self.toolButton_EditInterface = QToolButton(self) 143 sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 144 sizePolicy.setHorizontalStretch(0) 145 sizePolicy.setVerticalStretch(1) 146 sizePolicy.setHeightForWidth(self.toolButton_EditInterface.sizePolicy().hasHeightForWidth()) 147 self.toolButton_EditInterface.setSizePolicy(sizePolicy) 148 self.toolButton_EditInterface.setObjectName("toolButton_EditInterface") 149 self.verticalLayout.addWidget(self.toolButton_EditInterface) 150 self.toolButton_EditInterface.setText(self._translate("Edit selected interface")) 151 self.toolButton_EditInterface.clicked.connect(self._on_edit_interface_clicked) 152 self.toolButton_EditInterface.setVisible(False) 153 154 self.toolButton_CreateInterface = QToolButton(self) 155 sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 156 sizePolicy.setHorizontalStretch(0) 157 sizePolicy.setVerticalStretch(1) 158 sizePolicy.setHeightForWidth(self.toolButton_CreateInterface.sizePolicy().hasHeightForWidth()) 159 self.toolButton_CreateInterface.setSizePolicy(sizePolicy) 160 self.toolButton_CreateInterface.setObjectName("toolButton_CreateInterface") 161 self.verticalLayout.addWidget(self.toolButton_CreateInterface) 162 self.toolButton_CreateInterface.setText(self._translate("Create an interface")) 163 self.toolButton_CreateInterface.clicked.connect(self._on_create_interface_clicked) 164 self.toolButton_CreateInterface.setVisible(False) 165 166 self.textedit = TextEdit('', self) 167 self.hl = SyncHighlighter(self.textedit.document()) 168 sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 169 self.textedit.setSizePolicy(sizePolicy) 170 self.textedit.setObjectName("syncedit") 171 self.verticalLayout.addWidget(self.textedit) 172 self.textedit.setVisible(False) 173 174 self._fill_interface_thread = None 175 self._interfaces_files = None 176 self._sync_args = [] 177 self._interface_filename = None 178 179 self.buttonBox = QDialogButtonBox(self) 180 self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel) 181 self.buttonBox.setOrientation(Qt.Horizontal) 182 self.buttonBox.setObjectName("buttonBox") 183 self.verticalLayout.addWidget(self.buttonBox) 184 self.buttonBox.accepted.connect(self.accept) 185 self.buttonBox.rejected.connect(self.reject) 186 self._new_iface = True
187
188 - def _translate(self, text):
189 if hasattr(QApplication, "UnicodeUTF8"): 190 return QApplication.translate("Form", text, None, QApplication.UnicodeUTF8) 191 else: 192 return QApplication.translate("Form", text, None)
193 194 @property
195 - def sync_args(self):
196 return self._sync_args
197 198 @property
199 - def interface_filename(self):
200 return self._interface_filename
201
202 - def _on_sync_all_clicked(self):
203 self.setResult(QDialog.Accepted) 204 self._sync_args = [] 205 self._sync_args.append(''.join(['_interface_url:=', "'.'"])) 206 self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'False'])) 207 self._sync_args.append(''.join(['_ignore_hosts:=', '[]'])) 208 self._sync_args.append(''.join(['_sync_hosts:=', '[]'])) 209 self._sync_args.append(''.join(['_ignore_nodes:=', '[]'])) 210 self._sync_args.append(''.join(['_sync_nodes:=', '[]'])) 211 self._sync_args.append(''.join(['_ignore_topics:=', '[]'])) 212 self._sync_args.append(''.join(['_ignore_publishers:=', '[]'])) 213 self._sync_args.append(''.join(['_ignore_subscribers:=', '[]'])) 214 self._sync_args.append(''.join(['_sync_topics:=', '[]'])) 215 self._sync_args.append(''.join(['_ignore_services:=', '[]'])) 216 self._sync_args.append(''.join(['_sync_services:=', '[]'])) 217 self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False'])) 218 self._interface_filename = None 219 self.accept()
220 221 # def _on_sync_all_anymsg_clicked(self): 222 # self._sync_args = [] 223 # self._sync_args.append(''.join(['_interface_url:=', "'.'"])) 224 # self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'True'])) 225 # self._sync_args.append(''.join(['_ignore_hosts:=', '[]'])) 226 # self._sync_args.append(''.join(['_sync_hosts:=', '[]'])) 227 # self._sync_args.append(''.join(['_ignore_nodes:=', '[]'])) 228 # self._sync_args.append(''.join(['_sync_nodes:=', '[]'])) 229 # self._sync_args.append(''.join(['_ignore_topics:=', '[]'])) 230 # self._sync_args.append(''.join(['_sync_topics:=', '[/*]'])) 231 # self._sync_args.append(''.join(['_ignore_services:=', '[]'])) 232 # self._sync_args.append(''.join(['_sync_services:=', '[]'])) 233 # self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False'])) 234 # self._interface_filename = None 235 # self.accept() 236
238 self._sync_args = [] 239 self._sync_args.append(''.join(['_interface_url:=', "'.'"])) 240 self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'True'])) 241 self._sync_args.append(''.join(['_ignore_hosts:=', '[]'])) 242 self._sync_args.append(''.join(['_sync_hosts:=', '[]'])) 243 self._sync_args.append(''.join(['_ignore_nodes:=', '[]'])) 244 self._sync_args.append(''.join(['_sync_nodes:=', '[]'])) 245 self._sync_args.append(''.join(['_ignore_topics:=', '[]'])) 246 self._sync_args.append(''.join(['_ignore_publishers:=', '[]'])) 247 self._sync_args.append(''.join(['_ignore_subscribers:=', '[]'])) 248 self._sync_args.append(''.join(['_sync_topics:=', '[/only_on_demand]'])) 249 self._sync_args.append(''.join(['_ignore_services:=', '[/*]'])) 250 self._sync_args.append(''.join(['_sync_services:=', '[]'])) 251 self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False'])) 252 self._interface_filename = None 253 self.accept()
254
256 self.toolButton_SyncAll.setVisible(False) 257 # self.toolButton_SyncAllAnyMsg.setVisible(False) 258 self.toolButton_SyncTopicOnDemand.setVisible(False) 259 self.toolButton_SelectInterface.setVisible(False) 260 self.interface_field.setVisible(True) 261 self.toolButton_CreateInterface.setVisible(True) 262 self.toolButton_EditInterface.setVisible(True) 263 self.toolButton_EditInterface.setEnabled(False) 264 self.textedit.setVisible(False) 265 # # fill the interfaces 266 if self._interfaces_files is None: 267 self.interface_field.addItems(['interface searching...']) 268 self.interface_field.setCurrentIndex(0) 269 self._fill_interface_thread = InterfacesThread() 270 self._fill_interface_thread.interfaces.connect(self._fill_interfaces) 271 self._fill_interface_thread.start() 272 else: 273 self.toolButton_EditInterface.setEnabled(self.interface_field.currentText() in self._interfaces_files) 274 self.buttonBox.clear() 275 self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) 276 self.interface_field.setFocus(Qt.TabFocusReason) 277 self.resize(350, 80)
278
279 - def _fill_interfaces(self, interfaces_files):
280 self._interfaces_files = interfaces_files 281 self.interface_field.clear() 282 self.interface_field.clearEditText() 283 self.interface_field.addItems(self._interfaces_files.keys())
284
285 - def _on_interface_selected(self, interface):
286 if self._interfaces_files and interface in self._interfaces_files: 287 self._sync_args = [] 288 self._sync_args.append(''.join(['_interface_url:=', interface])) 289 self.toolButton_EditInterface.setEnabled(True) 290 else: 291 self.toolButton_EditInterface.setEnabled(False)
292
293 - def accept(self):
294 if self.textedit.isVisible(): 295 try: 296 tmp_file = os.path.join(nm.screen().LOG_PATH, 'tmp_sync_interface.sync') 297 with open(tmp_file, 'w+') as f: 298 f.write(self.textedit.toPlainText()) 299 from master_discovery_fkie.common import read_interface 300 read_interface(tmp_file) 301 if not self._new_iface and self.interface_field.currentText() in self._interfaces_files: 302 fileName = self._interfaces_files[self.interface_field.currentText()] 303 else: 304 fileName, _ = QFileDialog.getSaveFileName(self, 'Save sync interface', '/home', "Sync Files (*.sync)") 305 if fileName: 306 with open(fileName, 'w+') as f: 307 self._interface_filename = fileName 308 f.write(self.textedit.toPlainText()) 309 if self._new_iface: 310 self.interface_field.clear() 311 self._interfaces_files = None 312 self._on_select_interface_clicked() 313 # QDialog.accept(self) 314 # self.resetView() 315 except Exception as e: 316 MessageBox.warning(self, "Create sync interface", 317 "Error while create interface", 318 utf8(e)) 319 elif self.interface_field.isVisible(): 320 interface = self.interface_field.currentText() 321 if self._interfaces_files and interface in self._interfaces_files: 322 self._interface_filename = self._interfaces_files[interface] 323 self._sync_args = [] 324 self._sync_args.append(''.join(['_interface_url:=', interface])) 325 QDialog.accept(self) 326 self.resetView() 327 else: 328 QDialog.accept(self) 329 self.resetView()
330
331 - def reject(self):
332 if self.textedit.isVisible(): 333 self._on_select_interface_clicked() 334 else: 335 QDialog.reject(self) 336 self.resetView()
337
339 self._new_iface = True 340 self.interface_field.setVisible(False) 341 self.toolButton_CreateInterface.setVisible(False) 342 self.toolButton_EditInterface.setVisible(False) 343 self.textedit.setVisible(True) 344 self.textedit.setText("# The ignore_* lists will be processed first.\n" 345 "# For ignore/sync nodes, topics or services\n" 346 "# use follow declaration:\n" 347 "#{param name}: \n" 348 "# - {ros name}\n" 349 "# or for selected hosts:\n" 350 "# - {host name}:\n" 351 "# - {ros name}\n\n" 352 "# you can use follow wildcard: '*', but not as a first character\n" 353 "ignore_hosts:\n" 354 "sync_hosts:\n\n" 355 "ignore_nodes:\n" 356 "sync_nodes:\n\n" 357 "ignore_topics:\n" 358 "ignore_publishers:\n" 359 "ignore_subscribers:\n" 360 "sync_topics:\n\n" 361 "ignore_services:\n" 362 " - /*get_loggers\n" 363 " - /*set_logger_level\n" 364 "sync_services:\n\n" 365 "# If sync_topics_on_demand is True the local subscribed and published topics\n" 366 "# are synchronized with remote even if they are not in the sync_* list.\n" 367 "sync_topics_on_demand: False\n\n" 368 "# The nodes which are running not at the same host as the ROS master are not\n" 369 "# synchronized by default. Use sync_remote_nodes to sync these nodes also.\n" 370 "sync_remote_nodes: False\n\n" 371 ) 372 self.resize(350, 300)
373
375 self._new_iface = False 376 self.interface_field.setVisible(False) 377 self.toolButton_CreateInterface.setVisible(False) 378 self.toolButton_EditInterface.setVisible(False) 379 self.textedit.setVisible(True) 380 if self.interface_field.currentText() in self._interfaces_files: 381 try: 382 with open(self._interfaces_files[self.interface_field.currentText()], 'rw') as f: 383 iface = f.read() 384 self.textedit.setText(iface) 385 except Exception as e: 386 MessageBox.warning(self, "Edit sync interface", 387 "Error while open interface", 388 utf8(e)) 389 self.resize(350, 300)
390
391 - def resetView(self):
392 self.toolButton_SyncAll.setVisible(True) 393 # self.toolButton_SyncAllAnyMsg.setVisible(True) 394 self.toolButton_SyncTopicOnDemand.setVisible(True) 395 self.toolButton_SelectInterface.setVisible(True) 396 self.interface_field.setVisible(False) 397 self.toolButton_CreateInterface.setVisible(False) 398 self.toolButton_EditInterface.setVisible(False) 399 self.textedit.setVisible(False) 400 self.buttonBox.clear() 401 self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel) 402 self.resize(350, 160)
403
404 405 -class InterfacesThread(QObject, threading.Thread):
406 ''' 407 A thread to list all sync interface files and 408 publish there be sending a QT signal. 409 ''' 410 interfaces = Signal(dict) 411 ''' 412 @ivar: interfaces is a signal, which is emitted, if a list with sync files was created. 413 ''' 414
415 - def __init__(self):
416 ''' 417 ''' 418 QObject.__init__(self) 419 threading.Thread.__init__(self) 420 self._interfaces_files = None 421 self.setDaemon(True)
422
423 - def run(self):
424 ''' 425 ''' 426 try: 427 # fill the input fields 428 self.root_paths = [os.path.normpath(p) for p in os.getenv("ROS_PACKAGE_PATH").split(':')] 429 self._interfaces_files = {} 430 for p in self.root_paths: 431 ret = self._getInterfaces(p) 432 self._interfaces_files = dict(ret.items() + self._interfaces_files.items()) 433 self.interfaces.emit(self._interfaces_files) 434 except: 435 import traceback 436 formatted_lines = traceback.format_exc(1).splitlines() 437 rospy.logwarn("Error while list sync interfaces:\n\t%s", formatted_lines[-1])
438
439 - def _getInterfaces(self, path, package=None):
440 result = {} 441 if os.path.isdir(path): 442 fileList = os.listdir(path) 443 # set package, if it is currently None and one found 444 if not package: 445 # detect package 446 if is_package(fileList): 447 package = os.path.basename(path) 448 for f in fileList: 449 ret = self._getInterfaces(os.path.join(path, f), package) 450 result = dict(ret.items() + result.items()) 451 elif package and os.path.isfile(path) and path.endswith('.sync'): 452 # create a selection for binaries 453 return {''.join(['pkg://', package, '///', os.path.basename(path)]): path} 454 return result
455