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

Source Code for Module node_manager_fkie.editor.editor

  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 QFileInfo, QPoint, QSize, Qt, Signal 
 34  from python_qt_binding.QtGui import QIcon, QKeySequence, QTextCursor, QTextDocument 
 35  import os 
 36   
 37  import rospy 
 38   
 39  from node_manager_fkie.common import package_name 
 40  from node_manager_fkie.run_dialog import PackageDialog 
 41  import node_manager_fkie as nm 
 42   
 43  from .line_number_widget import LineNumberWidget 
 44  from .text_edit import TextEdit 
 45  from .text_search_frame import TextSearchFrame 
 46  from .text_search_thread import TextSearchThread 
 47   
 48  try: 
 49      from python_qt_binding.QtGui import QApplication, QAction, QLineEdit, QMessageBox, QWidget, QMainWindow 
 50      from python_qt_binding.QtGui import QDialog, QInputDialog, QLabel, QMenu, QPushButton, QTabWidget 
 51      from python_qt_binding.QtGui import QHBoxLayout, QVBoxLayout, QSpacerItem, QSplitter, QSizePolicy 
 52  except: 
 53      from python_qt_binding.QtWidgets import QApplication, QAction, QLineEdit, QMessageBox, QWidget, QMainWindow 
 54      from python_qt_binding.QtWidgets import QDialog, QInputDialog, QLabel, QMenu, QPushButton, QTabWidget 
 55      from python_qt_binding.QtWidgets import QHBoxLayout, QVBoxLayout, QSpacerItem, QSplitter, QSizePolicy 
 56   
 57   
