TextEdit.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002  *   Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net>     *
00003  *                                                                         *
00004  *   This file is part of the FreeCAD CAx development system.              *
00005  *                                                                         *
00006  *   This library is free software; you can redistribute it and/or         *
00007  *   modify it under the terms of the GNU Library General Public           *
00008  *   License as published by the Free Software Foundation; either          *
00009  *   version 2 of the License, or (at your option) any later version.      *
00010  *                                                                         *
00011  *   This library  is distributed in the hope that it will be useful,      *
00012  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00013  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00014  *   GNU Library General Public License for more details.                  *
00015  *                                                                         *
00016  *   You should have received a copy of the GNU Library General Public     *
00017  *   License along with this library; see the file COPYING.LIB. If not,    *
00018  *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
00019  *   Suite 330, Boston, MA  02111-1307, USA                                *
00020  *                                                                         *
00021  ***************************************************************************/
00022 
00023 
00024 #include "PreCompiled.h"
00025 
00026 #ifndef _PreComp_
00027 # include <QKeyEvent>
00028 # include <QPainter>
00029 # include <QShortcut>
00030 # include <QTextCursor>
00031 #endif
00032 
00033 #include "TextEdit.h"
00034 #include "SyntaxHighlighter.h"
00035 
00036 using namespace Gui;
00037 
00041 TextEdit::TextEdit(QWidget* parent)
00042     : QPlainTextEdit(parent), listBox(0)
00043 {
00044     //Note: Set the correct context to this shortcut as we may use several instances of this
00045     //class at a time
00046     QShortcut* shortcut = new QShortcut(this);
00047     shortcut->setKey(Qt::CTRL+Qt::Key_Space);
00048     shortcut->setContext(Qt::WidgetShortcut);
00049     connect(shortcut, SIGNAL(activated()), this, SLOT(complete()));
00050 }
00051 
00053 TextEdit::~TextEdit()
00054 {
00055 }
00056 
00060 void TextEdit::keyPressEvent(QKeyEvent* e)
00061 {
00062     QPlainTextEdit::keyPressEvent(e);
00063     // This can't be done in CompletionList::eventFilter() because we must first perform
00064     // the event and afterwards update the list widget
00065     if (listBox && listBox->isVisible()) {
00066         // Get the word under the cursor
00067         QTextCursor cursor = textCursor();
00068         cursor.movePosition(QTextCursor::StartOfWord);
00069         // the cursor has moved to outside the word prefix
00070         if (cursor.position() < cursorPosition-wordPrefix.length() || 
00071             cursor.position() > cursorPosition) {
00072             listBox->hide();
00073             return;
00074         }
00075         cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
00076         listBox->keyboardSearch(cursor.selectedText());
00077         cursor.clearSelection();
00078     }
00079 }
00080 
00084 void TextEdit::complete()
00085 {
00086     QTextBlock block = textCursor().block();
00087     if (!block.isValid())
00088         return;
00089     int cursorPos = textCursor().position()-block.position();
00090     QString para = block.text();
00091     int wordStart = cursorPos;
00092     while (wordStart > 0 && para[wordStart - 1].isLetterOrNumber())
00093         --wordStart;
00094     wordPrefix = para.mid(wordStart, cursorPos - wordStart);
00095     if (wordPrefix.isEmpty())
00096         return;
00097     
00098     QStringList list = toPlainText().split(QRegExp(QLatin1String("\\W+")));
00099     QMap<QString, QString> map;
00100     QStringList::Iterator it = list.begin();
00101     while (it != list.end()) {
00102         if ((*it).startsWith(wordPrefix) && (*it).length() > wordPrefix.length())
00103             map[(*it).toLower()] = *it;
00104         ++it;
00105     }
00106     
00107     if (map.count() == 1) {
00108         insertPlainText((*map.begin()).mid(wordPrefix.length()));
00109     } else if (map.count() > 1) {
00110         if (!listBox)
00111             createListBox();
00112         listBox->clear();
00113         listBox->addItems(map.values());
00114         listBox->setFont(QFont(font().family(), 8));
00115 
00116         this->cursorPosition = textCursor().position();
00117 
00118         // get the minimum width and height of the box
00119         int h = 0;
00120         int w = 0;
00121         for (int i = 0; i < listBox->count(); ++i) {
00122             QRect r = listBox->visualItemRect(listBox->item(i));
00123             w = qMax(w, r.width());
00124             h += r.height();
00125         }
00126 
00127         // Add an offset
00128         w += 2*listBox->frameWidth();
00129         h += 2*listBox->frameWidth();
00130 
00131         // get the start position of the word prefix
00132         QTextCursor cursor = textCursor();
00133         cursor.movePosition(QTextCursor::StartOfWord);
00134         QRect rect = cursorRect(cursor);
00135         int posX = rect.x();
00136         int posY = rect.y();
00137         int boxH = h;
00138 
00139         // Decide whether to show downstairs or upstairs
00140         if (posY > viewport()->height()/2) {
00141             h = qMin(qMin(h,posY), 250);
00142             if (h < boxH)
00143                 w += style()->pixelMetric(QStyle::PM_ScrollBarExtent);
00144             listBox->setGeometry(posX,posY-h, w, h);
00145         } else {
00146             h = qMin(qMin(h,viewport()->height()-fontMetrics().height()-posY), 250);
00147             if (h < boxH)
00148                 w += style()->pixelMetric(QStyle::PM_ScrollBarExtent);
00149             listBox->setGeometry(posX, posY+fontMetrics().height(), w, h);
00150         }
00151         
00152         listBox->setCurrentRow(0);
00153         listBox->show();
00154     }
00155 }
00156 
00162 void TextEdit::createListBox()
00163 {
00164     listBox = new CompletionList(this);
00165     listBox->setFrameStyle(QFrame::Box|QFrame::Raised);
00166     listBox->setLineWidth(2);
00167     installEventFilter(listBox);
00168     viewport()->installEventFilter(listBox);
00169     listBox->setSelectionMode( QAbstractItemView::SingleSelection );
00170     listBox->hide();
00171 }
00172 
00173 // ------------------------------------------------------------------------------
00174 
00175 namespace Gui {
00176 struct TextEditorP
00177 {
00178     QMap<QString, QColor> colormap; // Color map
00179     TextEditorP()
00180     {
00181         colormap[QLatin1String("Text")] = Qt::black;
00182         colormap[QLatin1String("Bookmark")] = Qt::cyan;
00183         colormap[QLatin1String("Breakpoint")] = Qt::red;
00184         colormap[QLatin1String("Keyword")] = Qt::blue;
00185         colormap[QLatin1String("Comment")] = QColor(0, 170, 0);
00186         colormap[QLatin1String("Block comment")] = QColor(160, 160, 164);
00187         colormap[QLatin1String("Number")] = Qt::blue;
00188         colormap[QLatin1String("String")] = Qt::red;
00189         colormap[QLatin1String("Character")] = Qt::red;
00190         colormap[QLatin1String("Class name")] = QColor(255, 170, 0);
00191         colormap[QLatin1String("Define name")] = QColor(255, 170, 0);
00192         colormap[QLatin1String("Operator")] = QColor(160, 160, 164);
00193         colormap[QLatin1String("Python output")] = QColor(170, 170, 127);
00194         colormap[QLatin1String("Python error")] = Qt::red;
00195         colormap[QLatin1String("Line")] = QColor(224,224,224);
00196     }
00197 };
00198 } // namespace Gui
00199 
00204 TextEditor::TextEditor(QWidget* parent)
00205   : TextEdit(parent), WindowParameter("Editor"), highlighter(0)
00206 {
00207     d = new TextEditorP();
00208     lineNumberArea = new LineMarker(this);
00209 
00210     QFont serifFont(QLatin1String("Courier"), 10, QFont::Normal);
00211     setFont(serifFont);
00212 
00213     ParameterGrp::handle hPrefGrp = getWindowParameter();
00214     // set default to 4 characters
00215     hPrefGrp->SetInt( "TabSize", 4 );
00216     hPrefGrp->Attach( this );
00217 
00218     // set colors and font
00219     hPrefGrp->NotifyAll();
00220 
00221     connect(this, SIGNAL(cursorPositionChanged()),
00222             this, SLOT(highlightCurrentLine()));
00223     connect(this, SIGNAL(blockCountChanged(int)),
00224             this, SLOT(updateLineNumberAreaWidth(int)));
00225     connect(this, SIGNAL(updateRequest(const QRect &, int)),
00226             this, SLOT(updateLineNumberArea(const QRect &, int)));
00227 
00228     updateLineNumberAreaWidth(0);
00229     highlightCurrentLine();
00230 }
00231 
00233 TextEditor::~TextEditor()
00234 {
00235     getWindowParameter()->Detach(this);
00236     delete highlighter;
00237     delete d;
00238 }
00239 
00240 int TextEditor::lineNumberAreaWidth()
00241 {
00242     return fontMetrics().width(QLatin1String("0000"))+10;
00243 }
00244 
00245 void TextEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
00246 {
00247     setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
00248 }
00249 
00250 void TextEditor::updateLineNumberArea(const QRect &rect, int dy)
00251 {
00252     if (dy)
00253         lineNumberArea->scroll(0, dy);
00254     else
00255         lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
00256 
00257     if (rect.contains(viewport()->rect()))
00258         updateLineNumberAreaWidth(0);
00259 }
00260 
00261 void TextEditor::resizeEvent(QResizeEvent *e)
00262 {
00263     QPlainTextEdit::resizeEvent(e);
00264 
00265     QRect cr = contentsRect();
00266     lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
00267 }
00268 
00269 void TextEditor::highlightCurrentLine()
00270 {
00271     QList<QTextEdit::ExtraSelection> extraSelections;
00272 
00273     if (!isReadOnly()) {
00274         QTextEdit::ExtraSelection selection;
00275         QColor lineColor = d->colormap[QLatin1String("Line")];
00276 
00277         selection.format.setBackground(lineColor);
00278         selection.format.setProperty(QTextFormat::FullWidthSelection, true);
00279         selection.cursor = textCursor();
00280         selection.cursor.clearSelection();
00281         extraSelections.append(selection);
00282     }
00283 
00284     setExtraSelections(extraSelections);
00285 }
00286 
00287 void TextEditor::drawMarker(int line, int x, int y, QPainter* p)
00288 {
00289 }
00290 
00291 void TextEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
00292 {
00293     QPainter painter(lineNumberArea);
00294     //painter.fillRect(event->rect(), Qt::lightGray);
00295 
00296     QTextBlock block = firstVisibleBlock();
00297     int blockNumber = block.blockNumber();
00298     int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
00299     int bottom = top + (int) blockBoundingRect(block).height();
00300 
00301     while (block.isValid() && top <= event->rect().bottom()) {
00302         if (block.isVisible() && bottom >= event->rect().top()) {
00303             QString number = QString::number(blockNumber + 1);
00304             painter.setPen(Qt::black);
00305             painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
00306                              Qt::AlignRight, number);
00307             drawMarker(blockNumber + 1, 1, top, &painter);
00308         }
00309 
00310         block = block.next();
00311         top = bottom;
00312         bottom = top + (int) blockBoundingRect(block).height();
00313         ++blockNumber;
00314     }
00315 }
00316 
00317 void TextEditor::setSyntaxHighlighter(SyntaxHighlighter* sh)
00318 {
00319     sh->setDocument(this->document());
00320     this->highlighter = sh;
00321 }
00322 
00323 void TextEditor::keyPressEvent (QKeyEvent * e)
00324 {
00325     if ( e->key() == Qt::Key_Tab ) {
00326         ParameterGrp::handle hPrefGrp = getWindowParameter();
00327         int indent = hPrefGrp->GetInt( "IndentSize", 4 );
00328         bool space = hPrefGrp->GetBool( "Spaces", false );
00329         QString ch = space ? QString(indent, QLatin1Char(' '))
00330                            : QString::fromAscii("\t");
00331 
00332         QTextCursor cursor = textCursor();
00333         if (!cursor.hasSelection()) {
00334             // insert a single tab or several spaces
00335             cursor.beginEditBlock();
00336             cursor.insertText(ch);
00337             cursor.endEditBlock();
00338         } else {
00339             // for each selected block insert a tab or spaces
00340             int selStart = cursor.selectionStart();
00341             int selEnd = cursor.selectionEnd();
00342             QTextBlock block;
00343             cursor.beginEditBlock();
00344             for (block = document()->begin(); block.isValid(); block = block.next()) {
00345                 int pos = block.position();
00346                 int off = block.length()-1;
00347                 // at least one char of the block is part of the selection
00348                 if ( pos >= selStart || pos+off >= selStart) {
00349                     if ( pos+1 > selEnd )
00350                         break; // end of selection reached
00351                     cursor.setPosition(block.position());
00352                     cursor.insertText(ch);
00353                         selEnd += ch.length();
00354                 }
00355             }
00356 
00357             cursor.endEditBlock();
00358         }
00359 
00360         return;
00361     }
00362     else if (e->key() == Qt::Key_Backtab) {
00363         QTextCursor cursor = textCursor();
00364         if (!cursor.hasSelection())
00365             return; // Shift+Tab should not do anything
00366         // If some text is selected we remove a leading tab or
00367         // spaces from each selected block
00368         ParameterGrp::handle hPrefGrp = getWindowParameter();
00369         int indent = hPrefGrp->GetInt( "IndentSize", 4 );
00370 
00371         int selStart = cursor.selectionStart();
00372         int selEnd = cursor.selectionEnd();
00373         QTextBlock block;
00374         cursor.beginEditBlock();
00375         for (block = document()->begin(); block.isValid(); block = block.next()) {
00376             int pos = block.position();
00377             int off = block.length()-1;
00378             // at least one char of the block is part of the selection
00379             if ( pos >= selStart || pos+off >= selStart) {
00380                 if ( pos+1 > selEnd )
00381                     break; // end of selection reached
00382                 // if possible remove one tab or several spaces
00383                 QString text = block.text();
00384                 if (text.startsWith(QLatin1String("\t"))) {
00385                     cursor.setPosition(block.position());
00386                     cursor.deleteChar();
00387                     selEnd--;
00388                 }
00389                 else {
00390                     cursor.setPosition(block.position());
00391                     for (int i=0; i<indent; i++) {
00392                         if (!text.startsWith(QLatin1String(" ")))
00393                             break;
00394                         text = text.mid(1);
00395                         cursor.deleteChar();
00396                         selEnd--;
00397                     }
00398                 }
00399             }
00400         }
00401 
00402         cursor.endEditBlock();
00403         return;
00404     }
00405 
00406     TextEdit::keyPressEvent( e );
00407 }
00408 
00410 void TextEditor::OnChange(Base::Subject<const char*> &rCaller,const char* sReason)
00411 {
00412     ParameterGrp::handle hPrefGrp = getWindowParameter();
00413     if (strcmp(sReason, "FontSize") == 0 || strcmp(sReason, "Font") == 0) {
00414 #ifdef FC_OS_LINUX
00415         int fontSize = hPrefGrp->GetInt("FontSize", 15);
00416 #else
00417         int fontSize = hPrefGrp->GetInt("FontSize", 10);
00418 #endif
00419         QString fontFamily = QString::fromAscii(hPrefGrp->GetASCII( "Font", "Courier" ).c_str());
00420         
00421         QFont font(fontFamily, fontSize);
00422         setFont(font);
00423     } else {
00424         QMap<QString, QColor>::ConstIterator it = d->colormap.find(QString::fromAscii(sReason));
00425         if (it != d->colormap.end()) {
00426             QColor color = it.value();
00427             unsigned long col = (color.red() << 24) | (color.green() << 16) | (color.blue() << 8);
00428             col = hPrefGrp->GetUnsigned( sReason, col);
00429             color.setRgb((col>>24)&0xff, (col>>16)&0xff, (col>>8)&0xff);
00430             if (this->highlighter)
00431                 this->highlighter->setColor(QLatin1String(sReason), color);
00432         }
00433     }
00434 
00435     if (strcmp(sReason, "TabSize") == 0 || strcmp(sReason, "FontSize") == 0) {
00436         int tabWidth = hPrefGrp->GetInt("TabSize", 4);
00437         QFontMetrics metric(font());
00438         int fontSize = metric.width(QLatin1String("0"));
00439         setTabStopWidth(tabWidth * fontSize);
00440     }
00441 }
00442 
00443 void TextEditor::paintEvent (QPaintEvent * e)
00444 {
00445     TextEdit::paintEvent( e );
00446 }
00447 
00448 // ------------------------------------------------------------------------------
00449 
00450 LineMarker::LineMarker(TextEditor* editor)
00451     : QWidget(editor), textEditor(editor)
00452 {
00453 }
00454 
00455 LineMarker::~LineMarker()
00456 {
00457 }
00458 
00459 QSize LineMarker::sizeHint() const
00460 {
00461     return QSize(textEditor->lineNumberAreaWidth(), 0);
00462 }
00463 
00464 void LineMarker::paintEvent(QPaintEvent* e)
00465 {
00466     textEditor->lineNumberAreaPaintEvent(e);
00467 }
00468 
00469 // ------------------------------------------------------------------------------
00470 
00471 CompletionList::CompletionList(QPlainTextEdit* parent)
00472   :  QListWidget(parent), textEdit(parent)
00473 {
00474     // make the user assume that the widget is active
00475     QPalette pal = parent->palette();
00476     pal.setColor(QPalette::Inactive, QPalette::Highlight, pal.color(QPalette::Active, QPalette::Highlight));
00477     pal.setColor(QPalette::Inactive, QPalette::HighlightedText, pal.color(QPalette::Active, QPalette::HighlightedText));
00478     parent->setPalette( pal );
00479 
00480     connect(this, SIGNAL(itemActivated(QListWidgetItem *)), 
00481             this, SLOT(completionItem(QListWidgetItem *)));
00482 }
00483 
00484 CompletionList::~CompletionList()
00485 {
00486 }
00487 
00488 void CompletionList::findCurrentWord(const QString& wordPrefix)
00489 { 
00490     for (int i=0; i<count(); ++i) {
00491         QString text = item(i)->text();
00492         if (text.startsWith(wordPrefix)) {
00493             setCurrentRow(i);
00494             return;
00495         }
00496     }
00497 
00498     setItemSelected(currentItem(), false);
00499 }
00500 
00505 bool CompletionList::eventFilter(QObject * watched, QEvent * event)
00506 {
00507     if (isVisible() && watched == textEdit->viewport()) {
00508         if (event->type() == QEvent::MouseButtonPress)
00509             hide();
00510     } else if (isVisible() && watched == textEdit) {
00511         if (event->type() == QEvent::KeyPress) {
00512             QKeyEvent* ke = (QKeyEvent*)event;
00513             if (ke->key() == Qt::Key_Up || ke->key() == Qt::Key_Down) {
00514                 keyPressEvent(ke);
00515                 return true;
00516             } else if (ke->key() == Qt::Key_PageUp || ke->key() == Qt::Key_PageDown) {
00517                 keyPressEvent(ke);
00518                 return true;
00519             } else if (ke->key() == Qt::Key_Escape) {
00520                 hide();
00521                 return true;
00522             } else if (ke->key() == Qt::Key_Space) {
00523                 hide();
00524                 return false;
00525             } else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
00526                 itemActivated(currentItem());
00527                 return true;
00528             }
00529         } else if (event->type() == QEvent::FocusOut) {
00530             if (!hasFocus())
00531                 hide();
00532         }
00533     }
00534 
00535     return QListWidget::eventFilter(watched, event);
00536 }
00537 
00542 void CompletionList::completionItem(QListWidgetItem *item)
00543 {
00544     hide();
00545     QString text = item->text();
00546     QTextCursor cursor = textEdit->textCursor();
00547     cursor.movePosition(QTextCursor::StartOfWord);
00548     cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
00549     cursor.insertText( text );
00550     textEdit->ensureCursorVisible();
00551 }
00552 
00553 #include "moc_TextEdit.cpp" 

Generated on Wed Nov 23 19:00:45 2011 for FreeCAD by  doxygen 1.6.1