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

Source Code for Module node_manager_fkie.xml_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  import os 
  34  from python_qt_binding import QtGui 
  35  from python_qt_binding import QtCore 
  36   
  37  import roslib 
  38  import rospy 
  39  from xml_highlighter import XmlHighlighter 
  40  from yaml_highlighter import YamlHighlighter 
  41  from detailed_msg_box import WarningMessageBox 
  42   
  43  import node_manager_fkie as nm 
  44  from master_discovery_fkie.common import resolve_url 
  45  from common import package_name 
  46   
47 -class Editor(QtGui.QTextEdit):
48 ''' 49 The XML editor to handle the included files. If an included file in the opened 50 launch file is detected, this can be open by STRG+(mouse click) in a new 51 editor. 52 ''' 53 54 load_request_signal = QtCore.Signal(str) 55 ''' @ivar: A signal for request to open a configuration file''' 56 57 SUBSTITUTION_ARGS = ['env', 'optenv', 'find', 'anon', 'arg'] 58 CONTEXT_FILE_EXT = ['.launch', '.test', '.xml'] 59 YAML_VALIDATION_FILES = ['.yaml', '.iface', '.sync'] 60
61 - def __init__(self, filename, parent=None):
62 self.parent = parent 63 QtGui.QTextEdit.__init__(self, parent) 64 self.setObjectName(' - '.join(['Editor', filename])) 65 font = QtGui.QFont() 66 font.setFamily("Fixed".decode("utf-8")) 67 font.setPointSize(12) 68 self.setFont(font) 69 self.setLineWrapMode(QtGui.QTextEdit.NoWrap) 70 self.setTabStopWidth(25) 71 self.setAcceptRichText(False) 72 self.setCursorWidth(2) 73 self.setFontFamily("courier new") 74 self.setProperty("backgroundVisible", True) 75 self.regexp_list = [QtCore.QRegExp("\\binclude\\b"), QtCore.QRegExp("\\btextfile\\b"), 76 QtCore.QRegExp("\\bfile\\b"), QtCore.QRegExp("\\bvalue=.*pkg:\/\/\\b"), 77 QtCore.QRegExp("\\bvalue=.*package:\/\/\\b"), 78 QtCore.QRegExp("\\bvalue=.*\$\(find\\b")] 79 self.filename = filename 80 self.file_info = None 81 if self.filename: 82 file = QtCore.QFile(filename); 83 if file.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text): 84 self.file_info = QtCore.QFileInfo(filename) 85 self.setText(unicode(file.readAll(), "utf-8")) 86 87 self.path = '.' 88 # enables drop events 89 self.setAcceptDrops(True)
90 91 # def __del__(self): 92 # print "********** desctroy:", self.objectName() 93
94 - def save(self):
95 ''' 96 Saves changes to the file. 97 :return: saved, errors, msg 98 :rtype: bool, bool, str 99 ''' 100 if self.document().isModified() or not QtCore.QFileInfo(self.filename).exists(): 101 file = QtCore.QFile(self.filename) 102 if file.open(QtCore.QIODevice.WriteOnly | QtCore.QIODevice.Text): 103 file.write(self.toPlainText().encode('utf-8')) 104 self.document().setModified(False) 105 self.file_info = QtCore.QFileInfo(self.filename) 106 107 ext = os.path.splitext(self.filename) 108 # validate the xml structure of the launch files 109 if ext[1] in self.CONTEXT_FILE_EXT: 110 imported = False 111 try: 112 from lxml import etree 113 imported = True 114 parser = etree.XMLParser() 115 etree.fromstring(self.toPlainText().encode('utf-8'), parser) 116 except Exception as e: 117 if imported: 118 self.markLine(e.position[0]) 119 return True, True, "%s"%e 120 # validate the yaml structure of yaml files 121 elif ext[1] in self.YAML_VALIDATION_FILES: 122 try: 123 import yaml 124 yaml.load(self.toPlainText().encode('utf-8')) 125 except yaml.MarkedYAMLError as e: 126 return True, True, "%s"%e 127 return True, False, '' 128 else: 129 return False, True, "Cannot write XML file" 130 return False, False, ''
131
132 - def markLine(self, no):
133 try: 134 cursor = self.textCursor() 135 cursor.setPosition(0, QtGui.QTextCursor.MoveAnchor) 136 while (cursor.block().blockNumber()+1 < no): 137 cursor.movePosition(QtGui.QTextCursor.NextBlock, QtGui.QTextCursor.MoveAnchor) 138 cursor.movePosition(QtGui.QTextCursor.EndOfBlock, QtGui.QTextCursor.KeepAnchor) 139 self.setTextCursor(cursor) 140 except: 141 pass
142
143 - def setCurrentPath(self, path):
144 ''' 145 Sets the current working path. This path is to open the included files, which 146 contains the relative path. 147 @param path: the path of the current opened file (without the file) 148 @type path: C{str} 149 ''' 150 self.path = path
151
152 - def interpretPath(self, path):
153 ''' 154 Tries to determine the path of the included file. The statement of 155 C{$(find 'package')} will be resolved. 156 @param path: the sting which contains the included path 157 @type path: C{str} 158 @return: if no leading C{os.sep} is detected, the path setted by L{setCurrentPath()} 159 will be prepend. C{$(find 'package')} will be resolved. Otherwise the parameter 160 itself will be returned 161 @rtype: C{str} 162 ''' 163 path = path.strip() 164 index = path.find('$') 165 if index > -1: 166 startIndex = path.find('(', index) 167 if startIndex > -1: 168 endIndex = path.find(')', startIndex+1) 169 script = path[startIndex+1:endIndex].split() 170 if len(script) == 2 and (script[0] == 'find'): 171 try: 172 pkg = roslib.packages.get_pkg_dir(script[1]) 173 return os.path.normpath(''.join([pkg, '/', path[endIndex+1:]])) 174 except Exception as e: 175 rospy.logwarn(str(e)) 176 else: 177 try: 178 return resolve_url(path) 179 except ValueError, e: 180 if len(path) > 0 and path[0] != '/': 181 return os.path.normpath(''.join([self.path, '/', path])) 182 # elif len(path) > 0 and path[0] != '/': 183 # return os.path.normpath(''.join([self.path, '/', path])) 184 return os.path.normpath(path)
185
186 - def index(self, text):
187 ''' 188 Searches in the given text for key indicates the including of a file and 189 return their index. 190 @param text: text to find 191 @type text: C{str} 192 @return: the index of the including key or -1 193 @rtype: C{int} 194 ''' 195 for pattern in self.regexp_list: 196 index = pattern.indexIn(text) 197 if index > -1: 198 return index 199 try: 200 return resolve_url(path) 201 except: 202 pass 203 return -1
204
205 - def includedFiles(self):
206 ''' 207 Returns all included files in the document. 208 ''' 209 result = [] 210 b = self.document().begin() 211 while b != self.document().end(): 212 text = b.text() 213 index = self.index(text) 214 if index > -1: 215 startIndex = text.find('"', index) 216 if startIndex > -1: 217 endIndex = text.find('"', startIndex+1) 218 fileName = text[startIndex+1:endIndex] 219 if len(fileName) > 0: 220 try: 221 path = self.interpretPath(fileName) 222 file = QtCore.QFile(path) 223 ext = os.path.splitext(path) 224 if file.exists() and ext[1] in nm.Settings().SEARCH_IN_EXT: 225 result.append(path) 226 except: 227 import traceback 228 print traceback.format_exc() 229 b = b.next() 230 return result
231
232 - def fileWithText(self, search_text):
233 ''' 234 Searches for given text in this document and all included files. 235 @param search_text: text to find 236 @type search_text: C{str} 237 @return: the list with all files contain the text 238 @rtype: C{[str, ...]} 239 ''' 240 result = [] 241 start_pos = QtGui.QTextCursor() 242 search_result = self.document().find(search_text, start_pos.position()+1) 243 if not search_result.isNull(): 244 result.append(self.filename) 245 inc_files = self.includedFiles() 246 for f in inc_files: 247 editor = Editor(f, None) 248 result[len(result):] = editor.fileWithText(search_text) 249 return result
250
251 - def focusInEvent(self, event):
252 # check for file changes 253 try: 254 if self.filename and self.file_info: 255 if self.file_info.lastModified() != QtCore.QFileInfo(self.filename).lastModified(): 256 self.file_info = QtCore.QFileInfo(self.filename) 257 result = QtGui.QMessageBox.question(self, "File changed", "File was changed, reload?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) 258 if result == QtGui.QMessageBox.Yes: 259 file = QtCore.QFile(self.filename); 260 if file.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text): 261 self.setText(unicode(file.readAll(), "utf-8")) 262 self.document().setModified(False) 263 self.textChanged.emit() 264 else: 265 QtGui.QMessageBox.critical(self, "Error", "Cannot open launch file%s"%self.filename) 266 except: 267 pass 268 QtGui.QTextEdit.focusInEvent(self, event)
269
270 - def mouseReleaseEvent(self, event):
271 ''' 272 Opens the new editor, if the user clicked on the included file and sets the 273 default cursor. 274 ''' 275 if event.modifiers() == QtCore.Qt.ControlModifier or event.modifiers() == QtCore.Qt.ShiftModifier: 276 cursor = self.cursorForPosition(event.pos()) 277 index = self.index(cursor.block().text()) 278 if index > -1: 279 startIndex = cursor.block().text().find('"', index) 280 if startIndex > -1: 281 endIndex = cursor.block().text().find('"', startIndex+1) 282 fileName = cursor.block().text()[startIndex+1:endIndex] 283 if len(fileName) > 0: 284 try: 285 file = QtCore.QFile(self.interpretPath(fileName)) 286 if not file.exists(): 287 # create a new file, if it does not exists 288 result = QtGui.QMessageBox.question(self, "File not found", '\n\n'.join(["Create a new file?", file.fileName()]), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) 289 if result == QtGui.QMessageBox.Yes: 290 dir = os.path.dirname(file.fileName()) 291 if not os.path.exists(dir): 292 os.makedirs(dir) 293 with open(file.fileName(),'w') as f: 294 if file.fileName().endswith('.launch'): 295 f.write('<launch>\n\n</launch>') 296 self.load_request_signal.emit(file.fileName()) 297 else: 298 self.load_request_signal.emit(file.fileName()) 299 except Exception, e: 300 WarningMessageBox(QtGui.QMessageBox.Warning, "File not found", fileName, str(e)).exec_() 301 QtGui.QTextEdit.mouseReleaseEvent(self, event)
302
303 - def mouseMoveEvent(self, event):
304 ''' 305 Sets the X{QtCore.Qt.PointingHandCursor} if the control key is pressed and 306 the mouse is over the included file. 307 ''' 308 if event.modifiers() == QtCore.Qt.ControlModifier or event.modifiers() == QtCore.Qt.ShiftModifier: 309 cursor = self.cursorForPosition(event.pos()) 310 index = self.index(cursor.block().text()) 311 if index > -1: 312 self.viewport().setCursor(QtCore.Qt.PointingHandCursor) 313 else: 314 self.viewport().setCursor(QtCore.Qt.IBeamCursor) 315 else: 316 self.viewport().setCursor(QtCore.Qt.IBeamCursor) 317 QtGui.QTextEdit.mouseMoveEvent(self, event)
318
319 - def keyPressEvent(self, event):
320 ''' 321 Enable the mouse tracking by X{setMouseTracking()} if the control key is pressed. 322 ''' 323 if event.key() == QtCore.Qt.Key_Control or event.key() == QtCore.Qt.Key_Shift: 324 self.setMouseTracking(True) 325 if event.modifiers() == QtCore.Qt.ControlModifier and event.key() == QtCore.Qt.Key_7: 326 self.commentText() 327 if event.key() != QtCore.Qt.Key_Escape: 328 # handle the shifting of the block 329 if event.key() == QtCore.Qt.Key_Tab: 330 self.shiftText() 331 else: 332 QtGui.QTextEdit.keyPressEvent(self, event) 333 else: 334 event.accept() 335 QtGui.QTextEdit.keyPressEvent(self, event)
336
337 - def keyReleaseEvent(self, event):
338 ''' 339 Disable the mouse tracking by X{setMouseTracking()} if the control key is 340 released and set the cursor back to X{QtCore.Qt.IBeamCursor}. 341 ''' 342 if event.key() == QtCore.Qt.Key_Control or event.key() == QtCore.Qt.Key_Shift: 343 self.setMouseTracking(False) 344 self.viewport().setCursor(QtCore.Qt.IBeamCursor) 345 elif event.modifiers() == QtCore.Qt.ControlModifier and event.key() == QtCore.Qt.Key_Space: 346 ext = os.path.splitext(self.filename) 347 if ext[1] in self.CONTEXT_FILE_EXT: 348 menu = self._create_context_substitution_menu() 349 if menu is None: 350 menu = self._create_context_tag_menu() 351 if menu: 352 menu.exec_(self.mapToGlobal(self.cursorRect().bottomRight())) 353 QtGui.QTextEdit.keyReleaseEvent(self, event)
354
355 - def commentText(self):
356 cursor = self.textCursor() 357 if not cursor.isNull(): 358 cursor.beginEditBlock() 359 start = cursor.selectionStart() 360 end = cursor.selectionEnd() 361 cursor.setPosition(start) 362 block_start = cursor.blockNumber() 363 cursor.setPosition(end) 364 block_end = cursor.blockNumber() 365 if block_end-block_start > 0 and end-cursor.block().position() <= 0: 366 # skip the last block, if no characters are selected 367 block_end -= 1 368 cursor.setPosition(start, QtGui.QTextCursor.MoveAnchor) 369 cursor.movePosition(QtGui.QTextCursor.StartOfLine) 370 start = cursor.position() 371 while (cursor.block().blockNumber() < block_end+1): 372 cursor.movePosition(QtGui.QTextCursor.StartOfLine) 373 ext = os.path.splitext(self.filename) 374 # XML comment 375 if ext[1] in self.CONTEXT_FILE_EXT: 376 if cursor.block().length() < 4: 377 cursor.movePosition(QtGui.QTextCursor.NextBlock) 378 continue 379 cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor, 4) 380 # only comments breakers at the start of the line are removed 381 if cursor.selectedText() == '<!--': 382 cursor.insertText('') 383 cursor.movePosition(QtGui.QTextCursor.EndOfLine) 384 cursor.movePosition(QtGui.QTextCursor.PreviousCharacter, QtGui.QTextCursor.KeepAnchor, 3) 385 if cursor.selectedText() == '-->': 386 cursor.insertText('') 387 else: 388 cursor.movePosition(QtGui.QTextCursor.StartOfLine) 389 cursor.movePosition(QtGui.QTextCursor.EndOfLine, QtGui.QTextCursor.KeepAnchor) 390 # only comment out, if no comments are found 391 if cursor.selectedText().find('<!--') < 0 and cursor.selectedText().find('-->') < 0: 392 cursor.movePosition(QtGui.QTextCursor.StartOfLine) 393 cursor.insertText('<!--') 394 cursor.movePosition(QtGui.QTextCursor.EndOfLine) 395 cursor.insertText('-->') 396 else: # other comments 397 if cursor.block().length() < 2: 398 cursor.movePosition(QtGui.QTextCursor.NextBlock) 399 continue 400 cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor, 2) 401 # only comments breakers at the start of the line are removed 402 if cursor.selectedText() == '# ': 403 cursor.insertText('') 404 else: 405 cursor.movePosition(QtGui.QTextCursor.StartOfLine) 406 cursor.insertText('# ') 407 cursor.movePosition(QtGui.QTextCursor.NextBlock) 408 # Set our cursor's selection to span all of the involved lines. 409 cursor.endEditBlock() 410 cursor.setPosition(start, QtGui.QTextCursor.MoveAnchor) 411 cursor.movePosition(QtGui.QTextCursor.StartOfBlock, QtGui.QTextCursor.MoveAnchor) 412 while (cursor.block().blockNumber() < block_end): 413 cursor.movePosition(QtGui.QTextCursor.NextBlock, QtGui.QTextCursor.KeepAnchor) 414 cursor.movePosition(QtGui.QTextCursor.EndOfBlock, QtGui.QTextCursor.KeepAnchor) 415 # set the cursor 416 self.setTextCursor(cursor)
417
418 - def shiftText(self):
419 ''' 420 Increase (Decrease) indentation using Tab (Ctrl+Tab). 421 ''' 422 cursor = self.textCursor() 423 if not cursor.isNull(): 424 key_mod = QtGui.QApplication.keyboardModifiers() 425 # one undo operation 426 cursor.beginEditBlock() 427 start = cursor.selectionStart() 428 end = cursor.selectionEnd() 429 cursor.setPosition(start) 430 block_start = cursor.blockNumber() 431 cursor.setPosition(end) 432 block_end = cursor.blockNumber() 433 if block_end-block_start == 0: 434 # shift one line two spaces to the left 435 if key_mod & QtCore.Qt.ControlModifier or key_mod & QtCore.Qt.ShiftModifier: 436 for s in range(2): 437 cursor.movePosition(QtGui.QTextCursor.StartOfLine) 438 cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor, 1) 439 if cursor.selectedText() == ' ': 440 cursor.insertText('') 441 elif cursor.selectedText() == "\t": 442 cursor.insertText('') 443 break 444 cursor.movePosition(QtGui.QTextCursor.StartOfLine) 445 else: 446 # shift one line two spaces to the right 447 cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor, end-start) 448 cursor.insertText(' ') 449 else: 450 # shift the selected block two spaces to the left 451 if key_mod & QtCore.Qt.ControlModifier or key_mod & QtCore.Qt.ShiftModifier: 452 removed = 0 453 for i in reversed(range(start, end)): 454 cursor.setPosition(i) 455 if cursor.atBlockStart(): 456 cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor, 2) 457 if cursor.selectedText() == ' ': 458 cursor.insertText('') 459 removed += 2 460 else: 461 cursor.movePosition(QtGui.QTextCursor.StartOfLine) 462 cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor, 1) 463 if cursor.selectedText() == ' ': 464 cursor.insertText('') 465 removed += 1 466 elif cursor.selectedText() == "\t": 467 cursor.insertText('') 468 removed += 1 469 cursor.setPosition(start) 470 cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor, end-start-removed) 471 else: 472 # shift selected block two spaces to the right 473 inserted = 0 474 for i in reversed(range(start, end)): 475 cursor.setPosition(i) 476 if cursor.atBlockStart(): 477 cursor.insertText(' ') 478 inserted += 2 479 cursor.setPosition(start) 480 cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor, end-start+inserted) 481 self.setTextCursor(cursor) 482 cursor.endEditBlock()
483 484 ############################################################################# 485 ########## Drag&Drop ###### 486 ############################################################################# 487
488 - def dragEnterEvent(self, e):
489 if e.mimeData().hasFormat('text/plain'): 490 e.accept() 491 else: 492 e.ignore()
493
494 - def dragMoveEvent(self, e):
495 e.accept()
496
497 - def dropEvent(self, e):
498 cursor = self.cursorForPosition(e.pos()) 499 if not cursor.isNull(): 500 text = e.mimeData().text() 501 # the files will be included 502 if text.startswith('file://'): 503 text = text[7:] 504 if os.path.exists(text) and os.path.isfile(text): 505 # find the package name containing the included file 506 (package, path) = package_name(os.path.dirname(text)) 507 if text.endswith('.launch'): 508 if package: 509 cursor.insertText('<include file="$(find %s)%s" />'%(package, text.replace(path, ''))) 510 else: 511 cursor.insertText('<include file="%s" />'%text) 512 else: 513 if package: 514 cursor.insertText('<rosparam file="$(find %s)%s" command="load" />'%(package, text.replace(path, ''))) 515 else: 516 cursor.insertText('<rosparam file="%s" command="load" />'%text) 517 else: 518 cursor.insertText(e.mimeData().text()) 519 e.accept()
520 521 ############################################################################# 522 ########## Ctrl&Space Context menu ###### 523 ############################################################################# 524
525 - def _create_context_tag_menu(self):
526 parent_tag, inblock, attrs = self._get_parent_tag() 527 if not parent_tag: 528 return None 529 menu = QtGui.QMenu(self) 530 menu.triggered.connect(self._context_activated) 531 text = self.toPlainText() 532 pos = self.textCursor().position() - 1 533 try: 534 if not inblock: 535 # create a menu with attributes 536 attributes = sorted(list((set(XmlHighlighter.LAUNCH_ATTR[parent_tag]) - set(attrs)))) 537 for attr in attributes: 538 action = menu.addAction(attr.rstrip('=')) 539 action.setData('%s"'%attr if text[pos] == ' ' else ' %s"'%attr) 540 else: 541 # create a menu with tags 542 tags = sorted(XmlHighlighter.LAUNCH_CHILDS[parent_tag]) 543 if not tags: 544 return None 545 for tag in tags: 546 data = '<%s></%s>'%(tag, tag) if XmlHighlighter.LAUNCH_CHILDS[tag] else '<%s/>'%tag 547 if text[pos] == '<': 548 data = data[1:] 549 action = menu.addAction(tag) 550 action.setData(data) 551 except: 552 # import traceback 553 # print traceback.format_exc() 554 return None 555 return menu
556
558 text = self.toPlainText() 559 pos = self.textCursor().position() - 1 560 try: 561 if text[pos] == '$' or (text[pos] == '(' and text[pos-1] == '$'): 562 menu = QtGui.QMenu(self) 563 menu.triggered.connect(self._context_activated) 564 for arg in self.SUBSTITUTION_ARGS: 565 action = menu.addAction("%s"%arg) 566 action.setData("(%s"%arg if text[pos] == '$' else "%s"%arg) 567 return menu 568 except: 569 pass 570 return None
571
572 - def _get_parent_tag(self):
573 text = self.toPlainText() 574 pos = self.textCursor().position() - 1 575 # do not parse, if the menu was requested in a string sequence 576 try: 577 if not (text[pos] in [' ', '<', '>', '"', '\n'] or text[pos+1] in [' ', '<','"', '\n']): 578 return '', False, [] 579 except: 580 pass 581 instr = (text[:pos+1].count('"') % 2) 582 # do not parse, if the menu was requested in a string definition 583 if instr: 584 return '', False, [] 585 # some parameter definition 586 closed_tags = [] 587 current_attr = [] 588 tag_reading = True 589 tag = '' 590 attr_reading = False 591 attr = '' 592 closed_gts = False 593 # parse the text from current position to the beginning 594 i = pos 595 while i >= 0: 596 if text[i] == '"': 597 instr = not instr 598 elif not instr: 599 # parse only text which is not in string definitions 600 if text[i] == '=': 601 attr_reading = True 602 elif text[i] in ['<', '/', '>']: 603 if text[i] == '>': 604 closed_gts = True 605 tag = '' 606 elif text[i] == '/' and closed_gts: 607 closed_gts = False 608 closed_tags.append(tag if tag else '/') 609 tag = '' 610 elif text[i] == '<': 611 if closed_tags and (tag == closed_tags[-1] or closed_tags[-1] == '/'): 612 closed_tags.pop() 613 current_attr = [] 614 tag = '' 615 elif tag: 616 return tag[::-1], closed_gts, current_attr # reverse the tag 617 # start or end of attribute parsing 618 elif text[i] == ' ': 619 if attr_reading and attr: 620 current_attr.append("%s="%attr[::-1]) 621 attr_reading = False 622 attr = '' 623 tag = '' 624 tag_reading = True 625 else: 626 if tag_reading or closed_gts: 627 tag += text[i] 628 if attr_reading: 629 attr += text[i] 630 i -= 1 631 return '', False, []
632
633 - def _context_activated(self, arg):
634 cursor = self.textCursor() 635 if not cursor.isNull(): 636 cursor.insertText(arg.data())
637 638
639 -class FindDialog(QtGui.QDialog):
640 ''' 641 A dialog to find text in the Editor. 642 ''' 643
644 - def __init__(self, parent=None):
645 QtGui.QDialog.__init__(self, parent) 646 self.setObjectName('FindDialog') 647 self.setWindowTitle('Search') 648 self.verticalLayout = QtGui.QVBoxLayout(self) 649 self.verticalLayout.setObjectName("verticalLayout") 650 651 self.content = QtGui.QWidget(self) 652 self.contentLayout = QtGui.QFormLayout(self.content) 653 # self.contentLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow) 654 # self.contentLayout.setVerticalSpacing(0) 655 self.contentLayout.setContentsMargins(0, 0, 0, 0) 656 self.verticalLayout.addWidget(self.content) 657 658 label = QtGui.QLabel("Find:", self.content) 659 self.search_field = QtGui.QLineEdit(self.content) 660 self.contentLayout.addRow(label, self.search_field) 661 replace_label = QtGui.QLabel("Replace:", self.content) 662 self.replace_field = QtGui.QLineEdit(self.content) 663 self.contentLayout.addRow(replace_label, self.replace_field) 664 self.recursive = QtGui.QCheckBox("recursive search") 665 self.contentLayout.addRow(self.recursive) 666 self.result_label = QtGui.QLabel("") 667 self.verticalLayout.addWidget(self.result_label) 668 self.found_files = QtGui.QListWidget() 669 self.found_files.setVisible(False) 670 self.found_files.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) 671 self.verticalLayout.addWidget(self.found_files) 672 673 self.buttonBox = QtGui.QDialogButtonBox(self) 674 self.find_button = QtGui.QPushButton(self.tr("&Find")) 675 self.find_button.setDefault(True) 676 self.buttonBox.addButton(self.find_button, QtGui.QDialogButtonBox.ActionRole) 677 self.replace_button = QtGui.QPushButton(self.tr("&Replace/Find")) 678 self.buttonBox.addButton(self.replace_button, QtGui.QDialogButtonBox.ActionRole) 679 self.buttonBox.addButton(QtGui.QDialogButtonBox.Close) 680 self.buttonBox.setOrientation(QtCore.Qt.Horizontal) 681 self.buttonBox.setObjectName("buttonBox") 682 self.verticalLayout.addWidget(self.buttonBox) 683 684 # QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), self.accept) 685 QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), self.reject) 686 QtCore.QMetaObject.connectSlotsByName(self) 687 688 self.search_text = '' 689 self.search_pos = QtGui.QTextCursor()
690 691 # def __del__(self): 692 # print "********** desctroy:", self.objectName() 693 694
695 -class XmlEditor(QtGui.QDialog):
696 ''' 697 Creates a dialog to edit a launch file. 698 ''' 699 finished_signal = QtCore.Signal(list) 700 ''' 701 finished_signal has as parameter the filenames of the initialization and is emitted, if this 702 dialog was closed. 703 ''' 704
705 - def __init__(self, filenames, search_text='', parent=None):
706 ''' 707 @param filenames: a list with filenames. The last one will be activated. 708 @type filenames: C{[str, ...]} 709 @param search_text: if not empty, searches in new document for first occurrence of the given text 710 @type search_text: C{str} (Default: C{Empty String}) 711 ''' 712 QtGui.QDialog.__init__(self, parent) 713 self.setObjectName(' - '.join(['xmlEditor', str(filenames)])) 714 self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) 715 self.setWindowFlags(QtCore.Qt.Window) 716 self.mIcon = QtGui.QIcon(":/icons/crystal_clear_edit_launch.png") 717 self._error_icon = QtGui.QIcon(":/icons/crystal_clear_warning.png") 718 self._empty_icon = QtGui.QIcon() 719 self.setWindowIcon(self.mIcon) 720 self.setWindowTitle("ROSLaunch Editor"); 721 # self.finished.connect(self.closeEvent) 722 self.init_filenames = list(filenames) 723 724 self.files = [] 725 '''@ivar: list with all open files ''' 726 727 # create tabs for files 728 self.verticalLayout = QtGui.QVBoxLayout(self) 729 self.verticalLayout.setContentsMargins(0, 0, 0, 0) 730 self.verticalLayout.setObjectName("verticalLayout") 731 self.tabWidget = QtGui.QTabWidget(self) 732 self.tabWidget.setTabPosition(QtGui.QTabWidget.North) 733 self.tabWidget.setDocumentMode(True) 734 self.tabWidget.setTabsClosable(True) 735 self.tabWidget.setMovable(False) 736 self.tabWidget.setObjectName("tabWidget") 737 self.tabWidget.tabCloseRequested.connect(self.on_close_tab) 738 self.verticalLayout.addWidget(self.tabWidget) 739 740 # create the buttons line 741 self.buttons = QtGui.QWidget(self) 742 self.horizontalLayout = QtGui.QHBoxLayout(self.buttons) 743 self.horizontalLayout.setContentsMargins(4, 0, 4, 0) 744 self.horizontalLayout.setObjectName("horizontalLayout") 745 # add the search button 746 self.searchButton = QtGui.QPushButton(self) 747 self.searchButton.setObjectName("searchButton") 748 self.searchButton.clicked.connect(self.on_shortcut_find) 749 self.searchButton.setText(QtGui.QApplication.translate("XmlEditor", "Search", None, QtGui.QApplication.UnicodeUTF8)) 750 self.searchButton.setShortcut(QtGui.QApplication.translate("XmlEditor", "Ctrl+F", None, QtGui.QApplication.UnicodeUTF8)) 751 self.searchButton.setToolTip('Open a search dialog (Ctrl+F)') 752 self.horizontalLayout.addWidget(self.searchButton) 753 # add the goto button 754 self.gotoButton = QtGui.QPushButton(self) 755 self.gotoButton.setObjectName("gotoButton") 756 self.gotoButton.clicked.connect(self.on_shortcut_goto) 757 self.gotoButton.setText(QtGui.QApplication.translate("XmlEditor", "Goto line", None, QtGui.QApplication.UnicodeUTF8)) 758 self.gotoButton.setShortcut(QtGui.QApplication.translate("XmlEditor", "Ctrl+L", None, QtGui.QApplication.UnicodeUTF8)) 759 self.gotoButton.setToolTip('Open a goto dialog (Ctrl+L)') 760 self.horizontalLayout.addWidget(self.gotoButton) 761 # add a tag button 762 self.tagButton = self._create_tag_button(self) 763 self.horizontalLayout.addWidget(self.tagButton) 764 765 # add spacer 766 spacerItem = QtGui.QSpacerItem(515, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) 767 self.horizontalLayout.addItem(spacerItem) 768 # add line number label 769 self.pos_label = QtGui.QLabel() 770 self.horizontalLayout.addWidget(self.pos_label) 771 # add spacer 772 spacerItem = QtGui.QSpacerItem(515, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) 773 self.horizontalLayout.addItem(spacerItem) 774 # add save button 775 self.saveButton = QtGui.QPushButton(self) 776 self.saveButton.setObjectName("saveButton") 777 self.saveButton.clicked.connect(self.on_saveButton_clicked) 778 self.saveButton.setText(QtGui.QApplication.translate("XmlEditor", "Save", None, QtGui.QApplication.UnicodeUTF8)) 779 self.saveButton.setShortcut(QtGui.QApplication.translate("XmlEditor", "Ctrl+S", None, QtGui.QApplication.UnicodeUTF8)) 780 self.saveButton.setToolTip('Save the changes to the file (Ctrl+S)') 781 self.horizontalLayout.addWidget(self.saveButton) 782 self.verticalLayout.addWidget(self.buttons) 783 784 #create the find dialog 785 self.find_dialog = FindDialog(self) 786 self.find_dialog.buttonBox.clicked.connect(self.on_find_dialog_clicked) 787 self.find_dialog.found_files.itemActivated.connect(self.find_dialog_itemActivated) 788 789 # self._shortcut_find = QtGui.QShortcut(QtGui.QKeySequence(self.tr("Ctrl+F", "find text")), self) 790 # self._shortcut_find.activated.connect(self.on_shortcut_find) 791 792 #open the files 793 for f in filenames: 794 if f: 795 self.on_load_request(os.path.normpath(f), search_text) 796 797 self.readSettings()
798 # print "================ create", self.objectName() 799 # 800 # def __del__(self): 801 # print "******** destroy", self.objectName()
802 - def readSettings(self):
803 if nm.settings().store_geometry: 804 settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) 805 settings.beginGroup("editor") 806 maximized = settings.value("maximized", 'false') == 'true' 807 if maximized: 808 self.showMaximized() 809 else: 810 self.resize(settings.value("size", QtCore.QSize(800,640))) 811 self.move(settings.value("pos", QtCore.QPoint(0, 0))) 812 settings.endGroup()
813
814 - def storeSetting(self):
815 if nm.settings().store_geometry: 816 settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) 817 settings.beginGroup("editor") 818 settings.setValue("size", self.size()) 819 settings.setValue("pos", self.pos()) 820 settings.setValue("maximized", self.isMaximized()) 821 settings.endGroup()
822
823 - def on_load_request(self, filename, search_text=''):
824 ''' 825 Loads a file in a new tab or focus the tab, if the file is already open. 826 @param filename: the path to file 827 @type filename: C{str} 828 @param search_text: if not empty, searches in new document for first occurrence of the given text 829 @type search_text: C{str} (Default: C{Empty String}) 830 ''' 831 if not filename: 832 return 833 834 self.tabWidget.setUpdatesEnabled(False) 835 try: 836 if not filename in self.files: 837 tab_name = self.__getTabName(filename) 838 editor = Editor(filename, self.tabWidget) 839 tab_index = self.tabWidget.addTab(editor, tab_name) 840 self.files.append(filename) 841 editor.setCurrentPath(os.path.basename(filename)) 842 editor.load_request_signal.connect(self.on_load_request) 843 if filename.endswith('.launch'): 844 self.hl = XmlHighlighter(editor.document()) 845 else: 846 self.hl = YamlHighlighter(editor.document()) 847 editor.textChanged.connect(self.on_editor_textChanged) 848 editor.cursorPositionChanged.connect(self.on_editor_positionChanged) 849 editor.setFocus(QtCore.Qt.OtherFocusReason) 850 self.tabWidget.setCurrentIndex(tab_index) 851 else: 852 for i in range(self.tabWidget.count()): 853 if self.tabWidget.widget(i).filename == filename: 854 self.tabWidget.setCurrentIndex(i) 855 break 856 except: 857 import traceback 858 rospy.logwarn("Error while open %s: %s", filename, traceback.format_exc()) 859 self.tabWidget.setUpdatesEnabled(True) 860 if search_text: 861 if self.find(search_text, False): 862 if not self.find_dialog.search_pos.isNull(): 863 self.tabWidget.currentWidget().moveCursor(QtGui.QTextCursor.StartOfLine)
864
865 - def on_close_tab(self, tab_index):
866 ''' 867 Signal handling to close single tabs. 868 @param tab_index: tab index to close 869 @type tab_index: C{int} 870 ''' 871 try: 872 doremove = True 873 w = self.tabWidget.widget(tab_index) 874 if w.document().isModified(): 875 name = self.__getTabName(w.filename) 876 result = QtGui.QMessageBox.question(self, "Unsaved Changes", '\n\n'.join(["Save the file before closing?", name]), QtGui.QMessageBox.Yes | QtGui.QMessageBox.No | QtGui.QMessageBox.Cancel) 877 if result == QtGui.QMessageBox.Yes: 878 self.tabWidget.currentWidget().save() 879 elif result == QtGui.QMessageBox.No: 880 pass 881 else: 882 doremove = False 883 if doremove: 884 # remove the indexed files 885 if w.filename in self.files: 886 self.files.remove(w.filename) 887 # close tab 888 self.tabWidget.removeTab(tab_index) 889 # close editor, if no tabs are open 890 if not self.tabWidget.count(): 891 self.close() 892 except: 893 import traceback 894 rospy.logwarn("Error while close tab %s: %s", str(tab_index), traceback.format_exc())
895 896 # def hideEvent(self, event): 897 # self.close() 898
899 - def reject(self):
900 self.close()
901
902 - def closeEvent (self, event):
903 ''' 904 Test the open files for changes and save this if needed. 905 ''' 906 changed = [] 907 #get the names of all changed files 908 for i in range(self.tabWidget.count()): 909 w = self.tabWidget.widget(i) 910 if w.document().isModified(): 911 changed.append(self.__getTabName(w.filename)) 912 if changed: 913 # ask the user for save changes 914 if self.isHidden(): 915 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No 916 else: 917 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No | QtGui.QMessageBox.Cancel 918 result = QtGui.QMessageBox.question(self, "Unsaved Changes", '\n\n'.join(["Save the file before closing?", '\n'.join(changed)]), buttons) 919 if result == QtGui.QMessageBox.Yes: 920 for i in range(self.tabWidget.count()): 921 w = self.tabWidget.widget(i).save() 922 event.accept() 923 elif result == QtGui.QMessageBox.No: 924 event.accept() 925 else: 926 event.ignore() 927 else: 928 event.accept() 929 if event.isAccepted(): 930 self.storeSetting() 931 self.finished_signal.emit(self.init_filenames)
932
933 - def on_saveButton_clicked(self):
934 ''' 935 Saves the current document. This method is called if the C{save button} 936 was clicked. 937 ''' 938 saved, errors, msg = self.tabWidget.currentWidget().save() 939 if errors: 940 QtGui.QMessageBox.critical(self, "Error", msg) 941 self.tabWidget.setTabIcon(self.tabWidget.currentIndex(), self._error_icon) 942 self.tabWidget.setTabToolTip(self.tabWidget.currentIndex(), msg) 943 else: 944 self.tabWidget.setTabIcon(self.tabWidget.currentIndex(), self._empty_icon) 945 self.tabWidget.setTabToolTip(self.tabWidget.currentIndex(), '') 946 if saved: 947 self.on_editor_textChanged()
948
949 - def on_editor_textChanged(self):
950 ''' 951 If the content was changed, a '*' will be shown in the tab name. 952 ''' 953 tab_name = self.__getTabName(self.tabWidget.currentWidget().filename) 954 if (self.tabWidget.currentWidget().document().isModified()) or not QtCore.QFileInfo(self.tabWidget.currentWidget().filename).exists(): 955 tab_name = ''.join(['*', tab_name]) 956 self.tabWidget.setTabText(self.tabWidget.currentIndex(), tab_name)
957
958 - def on_editor_positionChanged(self):
959 ''' 960 Shows the number of the line and column in a label. 961 ''' 962 cursor = self.tabWidget.currentWidget().textCursor() 963 self.pos_label.setText('%s:%s'%(cursor.blockNumber()+1, cursor.columnNumber()))
964
965 - def on_shortcut_find(self):
966 ''' 967 Opens a find dialog. 968 ''' 969 self.find_dialog.show() 970 self.find_dialog.raise_() 971 self.find_dialog.activateWindow()
972
973 - def on_shortcut_goto(self):
974 ''' 975 Opens a C{goto} dialog. 976 ''' 977 value = 1 978 ok = False 979 try: 980 value, ok = QtGui.QInputDialog.getInt(self, "Goto", 981 self.tr("Line number:"), QtGui.QLineEdit.Normal, 982 minValue=1, step=1) 983 except: 984 value, ok = QtGui.QInputDialog.getInt(self, "Goto", 985 self.tr("Line number:"), QtGui.QLineEdit.Normal, 986 min=1, step=1) 987 if ok: 988 if value > self.tabWidget.currentWidget().document().blockCount(): 989 value = self.tabWidget.currentWidget().document().blockCount() 990 curpos = self.tabWidget.currentWidget().textCursor().blockNumber()+1 991 while curpos != value: 992 mov = QtGui.QTextCursor.NextBlock if curpos < value else QtGui.QTextCursor.PreviousBlock 993 self.tabWidget.currentWidget().moveCursor(mov) 994 curpos = self.tabWidget.currentWidget().textCursor().blockNumber()+1 995 self.tabWidget.currentWidget().setFocus(QtCore.Qt.ActiveWindowFocusReason)
996
997 - def __getTabName(self, file):
998 base = os.path.basename(file).replace('.launch', '') 999 (package, path) = package_name(os.path.dirname(file)) 1000 return ''.join([str(base), ' [', str(package),']'])
1001
1002 - def on_find_dialog_clicked(self, button):
1003 ''' 1004 Method to handle the button actions of the C{find dialog}. 1005 ''' 1006 if button == self.find_dialog.find_button: 1007 self.find(self.find_dialog.search_field.text(), self.find_dialog.recursive.isChecked()) 1008 elif button == self.find_dialog.replace_button: 1009 self.find_dialog.recursive.setChecked(False) 1010 cursor = self.tabWidget.currentWidget().textCursor() 1011 if self.find_dialog.search_field.text() and cursor.selectedText() == self.find_dialog.search_field.text(): 1012 cursor.insertText(self.find_dialog.replace_field.text()) 1013 currentLine = str(cursor.blockNumber()+1) 1014 self.find_dialog.result_label.setText(''.join(["'", self.find_dialog.search_text, "'", ' replaced at line: ', currentLine, ' by ', "'", self.find_dialog.replace_field.text(),"'"])) 1015 self.tabWidget.currentWidget().setTextCursor(cursor) 1016 self.find(self.find_dialog.search_field.text(), self.find_dialog.recursive.isChecked())
1017
1018 - def find(self, search_text, recursive):
1019 ''' 1020 Searches for text in the current text editor. If `recursive` is C{True}, 1021 the included files will be searched. 1022 @param search_text: text to find 1023 @type search_text: C{str} 1024 @param recursive: search in included files if this is C{True} 1025 @type recursive: C{bool} 1026 ''' 1027 found = False 1028 if self.find_dialog.search_text != search_text: 1029 self.find_dialog.search_pos = QtGui.QTextCursor() 1030 self.find_dialog.found_files.clear() 1031 self.find_dialog.found_files.setVisible(False) 1032 self.find_dialog.result_label.setText(''.join(["'", search_text, "'", ' not found!'])) 1033 self.find_dialog.search_text = search_text 1034 if search_text: 1035 if recursive: 1036 files = self.tabWidget.currentWidget().fileWithText(search_text) 1037 items = list(set(files)) 1038 self.find_dialog.result_label.setText(''.join(["'", search_text, "'", ' found in ', str(len(items)), ' files:'])) 1039 self.find_dialog.found_files.clear() 1040 self.find_dialog.found_files.addItems(items) 1041 self.find_dialog.found_files.setVisible(True) 1042 # self.find_dialog.resize(self.find_dialog.found_files.contentsSize()) 1043 found = True 1044 else: 1045 tmp_pos = self.find_dialog.search_pos 1046 self.find_dialog.search_pos = self.tabWidget.currentWidget().document().find(search_text, self.find_dialog.search_pos.position()+1) 1047 if self.find_dialog.search_pos.isNull() and not tmp_pos.isNull(): 1048 self.find_dialog.search_pos = self.tabWidget.currentWidget().document().find(search_text, self.find_dialog.search_pos.position()+1) 1049 # do recursive search 1050 if not self.find_dialog.search_pos.isNull(): 1051 self.tabWidget.currentWidget().setTextCursor(self.find_dialog.search_pos) 1052 currentTabName = self.tabWidget.tabText(self.tabWidget.currentIndex()) 1053 currentLine = str(self.tabWidget.currentWidget().textCursor().blockNumber()+1) 1054 self.find_dialog.result_label.setText(''.join(["'", search_text, "'", ' found at line: ', currentLine, ' in ', "'", currentTabName,"'"])) 1055 found = True 1056 else: 1057 self.find_dialog.result_label.setText(''.join(["'", search_text, "'", ' not found!'])) 1058 return found
1059
1060 - def find_dialog_itemActivated(self, item):
1061 ''' 1062 On recursive search all files contained the search text are listed. If one of 1063 this file is activated, it will be open in a new tab and the cursor moved to 1064 the C{search text} position. 1065 @param item: The activated item of the C{QListWidget} 1066 @type item: L{PySide.QtGui.QListWidgetItem} 1067 ''' 1068 self.find_dialog.recursive.setChecked(False) 1069 self.on_load_request(item.text(), self.find_dialog.search_text) 1070 self.find(self.find_dialog.search_text, False) 1071 currentTabName = self.tabWidget.tabText(self.tabWidget.currentIndex()) 1072 currentLine = str(self.tabWidget.currentWidget().textCursor().blockNumber()+1) 1073 self.find_dialog.result_label.setText(''.join(["'", self.find_dialog.search_text, "'", ' found at line: ', currentLine, ' in ', "'", currentTabName,"'"]))
1074 1075 ############################################################################## 1076 # LAUNCH TAG insertion 1077 ############################################################################## 1078
1079 - def _create_tag_button(self, parent=None):
1080 btn = QtGui.QPushButton(parent) 1081 btn.setObjectName("tagButton") 1082 btn.setText(QtGui.QApplication.translate("XmlEditor", "Add tag", None, QtGui.QApplication.UnicodeUTF8)) 1083 btn.setShortcut(QtGui.QApplication.translate("XmlEditor", "Ctrl+T", None, QtGui.QApplication.UnicodeUTF8)) 1084 btn.setToolTip('Adds a ROS launch tag to launch file (Ctrl+T)') 1085 # creates a tag menu 1086 tag_menu = QtGui.QMenu(btn) 1087 # group tag 1088 add_group_tag_action = QtGui.QAction("<group>", self, statusTip="", triggered=self._on_add_group_tag) 1089 tag_menu.addAction(add_group_tag_action) 1090 # node tag 1091 add_node_tag_action = QtGui.QAction("<node>", self, statusTip="", triggered=self._on_add_node_tag) 1092 tag_menu.addAction(add_node_tag_action) 1093 # node tag with all attributes 1094 add_node_tag_all_action = QtGui.QAction("<node all>", self, statusTip="", triggered=self._on_add_node_tag_all) 1095 tag_menu.addAction(add_node_tag_all_action) 1096 # include tag with all attributes 1097 add_include_tag_all_action = QtGui.QAction("<include>", self, statusTip="", triggered=self._on_add_include_tag_all) 1098 tag_menu.addAction(add_include_tag_all_action) 1099 # remap 1100 add_remap_tag_action = QtGui.QAction("<remap>", self, statusTip="", triggered=self._on_add_remap_tag) 1101 tag_menu.addAction(add_remap_tag_action) 1102 # env tag 1103 add_env_tag_action = QtGui.QAction("<env>", self, statusTip="", triggered=self._on_add_env_tag) 1104 tag_menu.addAction(add_env_tag_action) 1105 # param tag 1106 add_param_tag_action = QtGui.QAction("<param>", self, statusTip="", triggered=self._on_add_param_tag) 1107 tag_menu.addAction(add_param_tag_action) 1108 # param tag with all attributes 1109 add_param_tag_all_action = QtGui.QAction("<param all>", self, statusTip="", triggered=self._on_add_param_tag_all) 1110 tag_menu.addAction(add_param_tag_all_action) 1111 # rosparam tag with all attributes 1112 add_rosparam_tag_all_action = QtGui.QAction("<rosparam>", self, statusTip="", triggered=self._on_add_rosparam_tag_all) 1113 tag_menu.addAction(add_rosparam_tag_all_action) 1114 # arg tag with default definition 1115 add_arg_tag_default_action = QtGui.QAction("<arg default>", self, statusTip="", triggered=self._on_add_arg_tag_default) 1116 tag_menu.addAction(add_arg_tag_default_action) 1117 # arg tag with value definition 1118 add_arg_tag_value_action = QtGui.QAction("<arg value>", self, statusTip="", triggered=self._on_add_arg_tag_value) 1119 tag_menu.addAction(add_arg_tag_value_action) 1120 1121 # test tag 1122 add_test_tag_action = QtGui.QAction("<test>", self, statusTip="", triggered=self._on_add_test_tag) 1123 tag_menu.addAction(add_test_tag_action) 1124 # test tag with all attributes 1125 add_test_tag_all_action = QtGui.QAction("<test all>", self, statusTip="", triggered=self._on_add_test_tag_all) 1126 tag_menu.addAction(add_test_tag_all_action) 1127 1128 1129 btn.setMenu(tag_menu) 1130 return btn
1131
1132 - def _insert_text(self, text):
1133 cursor = self.tabWidget.currentWidget().textCursor() 1134 if not cursor.isNull(): 1135 col = cursor.columnNumber() 1136 spaces = ''.join([' ' for i in range(col)]) 1137 cursor.insertText(text.replace('\n','\n%s'%spaces)) 1138 self.tabWidget.currentWidget().setFocus(QtCore.Qt.OtherFocusReason)
1139
1140 - def _on_add_group_tag(self):
1141 self._insert_text('<group ns="namespace" clear_params="true|false">\n' 1142 '</group>')
1143
1144 - def _on_add_node_tag(self):
1145 self._insert_text('<node name="NAME" pkg="PKG" type="BIN">\n' 1146 '</node>')
1147
1148 - def _on_add_node_tag_all(self):
1149 self._insert_text('<node name="NAME" pkg="PKG" type="BIN"\n' 1150 ' args="arg1" machine="machine-name"\n' 1151 ' respawn="true" required="true"\n' 1152 ' ns="foo" clear_params="true|false"\n' 1153 ' output="log|screen" cwd="ROS_HOME|node"\n' 1154 ' launch-prefix="prefix arguments">\n' 1155 '</node>')
1156
1157 - def _on_add_include_tag_all(self):
1158 self._insert_text('<include file="$(find pkg-name)/path/filename.xml"\n' 1159 ' ns="foo" clear_params="true|false"\n' 1160 '</include>')
1161
1162 - def _on_add_remap_tag(self):
1163 self._insert_text('<remap from="original" to="new"/>')
1164
1165 - def _on_add_env_tag(self):
1166 self._insert_text('<env name="variable" value="value"/>')
1167
1168 - def _on_add_param_tag(self):
1169 self._insert_text('<param name="namespace/name" value="value" />')
1170
1171 - def _on_add_param_tag_all(self):
1172 self._insert_text('<param name="namespace/name" value="value"\n' 1173 ' type="str|int|double|bool"\n' 1174 ' textfile="$(find pkg-name)/path/file.txt"\n' 1175 ' binfile="$(find pkg-name)/path/file"\n' 1176 ' command="$(find pkg-name)/exe \'$(find pkg-name)/arg.txt\'"\n' 1177 '</param>')
1178
1179 - def _on_add_rosparam_tag_all(self):
1180 self._insert_text('<rosparam param="param-name"\n' 1181 ' file="$(find pkg-name)/path/foo.yaml"\n' 1182 ' command="load|dump|delete"\n' 1183 ' ns="namespace"\n' 1184 '</rosparam>')
1185
1186 - def _on_add_arg_tag_default(self):
1187 self._insert_text('<arg name="foo" default="1" />')
1188
1189 - def _on_add_arg_tag_value(self):
1190 self._insert_text('<arg name="foo" value="bar" />')
1191
1192 - def _on_add_test_tag(self):
1193 self._insert_text('<test name="NAME" pkg="PKG" type="BIN" test-name="test_name"\n' 1194 '</test>')
1195
1196 - def _on_add_test_tag_all(self):
1197 self._insert_text('<test name="NAME" pkg="PKG" type="BIN" test-name="test_name"\n' 1198 ' args="arg1" time-limit="60.0"\n' 1199 ' ns="foo" clear_params="true|false"\n' 1200 ' cwd="ROS_HOME|node" retry="0"\n' 1201 ' launch-prefix="prefix arguments">\n' 1202 '</test>')
1203