AeslEditor.cpp
Go to the documentation of this file.
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


aseba
Author(s): Stéphane Magnenat
autogenerated on Sun Oct 5 2014 23:46:38