58 -class EditorTabWidget(QTabWidget):
59 ''' 60 This class was overloaded to close tabs on middle mouse click 61 ''' 62
63 - def mouseReleaseEvent(self, event):
64 if event.button() == Qt.MidButton: 65 close_index = self.tabBar().tabAt(event.pos()) 66 if close_index > -1: 67 self.tabCloseRequested.emit(close_index) 68 event.setAccepted(True) 69 if not event.isAccepted(): 70 QTabWidget.mouseReleaseEvent(event)
71
72 - def currentWidget(self):
73 ''' 74 This is an overloaded function to use with LineNumberWidget 75 ''' 76 return QTabWidget.currentWidget(self).get_text_edit()
77
78 - def widget(self, index):
79 ''' 80 This is an overloaded function to use with LineNumberWidget 81 ''' 82 return QTabWidget.widget(self, index).get_text_edit()
83 84
85 -class Editor(QMainWindow):
86 ''' 87 Creates a dialog to edit a launch file. 88 ''' 89 finished_signal = Signal(list) 90 ''' 91 finished_signal has as parameter the filenames of the initialization and is emitted, if this 92 dialog was closed. 93 ''' 94
95 - def __init__(self, filenames, search_text='', parent=None):
96 ''' 97 @param filenames: a list with filenames. The last one will be activated. 98 @type filenames: C{[str, ...]} 99 @param search_text: if not empty, searches in new document for first occurrence of the given text 100 @type search_text: C{str} (Default: C{Empty String}) 101 ''' 102 QMainWindow.__init__(self, parent) 103 self.setObjectName(' - '.join(['Editor', str(filenames)])) 104 self.setAttribute(Qt.WA_DeleteOnClose, True) 105 self.setWindowFlags(Qt.Window) 106 self.mIcon = QIcon(":/icons/crystal_clear_edit_launch.png") 107 self._error_icon = QIcon(":/icons/crystal_clear_warning.png") 108 self._empty_icon = QIcon() 109 self.setWindowIcon(self.mIcon) 110 window_title = "ROSLaunch Editor" 111 if filenames: 112 window_title = self.__getTabName(filenames[0]) 113 self.setWindowTitle(window_title) 114 self.init_filenames = list(filenames) 115 self._search_thread = None 116 # list with all open files 117 self.files = [] 118 # create tabs for files 119 self.main_widget = QWidget(self) 120 self.verticalLayout = QVBoxLayout(self.main_widget) 121 self.verticalLayout.setContentsMargins(0, 0, 0, 0) 122 self.verticalLayout.setSpacing(1) 123 self.verticalLayout.setObjectName("verticalLayout") 124 125 self.tabWidget = EditorTabWidget(self) 126 self.tabWidget.setTabPosition(QTabWidget.North) 127 self.tabWidget.setDocumentMode(True) 128 self.tabWidget.setTabsClosable(True) 129 self.tabWidget.setMovable(False) 130 self.tabWidget.setObjectName("tabWidget") 131 self.tabWidget.tabCloseRequested.connect(self.on_close_tab) 132 133 self.verticalLayout.addWidget(self.tabWidget) 134 self.buttons = self._create_buttons() 135 self.verticalLayout.addWidget(self.buttons) 136 self.setCentralWidget(self.main_widget) 137 138 self.find_dialog = TextSearchFrame(self.tabWidget, self) 139 self.find_dialog.search_result_signal.connect(self.on_search_result) 140 self.find_dialog.replace_signal.connect(self.on_replace) 141 self.addDockWidget(Qt.RightDockWidgetArea, self.find_dialog) 142 # open the files 143 for f in filenames: 144 if f: 145 self.on_load_request(os.path.normpath(f), search_text) 146 self.readSettings() 147 self.find_dialog.setVisible(False)
148 149 # def __del__(self): 150 # print "******** destroy", self.objectName() 151
152 - def _create_buttons(self):
153 # create the buttons line 154 self.buttons = QWidget(self) 155 self.horizontalLayout = QHBoxLayout(self.buttons) 156 self.horizontalLayout.setContentsMargins(4, 0, 4, 0) 157 self.horizontalLayout.setObjectName("horizontalLayout") 158 # add the search button 159 self.searchButton = QPushButton(self) 160 self.searchButton.setObjectName("searchButton") 161 # self.searchButton.clicked.connect(self.on_shortcut_find) 162 self.searchButton.toggled.connect(self.on_toggled_find) 163 self.searchButton.setText(self._translate("&Find")) 164 self.searchButton.setToolTip('Open a search dialog (Ctrl+F)') 165 self.searchButton.setFlat(True) 166 self.searchButton.setCheckable(True) 167 self.horizontalLayout.addWidget(self.searchButton) 168 # add the replace button 169 self.replaceButton = QPushButton(self) 170 self.replaceButton.setObjectName("replaceButton") 171 # self.replaceButton.clicked.connect(self.on_shortcut_replace) 172 self.replaceButton.toggled.connect(self.on_toggled_replace) 173 self.replaceButton.setText(self._translate("&Replace")) 174 self.replaceButton.setToolTip('Open a search&replace dialog (Ctrl+R)') 175 self.replaceButton.setFlat(True) 176 self.replaceButton.setCheckable(True) 177 self.horizontalLayout.addWidget(self.replaceButton) 178 # add the goto button 179 self.gotoButton = QPushButton(self) 180 self.gotoButton.setObjectName("gotoButton") 181 self.gotoButton.clicked.connect(self.on_shortcut_goto) 182 self.gotoButton.setText(self._translate("&Goto line")) 183 self.gotoButton.setShortcut("Ctrl+G") 184 self.gotoButton.setToolTip('Open a goto dialog (Ctrl+G)') 185 self.gotoButton.setFlat(True) 186 self.horizontalLayout.addWidget(self.gotoButton) 187 # add a tag button 188 self.tagButton = self._create_tag_button(self) 189 self.horizontalLayout.addWidget(self.tagButton) 190 # add spacer 191 spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 192 self.horizontalLayout.addItem(spacerItem) 193 # add line number label 194 self.pos_label = QLabel() 195 self.horizontalLayout.addWidget(self.pos_label) 196 # add spacer 197 spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 198 self.horizontalLayout.addItem(spacerItem) 199 # add save button 200 self.saveButton = QPushButton(self) 201 self.saveButton.setObjectName("saveButton") 202 self.saveButton.clicked.connect(self.on_saveButton_clicked) 203 self.saveButton.setText(self._translate("&Save")) 204 self.saveButton.setShortcut("Ctrl+S") 205 self.saveButton.setToolTip('Save the changes to the file (Ctrl+S)') 206 self.saveButton.setFlat(True) 207 self.horizontalLayout.addWidget(self.saveButton) 208 return self.buttons
209
210 - def keyPressEvent(self, event):
211 ''' 212 Enable the shortcats for search and replace 213 ''' 214 if event.key() == Qt.Key_Escape: 215 self.reject() 216 elif event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_F: 217 if self.tabWidget.currentWidget().hasFocus(): 218 if not self.searchButton.isChecked(): 219 self.searchButton.setChecked(True) 220 else: 221 self.on_toggled_find(True) 222 else: 223 self.searchButton.setChecked(not self.searchButton.isChecked()) 224 elif event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_R: 225 if self.tabWidget.currentWidget().hasFocus(): 226 if not self.replaceButton.isChecked(): 227 self.replaceButton.setChecked(True) 228 else: 229 self.on_toggled_replace(True) 230 else: 231 self.replaceButton.setChecked(not self.replaceButton.isChecked()) 232 else: 233 event.accept() 234 QMainWindow.keyPressEvent(self, event)
235
236 - def _translate(self, text):
237 if hasattr(QApplication, "UnicodeUTF8"): 238 return QApplication.translate("Editor", text, None, QApplication.UnicodeUTF8) 239 else: 240 return QApplication.translate("Editor", text, None)
241
242 - def readSettings(self):
243 if nm.settings().store_geometry: 244 settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) 245 settings.beginGroup("editor") 246 maximized = settings.value("maximized", 'false') == 'true' 247 if maximized: 248 self.showMaximized() 249 else: 250 self.resize(settings.value("size", QSize(800, 640))) 251 self.move(settings.value("pos", QPoint(0, 0))) 252 try: 253 self.restoreState(settings.value("window_state")) 254 except: 255 import traceback 256 print traceback.format_exc() 257 settings.endGroup()
258
259 - def storeSetting(self):
260 if nm.settings().store_geometry: 261 settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) 262 settings.beginGroup("editor") 263 settings.setValue("size", self.size()) 264 settings.setValue("pos", self.pos()) 265 settings.setValue("maximized", self.isMaximized()) 266 settings.setValue("window_state", self.saveState()) 267 settings.endGroup()
268
269 - def on_load_request(self, filename, search_text=''):
270 ''' 271 Loads a file in a new tab or focus the tab, if the file is already open. 272 @param filename: the path to file 273 @type filename: C{str} 274 @param search_text: if not empty, searches in new document for first occurrence of the given text 275 @type search_text: C{str} (Default: C{Empty String}) 276 ''' 277 if not filename: 278 return 279 self.tabWidget.setUpdatesEnabled(False) 280 try: 281 if filename not in self.files: 282 tab_name = self.__getTabName(filename) 283 editor = TextEdit(filename, self.tabWidget) 284 linenumber_editor = LineNumberWidget(editor) 285 tab_index = self.tabWidget.addTab(linenumber_editor, tab_name) 286 self.files.append(filename) 287 editor.setCurrentPath(os.path.basename(filename)) 288 editor.load_request_signal.connect(self.on_load_request) 289 editor.document().modificationChanged.connect(self.on_editor_modificationChanged) 290 editor.cursorPositionChanged.connect(self.on_editor_positionChanged) 291 editor.setFocus(Qt.OtherFocusReason) 292 # editor.textChanged.connect(self.on_text_changed) 293 editor.undoAvailable.connect(self.on_text_changed) 294 self.tabWidget.setCurrentIndex(tab_index) 295 # self.find_dialog.set_search_path(filename) 296 else: 297 for i in range(self.tabWidget.count()): 298 if self.tabWidget.widget(i).filename == filename: 299 self.tabWidget.setCurrentIndex(i) 300 break 301 except: 302 import traceback 303 rospy.logwarn("Error while open %s: %s", filename, traceback.format_exc(1)) 304 self.tabWidget.setUpdatesEnabled(True) 305 if search_text: 306 try: 307 self._search_thread.stop() 308 self._search_thread = None 309 except: 310 pass 311 self._search_thread = TextSearchThread(search_text, filename, path_text=self.tabWidget.widget(0).document().toPlainText(), recursive=True) 312 self._search_thread.search_result_signal.connect(self.on_search_result_on_open) 313 self._search_thread.start()
314
315 - def on_text_changed(self, value=""):
316 if self.tabWidget.currentWidget().hasFocus(): 317 self.find_dialog.file_changed(self.tabWidget.currentWidget().filename)
318
319 - def on_close_tab(self, tab_index):
320 ''' 321 Signal handling to close single tabs. 322 @param tab_index: tab index to close 323 @type tab_index: C{int} 324 ''' 325 try: 326 doremove = True 327 w = self.tabWidget.widget(tab_index) 328 if w.document().isModified(): 329 name = self.__getTabName(w.filename) 330 result = QMessageBox.question(self, "Unsaved Changes", '\n\n'.join(["Save the file before closing?", name]), QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) 331 if result == QMessageBox.Yes: 332 self.tabWidget.currentWidget().save() 333 elif result == QMessageBox.No: 334 pass 335 else: 336 doremove = False 337 if doremove: 338 # remove the indexed files 339 if w.filename in self.files: 340 self.files.remove(w.filename) 341 # close tab 342 self.tabWidget.removeTab(tab_index) 343 # close editor, if no tabs are open 344 if not self.tabWidget.count(): 345 self.close() 346 except: 347 import traceback 348 rospy.logwarn("Error while close tab %s: %s", str(tab_index), traceback.format_exc(1))
349
350 - def reject(self):
351 if self.find_dialog.isVisible(): 352 self.searchButton.setChecked(not self.searchButton.isChecked()) 353 else: 354 self.close()
355
356 - def closeEvent(self, event):
357 ''' 358 Test the open files for changes and save this if needed. 359 ''' 360 changed = [] 361 # get the names of all changed files 362 for i in range(self.tabWidget.count()): 363 w = self.tabWidget.widget(i) 364 if w.document().isModified(): 365 changed.append(self.__getTabName(w.filename)) 366 if changed: 367 # ask the user for save changes 368 if self.isHidden(): 369 buttons = QMessageBox.Yes | QMessageBox.No 370 else: 371 buttons = QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel 372 result = QMessageBox.question(self, "Unsaved Changes", '\n\n'.join(["Save the file before closing?", '\n'.join(changed)]), buttons) 373 if result == QMessageBox.Yes: 374 for i in range(self.tabWidget.count()): 375 w = self.tabWidget.widget(i).save() 376 event.accept() 377 elif result == QMessageBox.No: 378 event.accept() 379 else: 380 event.ignore() 381 else: 382 event.accept() 383 if event.isAccepted(): 384 self.storeSetting() 385 self.finished_signal.emit(self.init_filenames)
386
387 - def on_editor_modificationChanged(self, value=None):
388 ''' 389 If the content was changed, a '*' will be shown in the tab name. 390 ''' 391 tab_name = self.__getTabName(self.tabWidget.currentWidget().filename) 392 if (self.tabWidget.currentWidget().document().isModified()) or not QFileInfo(self.tabWidget.currentWidget().filename).exists(): 393 tab_name = ''.join(['*', tab_name]) 394 self.tabWidget.setTabText(self.tabWidget.currentIndex(), tab_name)
395
397 ''' 398 Shows the number of the line and column in a label. 399 ''' 400 cursor = self.tabWidget.currentWidget().textCursor() 401 self.pos_label.setText(':%s:%s #%s' % (cursor.blockNumber() + 1, cursor.columnNumber(), cursor.position()))
402
403 - def __getTabName(self, lfile):
404 base = os.path.basename(lfile).replace('.launch', '') 405 (package, _) = package_name(os.path.dirname(lfile)) 406 return '%s [%s]' % (base, package)
407 408 ############################################################################## 409 # HANDLER for buttons 410 ############################################################################## 411
412 - def on_saveButton_clicked(self):
413 ''' 414 Saves the current document. This method is called if the C{save button} 415 was clicked. 416 ''' 417 saved, errors, msg = self.tabWidget.currentWidget().save(True) 418 if errors: 419 QMessageBox.critical(self, "Error", msg) 420 self.tabWidget.setTabIcon(self.tabWidget.currentIndex(), self._error_icon) 421 self.tabWidget.setTabToolTip(self.tabWidget.currentIndex(), msg) 422 elif saved: 423 self.tabWidget.setTabIcon(self.tabWidget.currentIndex(), self._empty_icon) 424 self.tabWidget.setTabToolTip(self.tabWidget.currentIndex(), '')
425
426 - def on_shortcut_find(self):
427 pass
428
429 - def on_toggled_find(self, value):
430 ''' 431 Shows the search frame 432 ''' 433 if value: 434 self.find_dialog.enable() 435 else: 436 self.replaceButton.setChecked(False) 437 self.find_dialog.setVisible(False) 438 self.tabWidget.currentWidget().setFocus()
439
440 - def on_toggled_replace(self, value):
441 ''' 442 Shows the replace lineedit in the search frame 443 ''' 444 if value: 445 self.searchButton.setChecked(True) 446 self.find_dialog.set_replace_visible(value)
447
448 - def on_shortcut_goto(self):
449 ''' 450 Opens a C{goto} dialog. 451 ''' 452 value = 1 453 ok = False 454 try: 455 value, ok = QInputDialog.getInt(self, "Goto", self.tr("Line number:"), 456 QLineEdit.Normal, minValue=1, step=1) 457 except: 458 value, ok = QInputDialog.getInt(self, "Goto", self.tr("Line number:"), 459 QLineEdit.Normal, min=1, step=1) 460 if ok: 461 if value > self.tabWidget.currentWidget().document().blockCount(): 462 value = self.tabWidget.currentWidget().document().blockCount() 463 curpos = self.tabWidget.currentWidget().textCursor().blockNumber() + 1 464 while curpos != value: 465 mov = QTextCursor.NextBlock if curpos < value else QTextCursor.PreviousBlock 466 self.tabWidget.currentWidget().moveCursor(mov) 467 curpos = self.tabWidget.currentWidget().textCursor().blockNumber() + 1 468 self.tabWidget.currentWidget().setFocus(Qt.ActiveWindowFocusReason)
469 470 ############################################################################## 471 # SLOTS for search dialog 472 ############################################################################## 473
474 - def on_search_result(self, search_text, found, path, index):
475 ''' 476 A slot to handle a found text. It goes to the position in the text and select 477 the searched text. On new file it will be open. 478 :param search_text: the searched text 479 :type search_text: str 480 :param found: the text was found or not 481 :type found: bool 482 :param path: the path of the file the text was found 483 :type path: str 484 :param index: the position in the text 485 :type index: int 486 ''' 487 if found: 488 if self.tabWidget.currentWidget().filename != path: 489 focus_widget = QApplication.focusWidget() 490 self.on_load_request(path) 491 focus_widget.setFocus() 492 cursor = self.tabWidget.currentWidget().textCursor() 493 cursor.setPosition(index, QTextCursor.MoveAnchor) 494 cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, len(search_text)) 495 self.tabWidget.currentWidget().setTextCursor(cursor)
496
497 - def on_search_result_on_open(self, search_text, found, path, index):
498 ''' 499 Like on_search_result, but skips the text in comments. 500 ''' 501 if found: 502 if self.tabWidget.currentWidget().filename != path: 503 focus_widget = QApplication.focusWidget() 504 self.on_load_request(path) 505 focus_widget.setFocus() 506 comment_start = self.tabWidget.currentWidget().document().find('<!--', index, QTextDocument.FindBackward) 507 if not comment_start.isNull(): 508 comment_end = self.tabWidget.currentWidget().document().find('-->', comment_start) 509 if not comment_end.isNull() and comment_end.position() > index + len(search_text): 510 # commented -> retrun 511 return 512 self.on_search_result(search_text, found, path, index)
513
514 - def on_replace(self, search_text, path, index, replaced_text):
515 ''' 516 A slot to handle a text replacement of the TextSearchFrame. 517 :param search_text: the searched text 518 :type search_text: str 519 :param path: the path of the file the text was found 520 :type path: str 521 :param index: the position in the text 522 :type index: int 523 :param replaced_text: the new text 524 :type replaced_text: str 525 ''' 526 cursor = self.tabWidget.currentWidget().textCursor() 527 if cursor.selectedText() == search_text: 528 cursor.insertText(replaced_text)
529 530 ############################################################################## 531 # LAUNCH TAG insertion 532 ############################################################################## 533
534 - def _create_tag_button(self, parent=None):
535 btn = QPushButton(parent) 536 btn.setObjectName("tagButton") 537 btn.setText(self._translate("Add &tag")) 538 btn.setShortcut("Ctrl+T") 539 btn.setToolTip('Adds a ROS launch tag to launch file (Ctrl+T)') 540 btn.setMenu(self._create_tag_menu(btn)) 541 btn.setFlat(True) 542 return btn
543
544 - def _create_tag_menu(self, parent=None):
545 # creates a tag menu 546 tag_menu = QMenu("ROS Tags", parent) 547 # group tag 548 add_group_tag_action = QAction("<group>", self, statusTip="", triggered=self._on_add_group_tag) 549 add_group_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+g")) 550 tag_menu.addAction(add_group_tag_action) 551 # node tag 552 add_node_tag_action = QAction("<node>", self, statusTip="", triggered=self._on_add_node_tag) 553 add_node_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+n")) 554 tag_menu.addAction(add_node_tag_action) 555 # node tag with all attributes 556 add_node_tag_all_action = QAction("<node all>", self, statusTip="", triggered=self._on_add_node_tag_all) 557 tag_menu.addAction(add_node_tag_all_action) 558 # include tag with all attributes 559 add_include_tag_all_action = QAction("<include>", self, statusTip="", triggered=self._on_add_include_tag_all) 560 add_include_tag_all_action.setShortcuts(QKeySequence("Ctrl+Shift+i")) 561 tag_menu.addAction(add_include_tag_all_action) 562 # remap 563 add_remap_tag_action = QAction("<remap>", self, statusTip="", triggered=self._on_add_remap_tag) 564 add_remap_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+r")) 565 tag_menu.addAction(add_remap_tag_action) 566 # env tag 567 add_env_tag_action = QAction("<env>", self, statusTip="", triggered=self._on_add_env_tag) 568 tag_menu.addAction(add_env_tag_action) 569 # param tag 570 add_param_tag_action = QAction("<param>", self, statusTip="", triggered=self._on_add_param_tag) 571 add_param_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+p")) 572 tag_menu.addAction(add_param_tag_action) 573 # param capability group tag 574 add_param_cap_group_tag_action = QAction("<param capability group>", self, statusTip="", triggered=self._on_add_param_cap_group_tag) 575 add_param_cap_group_tag_action.setShortcuts(QKeySequence("Ctrl+Alt+p")) 576 tag_menu.addAction(add_param_cap_group_tag_action) 577 # param tag with all attributes 578 add_param_tag_all_action = QAction("<param all>", self, statusTip="", triggered=self._on_add_param_tag_all) 579 tag_menu.addAction(add_param_tag_all_action) 580 # rosparam tag with all attributes 581 add_rosparam_tag_all_action = QAction("<rosparam>", self, statusTip="", triggered=self._on_add_rosparam_tag_all) 582 tag_menu.addAction(add_rosparam_tag_all_action) 583 # arg tag with default definition 584 add_arg_tag_default_action = QAction("<arg default>", self, statusTip="", triggered=self._on_add_arg_tag_default) 585 add_arg_tag_default_action.setShortcuts(QKeySequence("Ctrl+Shift+a")) 586 tag_menu.addAction(add_arg_tag_default_action) 587 # arg tag with value definition 588 add_arg_tag_value_action = QAction("<arg value>", self, statusTip="", triggered=self._on_add_arg_tag_value) 589 add_arg_tag_value_action.setShortcuts(QKeySequence("Ctrl+Alt+a")) 590 tag_menu.addAction(add_arg_tag_value_action) 591 592 # test tag 593 add_test_tag_action = QAction("<test>", self, statusTip="", triggered=self._on_add_test_tag) 594 add_test_tag_action.setShortcuts(QKeySequence("Ctrl+Alt+t")) 595 tag_menu.addAction(add_test_tag_action) 596 # test tag with all attributes 597 add_test_tag_all_action = QAction("<test all>", self, statusTip="", triggered=self._on_add_test_tag_all) 598 tag_menu.addAction(add_test_tag_all_action) 599 return tag_menu
600
601 - def _insert_text(self, text):
602 cursor = self.tabWidget.currentWidget().textCursor() 603 if not cursor.isNull(): 604 col = cursor.columnNumber() 605 spaces = ''.join([' ' for _ in range(col)]) 606 cursor.insertText(text.replace('\n', '\n%s' % spaces)) 607 self.tabWidget.currentWidget().setFocus(Qt.OtherFocusReason)
608
609 - def _on_add_group_tag(self):
610 self._insert_text('<group ns="namespace" clear_params="true|false">\n' 611 '</group>')
612
613 - def _on_add_node_tag(self):
614 dia = PackageDialog() 615 if dia.exec_(): 616 self._insert_text('<node name="%s" pkg="%s" type="%s">\n' 617 '</node>' % (dia.binary, dia.package, dia.binary))
618
619 - def _on_add_node_tag_all(self):
620 dia = PackageDialog() 621 if dia.exec_(): 622 self._insert_text('<node name="%s" pkg="%s" type="%s"\n' 623 ' args="arg1" machine="machine-name"\n' 624 ' respawn="true" required="true"\n' 625 ' ns="foo" clear_params="true|false"\n' 626 ' output="log|screen" cwd="ROS_HOME|node"\n' 627 ' launch-prefix="prefix arguments">\n' 628 '</node>' % (dia.binary, dia.package, dia.binary))
629
630 - def _on_add_include_tag_all(self):
631 self._insert_text('<include file="$(find pkg-name)/path/filename.xml"\n' 632 ' ns="foo" clear_params="true|false">\n' 633 '</include>')
634
635 - def _on_add_remap_tag(self):
636 self._insert_text('<remap from="original" to="new"/>')
637
638 - def _on_add_env_tag(self):
639 self._insert_text('<env name="variable" value="value"/>')
640
641 - def _on_add_param_tag(self):
642 self._insert_text('<param name="ns_name" value="value" />')
643
645 self._insert_text('<param name="capability_group" value="demo" />')
646
647 - def _on_add_param_tag_all(self):
648 self._insert_text('<param name="ns_name" value="value"\n' 649 ' type="str|int|double|bool"\n' 650 ' textfile="$(find pkg-name)/path/file.txt"\n' 651 ' binfile="$(find pkg-name)/path/file"\n' 652 ' command="$(find pkg-name)/exe \'$(find pkg-name)/arg.txt\'">\n' 653 '</param>')
654
655 - def _on_add_rosparam_tag_all(self):
656 self._insert_text('<rosparam param="param-name"\n' 657 ' file="$(find pkg-name)/path/foo.yaml"\n' 658 ' command="load|dump|delete"\n' 659 ' ns="namespace">\n' 660 '</rosparam>')
661
662 - def _on_add_arg_tag_default(self):
663 self._insert_text('<arg name="foo" default="1" />')
664
665 - def _on_add_arg_tag_value(self):
666 self._insert_text('<arg name="foo" value="bar" />')
667
668 - def _on_add_test_tag(self):
669 dia = PackageDialog() 670 if dia.exec_(): 671 self._insert_text('<test name="%s" pkg="%s" type="%s" test-name="test_%s">\n' 672 '</test>' % (dia.binary, dia.package, dia.binary, dia.binary))
673
674 - def _on_add_test_tag_all(self):
675 dia = PackageDialog() 676 if dia.exec_(): 677 self._insert_text('<test name="%s" pkg="%s" type="%s" test-name="test_%s">\n' 678 ' args="arg1" time-limit="60.0"\n' 679 ' ns="foo" clear_params="true|false"\n' 680 ' cwd="ROS_HOME|node" retry="0"\n' 681 ' launch-prefix="prefix arguments">\n' 682 '</test>' % (dia.binary, dia.package, dia.binary, dia.binary))
683