$search
00001 /* 00002 Aseba - an event-based framework for distributed robot control 00003 Copyright (C) 2007--2012: 00004 Stephane Magnenat <stephane at magnenat dot net> 00005 (http://stephane.magnenat.net) 00006 and other contributors, see authors.txt for details 00007 00008 This program is free software: you can redistribute it and/or modify 00009 it under the terms of the GNU Lesser General Public License as published 00010 by the Free Software Foundation, version 3 of the License. 00011 00012 This program is distributed in the hope that it will be useful, 00013 but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00015 GNU Lesser General Public License for more details. 00016 00017 You should have received a copy of the GNU Lesser General Public License 00018 along with this program. If not, see <http://www.gnu.org/licenses/>. 00019 */ 00020 00021 #include "AeslEditor.h" 00022 #include "MainWindow.h" 00023 #include "CustomWidgets.h" 00024 #include "../utils/utils.h" 00025 #include <QtGui> 00026 #include <QtGlobal> 00027 00028 #include <AeslEditor.moc> 00029 00030 namespace Aseba 00031 { 00034 00035 AeslHighlighter::AeslHighlighter(AeslEditor *editor, QTextDocument *parent) : 00036 QSyntaxHighlighter(parent), 00037 editor(editor) 00038 { 00039 HighlightingRule rule; 00040 00041 // keywords 00042 QTextCharFormat keywordFormat; 00043 keywordFormat.setForeground(Qt::darkRed); 00044 QStringList keywordPatterns; 00045 keywordPatterns << "\\bemit\\b" << "\\bwhile\\b" << "\\bdo\\b" 00046 << "\\bfor\\b" << "\\bin\\b" << "\\bstep\\b" 00047 << "\\bif\\b" << "\\bthen\\b" << "\\belse\\b" << "\\belseif\\b" 00048 << "\\bend\\b" << "\\bvar\\b" << "\\bcall\\b" 00049 << "\\bonevent\\b" << "\\bontimer\\b" << "\\bwhen\\b" 00050 << "\\band\\b" << "\\bor\\b" << "\\bnot\\b" << "\\babs\\b" 00051 << "\\bsub\\b" << "\\bcallsub\\b" << "\\breturn\\b"; 00052 foreach (QString pattern, keywordPatterns) 00053 { 00054 rule.pattern = QRegExp(pattern); 00055 rule.format = keywordFormat; 00056 highlightingRules.append(rule); 00057 } 00058 00059 // literals 00060 QTextCharFormat literalsFormat; 00061 literalsFormat.setForeground(Qt::darkBlue); 00062 rule.pattern = QRegExp("\\b(-{0,1}\\d+|0x([0-9]|[a-f]|[A-F])+|0b[0-1]+)\\b"); 00063 rule.format = literalsFormat; 00064 highlightingRules.append(rule); 00065 00066 // comments # 00067 QTextCharFormat commentFormat; 00068 commentFormat.setForeground(Qt::gray); 00069 rule.pattern = QRegExp("(?!\\*)#(?!\\*).*"); // '#' without '*' right after or before 00070 rule.format = commentFormat; 00071 highlightingRules.append(rule); 00072 00073 rule.pattern = QRegExp("^#(?!\\*).*"); // '#' without '*' right after + beginning of line 00074 rule.format = commentFormat; 00075 highlightingRules.append(rule); 00076 00077 // comments #* ... *# 00078 rule.pattern = QRegExp("#\\*.*\\*#"); 00079 rule.format = commentFormat; 00080 highlightingRules.append(rule); 00081 00082 // multilines block of comments #* ... [\n]* ... *# 00083 commentBlockRules.begin = QRegExp("#\\*(?!.*\\*#)"); // '#*' with no corresponding '*#' 00084 commentBlockRules.end = QRegExp(".*\\*#"); 00085 commentBlockRules.format = commentFormat; 00086 00087 // todo/fixme 00088 QTextCharFormat todoFormat; 00089 todoFormat.setForeground(Qt::black); 00090 todoFormat.setBackground(QColor(255, 192, 192)); 00091 rule.pattern = QRegExp("#.*(\\bTODO\\b|\\bFIXME\\b).*"); 00092 rule.format = todoFormat; 00093 highlightingRules.append(rule); 00094 } 00095 00096 void AeslHighlighter::highlightBlock(const QString &text) 00097 { 00098 AeslEditorUserData *uData = polymorphic_downcast_or_null<AeslEditorUserData *>(currentBlockUserData()); 00099 00100 // current line background blue 00101 bool isActive = uData && uData->properties.contains("active"); 00102 bool isExecutionError = uData && uData->properties.contains("executionError"); 00103 bool isBreakpointPending = uData && uData->properties.contains("breakpointPending"); 00104 bool isBreakpoint = uData && uData->properties.contains("breakpoint"); 00105 00106 QColor breakpointPendingColor(255, 240, 178); 00107 QColor breakpointColor(255, 211, 178); 00108 QColor activeColor(220, 220, 255); 00109 QColor errorColor(240, 100, 100); 00110 00111 00112 //This code seemed to have caused trash at a point but we cannot reproduce them now 00113 // use the QTextEdit::ExtraSelection class to highlight the background 00114 // of special lines (breakpoints, active line,...) 00115 QList<QTextEdit::ExtraSelection> extraSelections; 00116 if (currentBlock().blockNumber() != 0) 00117 // past the first line, we recall the previous "selections" 00118 extraSelections = editor->extraSelections(); 00119 00120 // prepare the current "selection" 00121 QTextEdit::ExtraSelection selection; 00122 selection.format.setProperty(QTextFormat::FullWidthSelection, true); 00123 selection.cursor = QTextCursor(currentBlock()); 00124 00125 if (isBreakpointPending) 00126 selection.format.setBackground(breakpointPendingColor); 00127 if (isBreakpoint) 00128 selection.format.setBackground(breakpointColor); 00129 if (editor->debugging) 00130 { 00131 if (isActive) 00132 selection.format.setBackground(activeColor); 00133 if (isExecutionError) 00134 selection.format.setBackground(errorColor); 00135 } 00136 00137 // we are done 00138 extraSelections.append(selection); 00139 editor->setExtraSelections(extraSelections); 00140 00141 /* 00142 00143 // This is backup code in case the ExtraSelection creates trashes 00144 00145 QColor specialBackground("white"); 00146 if (isBreakpointPending) 00147 specialBackground = breakpointPendingColor; 00148 if (isBreakpoint) 00149 specialBackground = breakpointColor; 00150 if (editor->debugging) 00151 { 00152 if (isActive) 00153 specialBackground = activeColor; 00154 if (isExecutionError) 00155 specialBackground = errorColor; 00156 } 00157 if (specialBackground != "white") 00158 { 00159 QTextCharFormat format; 00160 format.setBackground(specialBackground); 00161 setFormat(0, text.length(), format); 00162 } 00163 */ 00164 00165 // syntax highlight 00166 foreach (HighlightingRule rule, highlightingRules) 00167 { 00168 QRegExp expression(rule.pattern); 00169 int index = text.indexOf(expression); 00170 while (index >= 0) 00171 { 00172 int length = expression.matchedLength(); 00173 QTextCharFormat format = rule.format; 00174 00175 /* 00176 // This is backup code in case the ExtraSelection creates trashes 00177 if (specialBackground != "white") 00178 format.setBackground(specialBackground);*/ 00179 00180 setFormat(index, length, format); 00181 index = text.indexOf(expression, index + length); 00182 } 00183 } 00184 00185 // Prepare the format for multilines comment block 00186 int index; 00187 QTextCharFormat format = commentBlockRules.format; 00188 00189 // Comply with the breakpoint formatting 00190 if (isBreakpointPending) 00191 format.setBackground(breakpointPendingColor); 00192 00193 // Search for a multilines comment block 00194 setCurrentBlockState(NO_COMMENT); // No comment 00195 if (previousBlockState() != COMMENT) 00196 { 00197 // Search for a beginning 00198 if ((index = text.indexOf(commentBlockRules.begin)) != -1) 00199 { 00200 // Found one 00201 setFormat(index, text.length() - index, format); 00202 setCurrentBlockState(COMMENT); 00203 } 00204 } 00205 else 00206 { 00207 // Search for an end 00208 if ((index = text.indexOf(commentBlockRules.end)) != -1) 00209 { 00210 // Found one 00211 int length = commentBlockRules.end.matchedLength(); 00212 setFormat(0, length, format); 00213 setCurrentBlockState(NO_COMMENT); 00214 } 00215 else 00216 { 00217 // Still inside a block 00218 setFormat(0, text.length(), format); 00219 setCurrentBlockState(COMMENT); 00220 } 00221 } 00222 00223 // error word in red 00224 if (uData && uData->properties.contains("errorPos")) 00225 { 00226 int pos = uData->properties["errorPos"].toInt(); 00227 int len = 0; 00228 00229 if (pos + len < text.length()) 00230 { 00231 // find length of number or word 00232 while (pos + len < text.length()) 00233 if ( 00234 (!text[pos + len].isDigit()) && 00235 (!text[pos + len].isLetter()) && 00236 (text[pos + len] != '_') && 00237 (text[pos + len] != '.') 00238 ) 00239 break; 00240 else 00241 len++; 00242 } 00243 len = len > 0 ? len : 1; 00244 setFormat(pos, len, Qt::red); 00245 } 00246 } 00247 00248 AeslEditorSidebar::AeslEditorSidebar(AeslEditor* editor) : 00249 QWidget(editor), 00250 editor(editor), 00251 currentSizeHint(0,0), 00252 verticalScroll(0) 00253 { 00254 setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); 00255 connect(editor->verticalScrollBar(), SIGNAL(valueChanged(int)), SLOT(scroll(int))); 00256 connect(editor, SIGNAL(textChanged()), SLOT(update())); 00257 } 00258 00259 void AeslEditorSidebar::scroll(int dy) 00260 { 00261 verticalScroll = dy; 00262 update(); // repaint 00263 } 00264 00265 QSize AeslEditorSidebar::sizeHint() const 00266 { 00267 return QSize(idealWidth(), 0); 00268 } 00269 00270 void AeslEditorSidebar::paintEvent(QPaintEvent *event) 00271 { 00272 QSize newSizeHint = sizeHint(); 00273 00274 if (currentSizeHint != newSizeHint) 00275 { 00276 // geometry has changed, recompute the layout based on sizeHint() 00277 updateGeometry(); 00278 currentSizeHint = newSizeHint; 00279 } 00280 } 00281 00282 int AeslEditorSidebar::posToLineNumber(int y) 00283 { 00284 00285 // iterate over all text blocks 00286 QTextBlock block = editor->document()->firstBlock(); 00287 int offset = editor->contentsRect().top(); // top margin 00288 00289 while (block.isValid()) 00290 { 00291 QRectF bounds = block.layout()->boundingRect(); 00292 bounds.translate(0, offset + block.layout()->position().y() - verticalScroll); 00293 if (y > bounds.top() && y < bounds.bottom()) 00294 { 00295 // this is it 00296 return block.blockNumber(); 00297 } 00298 00299 block = block.next(); 00300 } 00301 00302 // not found 00303 return -1; 00304 } 00305 00306 AeslLineNumberSidebar::AeslLineNumberSidebar(AeslEditor *editor) : 00307 AeslEditorSidebar(editor) 00308 { 00309 } 00310 00311 void AeslLineNumberSidebar::showLineNumbers(bool state) 00312 { 00313 setVisible(state); 00314 } 00315 00316 void AeslLineNumberSidebar::paintEvent(QPaintEvent *event) 00317 { 00318 AeslEditorSidebar::paintEvent(event); 00319 00320 // begin painting 00321 QPainter painter(this); 00322 00323 // fill the background 00324 painter.fillRect(event->rect(), QColor(210, 210, 210)); 00325 00326 // get the editor's painting area 00327 QRect editorRect = editor->contentsRect(); 00328 00329 // enable clipping to match the vertical painting area of the editor 00330 painter.setClipRect(QRect(0, editorRect.top(), width(), editorRect.bottom()), Qt::ReplaceClip ); 00331 painter.setClipping(true); 00332 00333 // define the painting area for linenumbers (top / bottom are identic to the editor)) 00334 QRect region = editorRect; 00335 region.setLeft(0); 00336 region.setRight(idealWidth()); 00337 00338 // get the first text block 00339 QTextBlock block = editor->document()->firstBlock(); 00340 00341 painter.setPen(Qt::darkGray); 00342 // iterate over all text blocks 00343 // FIXME: do clever painting 00344 while(block.isValid()) 00345 { 00346 if (block.isVisible()) 00347 { 00348 QString number = QString::number(block.blockNumber() + 1); 00349 // paint the line number 00350 int y = block.layout()->position().y() + region.top() - verticalScroll; 00351 painter.drawText(0, y, width(), fontMetrics().height(), Qt::AlignRight, number); 00352 } 00353 00354 block = block.next(); 00355 } 00356 } 00357 00358 int AeslLineNumberSidebar::idealWidth() const 00359 { 00360 // This is based on the Qt code editor example 00361 // http://doc.qt.nokia.com/latest/widgets-codeeditor.html 00362 int digits = 1; 00363 int linenumber = editor->document()->blockCount(); 00364 while (linenumber >= 10) { 00365 linenumber /= 10; 00366 digits++; 00367 } 00368 00369 int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits; 00370 00371 return space; 00372 } 00373 00374 AeslBreakpointSidebar::AeslBreakpointSidebar(AeslEditor *editor) : 00375 AeslEditorSidebar(editor), 00376 borderSize(1) 00377 { 00378 // define the breakpoint geometry, according to the font metrics 00379 QFontMetrics metrics = fontMetrics(); 00380 int lineSpacing = metrics.lineSpacing(); 00381 breakpoint = QRect(borderSize, borderSize, lineSpacing - 2*borderSize, lineSpacing - 2*borderSize); 00382 } 00383 00384 void AeslBreakpointSidebar::paintEvent(QPaintEvent *event) 00385 { 00386 AeslEditorSidebar::paintEvent(event); 00387 00388 // begin painting 00389 QPainter painter(this); 00390 00391 // fill the background 00392 painter.fillRect(event->rect(), QColor(210, 210, 210)); 00393 00394 // get the editor's painting area 00395 QRect editorRect = editor->contentsRect(); 00396 00397 // enable clipping to match the vertical painting area of the editor 00398 painter.setClipRect(QRect(0, editorRect.top(), width(), editorRect.bottom()), Qt::ReplaceClip ); 00399 painter.setClipping(true); 00400 00401 // define the painting area for breakpoints (top / bottom are identic to the editor) 00402 QRect region = editorRect; 00403 region.setLeft(0); 00404 region.setRight(idealWidth()); 00405 00406 // get the first text block 00407 QTextBlock block = editor->document()->firstBlock(); 00408 00409 painter.setPen(Qt::red); 00410 painter.setBrush(Qt::red); 00411 // iterate over all text blocks 00412 // FIXME: do clever painting 00413 while(block.isValid()) 00414 { 00415 if (block.isVisible()) 00416 { 00417 // get the block data and check for a breakpoint 00418 if (editor->isBreakpoint(block)) 00419 { 00420 // paint the breakpoint 00421 int y = block.layout()->position().y() + region.top() - verticalScroll; 00422 // FIXME: Hughly breakpoing design... 00423 painter.drawRect(breakpoint.translated(0, y)); 00424 } 00425 00426 } 00427 00428 block = block.next(); 00429 } 00430 } 00431 00432 void AeslBreakpointSidebar::mousePressEvent(QMouseEvent *event) 00433 { 00434 // click in the breakpoint column 00435 int line = posToLineNumber(event->pos().y()); 00436 editor->toggleBreakpoint(editor->document()->findBlockByNumber(line)); 00437 } 00438 00439 int AeslBreakpointSidebar::idealWidth() const 00440 { 00441 return breakpoint.width() + 2*borderSize; 00442 } 00443 00444 AeslEditor::AeslEditor(const ScriptTab* tab) : 00445 tab(tab), 00446 debugging(false), 00447 dropSourceWidget(0), 00448 completer(0), 00449 vardefRegexp("^var .*"), 00450 leftValueRegexp("^\\w+\\s*=.*"), 00451 previousContext(UnknownContext), 00452 editingLeftValue(false) 00453 { 00454 QFont font; 00455 font.setFamily(""); 00456 font.setStyleHint(QFont::TypeWriter); 00457 font.setFixedPitch(true); 00458 //font.setPointSize(10); 00459 setFont(font); 00460 setAcceptDrops(true); 00461 setAcceptRichText(false); 00462 setTabStopWidth( QFontMetrics(font).width(' ') * 4); 00463 00464 #ifdef Q_WS_WIN 00465 // Fix selection color on Windows when the find dialog is active 00466 // See issue 93: https://github.com/aseba-community/aseba/issues/93 00467 // Code from: http://stackoverflow.com/questions/9880441/qt-how-to-display-selected-text-in-an-inactive-window 00468 QPalette p = palette(); 00469 p.setColor(QPalette::Inactive, QPalette::Highlight, p.color(QPalette::Active, QPalette::Highlight)); 00470 p.setColor(QPalette::Inactive, QPalette::HighlightedText, p.color(QPalette::Active, QPalette::HighlightedText)); 00471 setPalette(p); 00472 #endif // Q_WS_WIN 00473 00474 // create completer 00475 completer = new QCompleter(this); 00476 //completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel); 00477 completer->setCaseSensitivity(Qt::CaseInsensitive); 00478 completer->setWrapAround(false); 00479 completer->setWidget(this); 00480 completer->setCompletionMode(QCompleter::PopupCompletion); 00481 QObject::connect(completer, SIGNAL(activated(QString)), SLOT(insertCompletion(QString))); 00482 } 00483 00484 void AeslEditor::dropEvent(QDropEvent *event) 00485 { 00486 dropSourceWidget = event->source(); 00487 QTextEdit::dropEvent(event); 00488 dropSourceWidget = 0; 00489 setFocus(Qt::MouseFocusReason); 00490 } 00491 00492 void AeslEditor::insertFromMimeData ( const QMimeData * source ) 00493 { 00494 QTextCursor cursor(this->textCursor()); 00495 00496 // check whether we are at the beginning of a line 00497 bool startOfLine(cursor.atBlockStart()); 00498 const int posInBlock(cursor.position() - cursor.block().position()); 00499 if (!startOfLine && posInBlock) 00500 { 00501 const QString startText(cursor.block().text().left(posInBlock)); 00502 startOfLine = !startText.contains(QRegExp("\\S")); 00503 } 00504 00505 // if beginning of a line and source widget is known, add some helper text 00506 if (dropSourceWidget && startOfLine) 00507 { 00508 const NodeTab* nodeTab(dynamic_cast<const NodeTab*>(tab)); 00509 QString prefix(""); // before the text 00510 QString midfix(""); // between the text and the cursor 00511 QString postfix(""); // after the curser 00512 if (dropSourceWidget == nodeTab->vmFunctionsView) 00513 { 00514 // inserting function 00515 prefix = "call "; 00516 midfix = "("; 00517 // fill call from doc 00518 const TargetDescription *desc = nodeTab->vmFunctionsModel->descriptionRead; 00519 const std::wstring funcName = source->text().toStdWString(); 00520 for (size_t i = 0; i < desc->nativeFunctions.size(); i++) 00521 { 00522 const TargetDescription::NativeFunction native(desc->nativeFunctions[i]); 00523 if (native.name == funcName) 00524 { 00525 for (size_t j = 0; j < native.parameters.size(); ++j) 00526 { 00527 postfix += QString::fromStdWString(native.parameters[j].name); 00528 if (j + 1 < native.parameters.size()) 00529 postfix += ", "; 00530 } 00531 break; 00532 } 00533 } 00534 postfix += ")\n"; 00535 } 00536 else if (dropSourceWidget == nodeTab->vmMemoryView) 00537 { 00538 const std::wstring varName = source->text().toStdWString(); 00539 if (nodeTab->vmMemoryModel->getVariableSize(QString::fromStdWString(varName)) > 1) 00540 { 00541 midfix = "["; 00542 postfix = "] "; 00543 } 00544 else 00545 midfix = " "; 00546 } 00547 else if (dropSourceWidget == nodeTab->vmLocalEvents) 00548 { 00549 // inserting local event 00550 prefix = "onevent "; 00551 midfix = "\n"; 00552 } 00553 else if (dropSourceWidget == nodeTab->mainWindow->eventsDescriptionsView) 00554 { 00555 // inserting global event 00556 prefix = "onevent "; 00557 midfix = "\n"; 00558 } 00559 00560 cursor.beginEditBlock(); 00561 cursor.insertText(prefix + source->text() + midfix); 00562 const int pos = cursor.position(); 00563 cursor.insertText(postfix); 00564 cursor.setPosition(pos); 00565 cursor.endEditBlock(); 00566 00567 this->setTextCursor(cursor); 00568 } 00569 else 00570 cursor.insertText(source->text()); 00571 00572 } 00573 00574 void AeslEditor::contextMenuEvent ( QContextMenuEvent * e ) 00575 { 00576 // create menu 00577 QMenu *menu = createStandardContextMenu(); 00578 if (!isReadOnly()) 00579 { 00580 QAction *breakpointAction; 00581 menu->addSeparator(); 00582 00583 // check for breakpoint 00584 QTextBlock block = cursorForPosition(e->pos()).block(); 00585 //AeslEditorUserData *uData = polymorphic_downcast<AeslEditorUserData *>(block.userData()); 00586 bool breakpointPresent = isBreakpoint(block); 00587 00588 // add action 00589 if (breakpointPresent) 00590 breakpointAction = menu->addAction(tr("Clear breakpoint")); 00591 else 00592 breakpointAction = menu->addAction(tr("Set breakpoint")); 00593 QAction *breakpointClearAllAction = menu->addAction(tr("Clear all breakpoints")); 00594 00595 // execute menu 00596 QAction* selectedAction = menu->exec(e->globalPos()); 00597 00598 // do actions 00599 if (selectedAction == breakpointAction) 00600 { 00601 // modify editor state 00602 if (breakpointPresent) 00603 { 00604 // clear breakpoint 00605 clearBreakpoint(block); 00606 } 00607 else 00608 { 00609 // set breakpoint 00610 setBreakpoint(block); 00611 } 00612 } 00613 if (selectedAction == breakpointClearAllAction) 00614 { 00615 clearAllBreakpoints(); 00616 } 00617 } 00618 else 00619 menu->exec(e->globalPos()); 00620 delete menu; 00621 } 00622 00623 bool AeslEditor::isBreakpoint() 00624 { 00625 return isBreakpoint(textCursor().block()); 00626 } 00627 00628 bool AeslEditor::isBreakpoint(QTextBlock block) 00629 { 00630 AeslEditorUserData *uData = polymorphic_downcast_or_null<AeslEditorUserData *>(block.userData()); 00631 return (uData && (uData->properties.contains("breakpoint") || uData->properties.contains("breakpointPending") )) ; 00632 } 00633 00634 bool AeslEditor::isBreakpoint(int line) 00635 { 00636 QTextBlock block = document()->findBlockByNumber(line); 00637 return isBreakpoint(block); 00638 } 00639 00640 void AeslEditor::toggleBreakpoint() 00641 { 00642 toggleBreakpoint(textCursor().block()); 00643 } 00644 00645 void AeslEditor::toggleBreakpoint(QTextBlock block) 00646 { 00647 if (isBreakpoint(block)) 00648 clearBreakpoint(block); 00649 else 00650 setBreakpoint(block); 00651 } 00652 00653 void AeslEditor::setBreakpoint() 00654 { 00655 setBreakpoint(textCursor().block()); 00656 } 00657 00658 void AeslEditor::setBreakpoint(QTextBlock block) 00659 { 00660 AeslEditorUserData *uData = polymorphic_downcast_or_null<AeslEditorUserData *>(block.userData()); 00661 if (!uData) 00662 { 00663 // create user data 00664 uData = new AeslEditorUserData("breakpointPending"); 00665 block.setUserData(uData); 00666 } 00667 else 00668 uData->properties.insert("breakpointPending", QVariant()); 00669 emit breakpointSet(block.blockNumber()); 00670 } 00671 00672 void AeslEditor::clearBreakpoint() 00673 { 00674 clearBreakpoint(textCursor().block()); 00675 } 00676 00677 void AeslEditor::clearBreakpoint(QTextBlock block) 00678 { 00679 AeslEditorUserData *uData = polymorphic_downcast_or_null<AeslEditorUserData *>(block.userData()); 00680 uData->properties.remove("breakpointPending"); 00681 uData->properties.remove("breakpoint"); 00682 if (uData->properties.isEmpty()) 00683 { 00684 // garbage collect UserData 00685 block.setUserData(0); 00686 } 00687 emit breakpointCleared(block.blockNumber()); 00688 } 00689 00690 void AeslEditor::clearAllBreakpoints() 00691 { 00692 for (QTextBlock it = document()->begin(); it != document()->end(); it = it.next()) 00693 { 00694 AeslEditorUserData *uData = polymorphic_downcast_or_null<AeslEditorUserData *>(it.userData()); 00695 if (uData) 00696 { 00697 uData->properties.remove("breakpoint"); 00698 uData->properties.remove("breakpointPending"); 00699 } 00700 } 00701 emit breakpointClearedAll(); 00702 } 00703 00704 void AeslEditor::commentAndUncommentSelection(CommentOperation commentOperation) 00705 { 00706 QTextCursor cursor = textCursor(); 00707 bool moveFailed = false; 00708 00709 // get the last line of the selection 00710 QTextBlock endBlock = document()->findBlock(cursor.selectionEnd()); 00711 int lineEnd = endBlock.blockNumber(); 00712 int positionInEndBlock = cursor.selectionEnd() - endBlock.position(); 00713 00714 // if the end of the selection is at the begining of a line, 00715 // this last line should not be taken into account 00716 if (cursor.hasSelection() && (positionInEndBlock == 0)) 00717 lineEnd--; 00718 00719 // begin of editing block 00720 cursor.beginEditBlock(); 00721 00722 // start at the begining of the selection 00723 cursor.setPosition(cursor.selectionStart()); 00724 cursor.movePosition(QTextCursor::StartOfBlock); 00725 QTextCursor cursorRestore = cursor; 00726 00727 while (cursor.block().blockNumber() <= lineEnd) 00728 { 00729 // prepare for insertion / deletion 00730 setTextCursor(cursor); 00731 00732 if (commentOperation == CommentSelection) 00733 { 00734 // insert # 00735 cursor.insertText("#"); 00736 } 00737 else if (commentOperation == UncommentSelection) 00738 { 00739 // delete # 00740 if (cursor.block().text().at(0) == QChar('#')) 00741 cursor.deleteChar(); 00742 } 00743 00744 // move to the next line 00745 if (!cursor.movePosition(QTextCursor::NextBlock)) 00746 { 00747 // end of the document 00748 moveFailed = true; 00749 break; 00750 } 00751 } 00752 00753 // select the changed lines 00754 cursorRestore.movePosition(QTextCursor::StartOfBlock); 00755 if (!moveFailed) 00756 cursor.movePosition(QTextCursor::StartOfBlock); 00757 else 00758 cursor.movePosition(QTextCursor::EndOfBlock); 00759 cursorRestore.setPosition(cursor.position(), QTextCursor::KeepAnchor); 00760 setTextCursor(cursorRestore); 00761 00762 // end of editing block 00763 cursor.endEditBlock(); 00764 } 00765 00766 void AeslEditor::keyPressEvent(QKeyEvent * event) 00767 { 00768 if (handleCompleter(event)) 00769 return; 00770 if (handleTab(event)) 00771 return; 00772 if (handleNewLine(event)) 00773 return; 00774 00775 // default handler 00776 QKeyEvent eventCopy(*event); 00777 QTextEdit::keyPressEvent(event); 00778 detectLocalContextChange(&eventCopy); 00779 doCompletion(&eventCopy); 00780 } 00781 00782 bool AeslEditor::handleCompleter(QKeyEvent *event) 00783 { 00784 // if the popup is visible, forward special keys to the completer 00785 if (completer && completer->popup()->isVisible()) { 00786 switch (event->key()) { 00787 case Qt::Key_Enter: 00788 case Qt::Key_Return: 00789 case Qt::Key_Escape: 00790 case Qt::Key_Tab: 00791 case Qt::Key_Backtab: 00792 event->ignore(); 00793 return true; // let the completer do default behavior 00794 default: 00795 break; 00796 } 00797 } 00798 00799 // not the case, go on with other handlers 00800 return false; 00801 } 00802 00803 bool AeslEditor::handleTab(QKeyEvent *event) 00804 { 00805 // handle tab and control tab 00806 if (!(event->key() == Qt::Key_Tab) || !(textCursor().hasSelection())) 00807 // I won't handle this event 00808 return false; 00809 00810 QTextCursor cursor(document()->findBlock(textCursor().selectionStart())); 00811 00812 cursor.beginEditBlock(); 00813 00814 while (cursor.position() < textCursor().selectionEnd()) 00815 { 00816 cursor.movePosition(QTextCursor::StartOfLine); 00817 if (event->modifiers() & Qt::ControlModifier) 00818 { 00819 cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); 00820 if ((cursor.selectedText() == "\t") || 00821 ( (cursor.selectedText() == " ") && 00822 (cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 3)) && 00823 (cursor.selectedText() == " ") 00824 ) 00825 ) 00826 cursor.removeSelectedText(); 00827 } 00828 else 00829 cursor.insertText("\t"); 00830 cursor.movePosition(QTextCursor::Down); 00831 cursor.movePosition(QTextCursor::EndOfLine); 00832 } 00833 00834 cursor.movePosition(QTextCursor::StartOfLine); 00835 if (event->modifiers() & Qt::ControlModifier) 00836 { 00837 cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); 00838 if ((cursor.selectedText() == "\t") || 00839 ( (cursor.selectedText() == " ") && 00840 (cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 3)) && 00841 (cursor.selectedText() == " ") 00842 ) 00843 ) 00844 cursor.removeSelectedText(); 00845 } 00846 else 00847 cursor.insertText("\t"); 00848 00849 cursor.endEditBlock(); 00850 00851 // successfully handeled 00852 return true; 00853 } 00854 00855 bool AeslEditor::handleNewLine(QKeyEvent *event) 00856 { 00857 // handle indentation and new line 00858 if (!(event->key() == Qt::Key_Return ) && !(event->key() == Qt::Key_Enter)) 00859 // I won't handle this event 00860 return false; 00861 00862 QString headingSpace("\n"); 00863 const QString &line = textCursor().block().text(); 00864 for (size_t i = 0; i < (size_t)line.length(); i++) 00865 { 00866 const QChar c(line[(unsigned)i]); 00867 if (c.isSpace()) 00868 headingSpace += c; 00869 else 00870 break; 00871 00872 } 00873 insertPlainText(headingSpace); 00874 00875 // successfully handeled 00876 return true; 00877 } 00878 00879 void AeslEditor::detectLocalContextChange(QKeyEvent *event) 00880 { 00881 // this function spy the events to detect the local context 00882 LocalContext currentContext = UnknownContext; 00883 QString previous = previousWord(); 00884 QString line = currentLine(); 00885 00886 if (previous == "call") 00887 currentContext = FunctionContext; 00888 else if (previous == "onevent" || previous == "emit") 00889 currentContext = EventContext; 00890 else if (vardefRegexp.indexIn(line) != -1) 00891 currentContext = VarDefContext; 00892 else if (leftValueRegexp.indexIn(line) == -1) 00893 currentContext = LeftValueContext; 00894 else 00895 currentContext = GeneralContext; 00896 00897 if (currentContext != previousContext) 00898 { 00899 // local context has changed 00900 previousContext = currentContext; 00901 emit refreshModelRequest(currentContext); 00902 } 00903 } 00904 00905 void AeslEditor::doCompletion(QKeyEvent *event) 00906 { 00907 static QString eow("~!@#$%^&*()+{}|:\"<>?,/;'[]\\-="); // end of word 00908 QString completionPrefix = textUnderCursor(); 00909 // qDebug() << "Completion prefix: " << completionPrefix; 00910 00911 // don't show the popup if the word is too short, or if we detect an "end of word" character 00912 if (event->text().isEmpty()|| completionPrefix.length() < 1 || eow.contains(event->text().right(1))) { 00913 completer->popup()->hide(); 00914 return; 00915 } 00916 00917 // update the completion prefix 00918 if (completionPrefix != completer->completionPrefix()) { 00919 completer->setCompletionPrefix(completionPrefix); 00920 completer->popup()->setCurrentIndex(completer->completionModel()->index(0, 0)); 00921 } 00922 00923 // qDebug() << completer->completionCount(); 00924 // if we match the only completion available, close the popup. 00925 if (completer->completionCount() == 1) 00926 if (completer->currentCompletion() == completionPrefix) 00927 { 00928 completer->popup()->hide(); 00929 return; 00930 } 00931 00932 // show the popup 00933 QRect cr = cursorRect(); 00934 cr.setWidth(completer->popup()->sizeHintForColumn(0) + completer->popup()->verticalScrollBar()->sizeHint().width()); 00935 completer->complete(cr); 00936 } 00937 00938 void AeslEditor::insertCompletion(const QString& completion) 00939 { 00940 QTextCursor tc = textCursor(); 00941 // move at the end of the word (make sure to be on it) 00942 tc.movePosition(QTextCursor::Left); 00943 tc.movePosition(QTextCursor::EndOfWord); 00944 // move the cursor left, by the number of completer's prefix characters 00945 tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, completer->completionPrefix().length()); 00946 tc.removeSelectedText(); 00947 // insert completion 00948 tc.insertText(completion); 00949 setTextCursor(tc); 00950 } 00951 00952 QString AeslEditor::textUnderCursor() const 00953 { 00954 QTextCursor tc = textCursor(); 00955 // found the end 00956 tc.movePosition(QTextCursor::EndOfWord); 00957 int endPosition = tc.position(); 00958 // found the begining, including possible "." in the word 00959 tc.movePosition(QTextCursor::StartOfWord); 00960 while( (document()->characterAt(tc.position() - 1)) == QChar('.') ) 00961 { 00962 tc.movePosition(QTextCursor::Left); 00963 tc.movePosition(QTextCursor::WordLeft); 00964 } 00965 // select 00966 tc.setPosition(endPosition, QTextCursor::KeepAnchor); 00967 return tc.selectedText(); 00968 } 00969 00970 QString AeslEditor::previousWord() const 00971 { 00972 QTextCursor tc = textCursor(); 00973 tc.movePosition(QTextCursor::WordLeft); 00974 tc.movePosition(QTextCursor::PreviousWord); 00975 tc.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); 00976 return tc.selectedText(); 00977 } 00978 00979 QString AeslEditor::currentLine() const 00980 { 00981 // return everything between the start and the cursor 00982 QTextCursor tc = textCursor(); 00983 tc.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); 00984 return tc.selectedText(); 00985 } 00986 00987 void AeslEditor::setCompleterModel(QAbstractItemModel *model) 00988 { 00989 completer->setModel(model); 00990 completer->setCompletionRole(Qt::DisplayRole); 00991 } 00992 00993 00995 }; // Aseba