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