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