~ubuntu-branches/ubuntu/maverick/freecad/maverick

« back to all changes in this revision

Viewing changes to src/Gui/TextEdit.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Teemu Ikonen
  • Date: 2009-07-16 18:37:41 UTC
  • Revision ID: james.westby@ubuntu.com-20090716183741-oww9kcxqrk991i1n
Tags: upstream-0.8.2237
ImportĀ upstreamĀ versionĀ 0.8.2237

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/***************************************************************************
 
2
 *   Copyright (c) 2004 Werner Mayer <werner.wm.mayer@gmx.de>              *
 
3
 *                                                                         *
 
4
 *   This file is part of the FreeCAD CAx development system.              *
 
5
 *                                                                         *
 
6
 *   This library is free software; you can redistribute it and/or         *
 
7
 *   modify it under the terms of the GNU Library General Public           *
 
8
 *   License as published by the Free Software Foundation; either          *
 
9
 *   version 2 of the License, or (at your option) any later version.      *
 
10
 *                                                                         *
 
11
 *   This library  is distributed in the hope that it will be useful,      *
 
12
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 
13
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 
14
 *   GNU Library General Public License for more details.                  *
 
15
 *                                                                         *
 
16
 *   You should have received a copy of the GNU Library General Public     *
 
17
 *   License along with this library; see the file COPYING.LIB. If not,    *
 
18
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 
19
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 
20
 *                                                                         *
 
21
 ***************************************************************************/
 
22
 
 
23
 
 
24
#include "PreCompiled.h"
 
25
 
 
26
#ifndef _PreComp_
 
27
#endif
 
28
 
 
29
#include "TextEdit.h"
 
30
#include "SyntaxHighlighter.h"
 
31
 
 
32
using namespace Gui;
 
33
 
 
34
/**
 
35
 *  Constructs a TextEdit which is a child of 'parent'.
 
36
 */
 
37
TextEdit::TextEdit(QWidget* parent)
 
38
    : QTextEdit(parent), listBox(0)
 
39
{
 
40
    //Note: Set the correct context to this shortcut as we may use several instances of this
 
41
    //class at a time
 
42
    QShortcut* shortcut = new QShortcut(this);
 
43
    shortcut->setKey(Qt::CTRL+Qt::Key_Space);
 
44
    shortcut->setContext(Qt::WidgetShortcut);
 
45
    connect(shortcut, SIGNAL(activated()), this, SLOT(complete()));
 
46
}
 
47
 
 
48
/** Destroys the object and frees any allocated resources */
 
49
TextEdit::~TextEdit()
 
50
{
 
51
}
 
52
 
 
53
/**
 
54
 * Set the approproriate item of the completion box or hide it, if needed.
 
55
 */
 
56
void TextEdit::keyPressEvent(QKeyEvent* e)
 
57
{
 
58
    QTextEdit::keyPressEvent(e);
 
59
    // This can't be done in CompletionList::eventFilter() because we must first perform
 
60
    // the event and afterwards update the list widget
 
61
    if (listBox && listBox->isVisible()) {
 
62
        // Get the word under the cursor
 
63
        QTextCursor cursor = textCursor();
 
64
        cursor.movePosition(QTextCursor::StartOfWord);
 
65
        // the cursor has moved to outside the word prefix
 
66
        if (cursor.position() < cursorPosition-wordPrefix.length() || 
 
67
            cursor.position() > cursorPosition) {
 
68
            listBox->hide();
 
69
            return;
 
70
        }
 
71
        cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
 
72
        listBox->keyboardSearch(cursor.selectedText());
 
73
        cursor.clearSelection();
 
74
    }
 
75
}
 
76
 
 
77
/**
 
78
 * Completes the word.
 
79
 */
 
80
void TextEdit::complete()
 
81
{
 
82
    QTextBlock block = textCursor().block();
 
83
    if (!block.isValid())
 
84
        return;
 
85
    int cursorPos = textCursor().position()-block.position();
 
86
    QString para = block.text();
 
87
    int wordStart = cursorPos;
 
88
    while (wordStart > 0 && para[wordStart - 1].isLetterOrNumber())
 
89
        --wordStart;
 
90
    wordPrefix = para.mid(wordStart, cursorPos - wordStart);
 
91
    if (wordPrefix.isEmpty())
 
92
        return;
 
93
    
 
94
    QStringList list = toPlainText().split(QRegExp(QLatin1String("\\W+")));
 
95
    QMap<QString, QString> map;
 
96
    QStringList::Iterator it = list.begin();
 
97
    while (it != list.end()) {
 
98
        if ((*it).startsWith(wordPrefix) && (*it).length() > wordPrefix.length())
 
99
            map[(*it).toLower()] = *it;
 
100
        ++it;
 
101
    }
 
102
    
 
103
    if (map.count() == 1) {
 
104
        insertPlainText((*map.begin()).mid(wordPrefix.length()));
 
105
    } else if (map.count() > 1) {
 
106
        if (!listBox)
 
107
            createListBox();
 
108
        listBox->clear();
 
109
        listBox->addItems(map.values());
 
110
        listBox->setFont(QFont(font().family(), 8));
 
111
 
 
112
        this->cursorPosition = textCursor().position();
 
113
 
 
114
        // get the minimum width and height of the box
 
115
        int h = 0;
 
116
        int w = 0;
 
117
        for (int i = 0; i < listBox->count(); ++i) {
 
118
            QRect r = listBox->visualItemRect(listBox->item(i));
 
119
            w = qMax(w, r.width());
 
120
            h += r.height();
 
121
        }
 
122
 
 
123
        // Add an offset
 
124
        w += 2*listBox->frameWidth();
 
125
        h += 2*listBox->frameWidth();
 
126
 
 
127
        // get the start position of the word prefix
 
128
        QTextCursor cursor = textCursor();
 
129
        cursor.movePosition(QTextCursor::StartOfWord);
 
130
        QRect rect = cursorRect(cursor);
 
131
        int posX = rect.x();
 
132
        int posY = rect.y();
 
133
        int boxH = h;
 
134
 
 
135
        // Decide whether to show downstairs or upstairs
 
136
        if (posY > viewport()->height()/2) {
 
137
            h = qMin(qMin(h,posY), 250);
 
138
            if (h < boxH)
 
139
                w += style()->pixelMetric(QStyle::PM_ScrollBarExtent);
 
140
            listBox->setGeometry(posX,posY-h, w, h);
 
141
        } else {
 
142
            h = qMin(qMin(h,viewport()->height()-fontMetrics().height()-posY), 250);
 
143
            if (h < boxH)
 
144
                w += style()->pixelMetric(QStyle::PM_ScrollBarExtent);
 
145
            listBox->setGeometry(posX, posY+fontMetrics().height(), w, h);
 
146
        }
 
147
        
 
148
        listBox->setCurrentRow(0);
 
149
        listBox->show();
 
150
    }
 
151
}
 
152
 
 
153
/**
 
154
 * Creates the listbox containing all possibilities for the completion.
 
155
 * The listbox is closed when ESC is pressed, the text edit field looses focus or a
 
156
 * mouse button was pressed.
 
157
 */
 
158
void TextEdit::createListBox()
 
159
{
 
160
    listBox = new CompletionList(this);
 
161
    listBox->setFrameStyle(QFrame::Box|QFrame::Raised);
 
162
    listBox->setLineWidth(2);
 
163
    installEventFilter(listBox);
 
164
    viewport()->installEventFilter(listBox);
 
165
    listBox->setSelectionMode( QAbstractItemView::SingleSelection );
 
166
    listBox->hide();
 
167
}
 
168
 
 
169
// ------------------------------------------------------------------------------
 
170
 
 
171
namespace Gui {
 
172
struct TextEditorP
 
173
{
 
174
    QMap<QString, QColor> colormap; // Color map
 
175
    TextEditorP()
 
176
    {
 
177
        colormap[QLatin1String("Text")] = Qt::black;
 
178
        colormap[QLatin1String("Bookmark")] = Qt::cyan;
 
179
        colormap[QLatin1String("Breakpoint")] = Qt::red;
 
180
        colormap[QLatin1String("Keyword")] = Qt::blue;
 
181
        colormap[QLatin1String("Comment")] = QColor(0, 170, 0);
 
182
        colormap[QLatin1String("Block comment")] = QColor(160, 160, 164);
 
183
        colormap[QLatin1String("Number")] = Qt::blue;
 
184
        colormap[QLatin1String("String")] = Qt::red;
 
185
        colormap[QLatin1String("Character")] = Qt::red;
 
186
        colormap[QLatin1String("Class name")] = QColor(255, 170, 0);
 
187
        colormap[QLatin1String("Define name")] = QColor(255, 170, 0);
 
188
        colormap[QLatin1String("Operator")] = QColor(160, 160, 164);
 
189
        colormap[QLatin1String("Python output")] = QColor(170, 170, 127);
 
190
        colormap[QLatin1String("Python error")] = Qt::red;
 
191
        colormap[QLatin1String("Line")] = QColor(224,224,224);
 
192
    }
 
193
};
 
194
} // namespace Gui
 
195
 
 
196
/**
 
197
 *  Constructs a TextEditor which is a child of 'parent' and does the
 
198
 *  syntax highlighting for the Python language. 
 
199
 */
 
200
TextEditor::TextEditor(QWidget* parent)
 
201
  : TextEdit(parent), WindowParameter("Editor"), highlighter(0)
 
202
{
 
203
    d = new TextEditorP();
 
204
 
 
205
#ifdef FC_OS_LINUX
 
206
    QFont serifFont(QLatin1String("Courier"), 15, QFont::Normal );
 
207
#else
 
208
    QFont serifFont(QLatin1String("Courier"), 10, QFont::Normal );
 
209
#endif
 
210
    setFont(serifFont);
 
211
 
 
212
    ParameterGrp::handle hPrefGrp = getWindowParameter();
 
213
    // set default to 4 characters
 
214
    hPrefGrp->SetInt( "TabSize", 4 );
 
215
    hPrefGrp->Attach( this );
 
216
 
 
217
    // set colors and font
 
218
    hPrefGrp->NotifyAll();
 
219
    connect(this, SIGNAL(cursorPositionChanged()), 
 
220
            this, SLOT(onCursorPositionChanged()));
 
221
}
 
222
 
 
223
/** Destroys the object and frees any allocated resources */
 
224
TextEditor::~TextEditor()
 
225
{
 
226
    getWindowParameter()->Detach(this);
 
227
    delete highlighter;
 
228
    delete d;
 
229
}
 
230
 
 
231
void TextEditor::keyPressEvent (QKeyEvent * e)
 
232
{
 
233
    if ( e->key() == Qt::Key_Tab ) {
 
234
        ParameterGrp::handle hPrefGrp = getWindowParameter();
 
235
        int indent = hPrefGrp->GetInt( "IndentSize", 4 );
 
236
        bool space = hPrefGrp->GetBool( "Spaces", false );
 
237
        QString ch = space ? QString(indent, QLatin1Char(' '))
 
238
                           : QString::fromAscii("\t");
 
239
 
 
240
        QTextCursor cursor = textCursor();
 
241
        if (!cursor.hasSelection()) {
 
242
            // insert a single tab or several spaces
 
243
            cursor.beginEditBlock();
 
244
            cursor.insertText(ch);
 
245
            cursor.endEditBlock();
 
246
        } else {
 
247
            // for each selected block insert a tab or spaces
 
248
            int selStart = cursor.selectionStart();
 
249
            int selEnd = cursor.selectionEnd();
 
250
            QTextBlock block;
 
251
            cursor.beginEditBlock();
 
252
            for (block = document()->begin(); block.isValid(); block = block.next()) {
 
253
                int pos = block.position();
 
254
                int off = block.length()-1;
 
255
                // at least one char of the block is part of the selection
 
256
                if ( pos >= selStart || pos+off >= selStart) {
 
257
                    if ( pos+1 > selEnd )
 
258
                        break; // end of selection reached
 
259
                    cursor.setPosition(block.position());
 
260
                    cursor.insertText(ch);
 
261
                        selEnd += ch.length();
 
262
                }
 
263
            }
 
264
 
 
265
            cursor.endEditBlock();
 
266
        }
 
267
 
 
268
        return;
 
269
    } else if (e->key() == Qt::Key_Backtab) {
 
270
        QTextCursor cursor = textCursor();
 
271
        if (!cursor.hasSelection())
 
272
            return; // Shift+Tab should not do anything
 
273
        // If some text is selected we remove a leading tab or
 
274
        // spaces from each selected block
 
275
        ParameterGrp::handle hPrefGrp = getWindowParameter();
 
276
        int indent = hPrefGrp->GetInt( "IndentSize", 4 );
 
277
 
 
278
        int selStart = cursor.selectionStart();
 
279
        int selEnd = cursor.selectionEnd();
 
280
        QTextBlock block;
 
281
        cursor.beginEditBlock();
 
282
        for (block = document()->begin(); block.isValid(); block = block.next()) {
 
283
            int pos = block.position();
 
284
            int off = block.length()-1;
 
285
            // at least one char of the block is part of the selection
 
286
            if ( pos >= selStart || pos+off >= selStart) {
 
287
                if ( pos+1 > selEnd )
 
288
                    break; // end of selection reached
 
289
                // if possible remove one tab or several spaces
 
290
                QString text = block.text();
 
291
                if (text.startsWith(QLatin1String("\t"))) {
 
292
                    cursor.setPosition(block.position());
 
293
                    cursor.deleteChar();
 
294
                    selEnd--;
 
295
                } else {
 
296
                    cursor.setPosition(block.position());
 
297
                    for (int i=0; i<indent; i++) {
 
298
                        if (!text.startsWith(QLatin1String(" ")))
 
299
                            break;
 
300
                        text = text.mid(1);
 
301
                        cursor.deleteChar();
 
302
                        selEnd--;
 
303
                    }
 
304
                }
 
305
            }
 
306
        }
 
307
 
 
308
        cursor.endEditBlock();
 
309
        return;
 
310
    }
 
311
 
 
312
    TextEdit::keyPressEvent( e );
 
313
}
 
314
 
 
315
/** Sets the font, font size and tab size of the editor. */  
 
316
void TextEditor::OnChange(Base::Subject<const char*> &rCaller,const char* sReason)
 
317
{
 
318
    ParameterGrp::handle hPrefGrp = getWindowParameter();
 
319
    if (strcmp(sReason, "FontSize") == 0 || strcmp(sReason, "Font") == 0) {
 
320
#ifdef FC_OS_LINUX
 
321
        int fontSize = hPrefGrp->GetInt("FontSize", 15);
 
322
#else
 
323
        int fontSize = hPrefGrp->GetInt("FontSize", 10);
 
324
#endif
 
325
        QString fontFamily = QString::fromAscii(hPrefGrp->GetASCII( "Font", "Courier" ).c_str());
 
326
        
 
327
        QFont font(fontFamily, fontSize);
 
328
        setFont(font);
 
329
    } else {
 
330
        QMap<QString, QColor>::ConstIterator it = d->colormap.find(QString::fromAscii(sReason));
 
331
        if (it != d->colormap.end()) {
 
332
            QColor color = it.value();
 
333
            unsigned long col = (color.red() << 24) | (color.green() << 16) | (color.blue() << 8);
 
334
            col = hPrefGrp->GetUnsigned( sReason, col);
 
335
            color.setRgb((col>>24)&0xff, (col>>16)&0xff, (col>>8)&0xff);
 
336
            if (this->highlighter)
 
337
                this->highlighter->setColor(QLatin1String(sReason), color);
 
338
        }
 
339
    }
 
340
 
 
341
    if (strcmp(sReason, "TabSize") == 0 || strcmp(sReason, "FontSize") == 0) {
 
342
        int tabWidth = hPrefGrp->GetInt("TabSize", 4);
 
343
        QFontMetrics metric(font());
 
344
        int fontSize = metric.width(QLatin1String("0"));
 
345
        setTabStopWidth(tabWidth * fontSize);
 
346
    }
 
347
}
 
348
 
 
349
void TextEditor::onCursorPositionChanged()
 
350
{
 
351
    const QColor& color = d->colormap[QLatin1String("Line")];
 
352
    if ( color.isValid() )
 
353
        viewport()->update();
 
354
}
 
355
 
 
356
void TextEditor::paintEvent (QPaintEvent * e)
 
357
{
 
358
    const QColor& color = d->colormap[QLatin1String("Line")];
 
359
    if ( color.isValid() )
 
360
    {
 
361
        QPainter painter( viewport() );
 
362
        QRect r = cursorRect();
 
363
        r.setX( 0 );
 
364
        r.setWidth( viewport()->width() );
 
365
        painter.fillRect( r, QBrush( color ) );
 
366
        painter.end();
 
367
    }
 
368
 
 
369
    TextEdit::paintEvent( e );
 
370
}
 
371
 
 
372
// ------------------------------------------------------------------------------
 
373
 
 
374
CompletionList::CompletionList(QTextEdit* parent)
 
375
  :  QListWidget(parent), textEdit(parent)
 
376
{
 
377
    // make the user assume that the widget is active
 
378
    QPalette pal = parent->palette();
 
379
    pal.setColor(QPalette::Inactive, QPalette::Highlight, pal.color(QPalette::Active, QPalette::Highlight));
 
380
    pal.setColor(QPalette::Inactive, QPalette::HighlightedText, pal.color(QPalette::Active, QPalette::HighlightedText));
 
381
    parent->setPalette( pal );
 
382
 
 
383
    connect(this, SIGNAL(itemActivated(QListWidgetItem *)), 
 
384
            this, SLOT(completionItem(QListWidgetItem *)));
 
385
}
 
386
 
 
387
CompletionList::~CompletionList()
 
388
{
 
389
}
 
390
 
 
391
void CompletionList::findCurrentWord(const QString& wordPrefix)
 
392
 
393
    for (int i=0; i<count(); ++i) {
 
394
        QString text = item(i)->text();
 
395
        if (text.startsWith(wordPrefix)) {
 
396
            setCurrentRow(i);
 
397
            return;
 
398
        }
 
399
    }
 
400
 
 
401
    setItemSelected(currentItem(), false);
 
402
}
 
403
 
 
404
/** 
 
405
 * Get all incoming events of the text edit and redirect some of them, like key up and
 
406
 * down, mouse press events, ... to the widget itself.
 
407
 */
 
408
bool CompletionList::eventFilter(QObject * watched, QEvent * event)
 
409
{
 
410
    if (isVisible() && watched == textEdit->viewport()) {
 
411
        if (event->type() == QEvent::MouseButtonPress)
 
412
            hide();
 
413
    } else if (isVisible() && watched == textEdit) {
 
414
        if (event->type() == QEvent::KeyPress) {
 
415
            QKeyEvent* ke = (QKeyEvent*)event;
 
416
            if (ke->key() == Qt::Key_Up || ke->key() == Qt::Key_Down) {
 
417
                keyPressEvent(ke);
 
418
                return true;
 
419
            } else if (ke->key() == Qt::Key_PageUp || ke->key() == Qt::Key_PageDown) {
 
420
                keyPressEvent(ke);
 
421
                return true;
 
422
            } else if (ke->key() == Qt::Key_Escape) {
 
423
                hide();
 
424
                return true;
 
425
            } else if (ke->key() == Qt::Key_Space) {
 
426
                hide();
 
427
                return false;
 
428
            } else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
 
429
                itemActivated(currentItem());
 
430
                return true;
 
431
            }
 
432
        } else if (event->type() == QEvent::FocusOut) {
 
433
            if (!hasFocus())
 
434
                hide();
 
435
        }
 
436
    }
 
437
 
 
438
    return QListWidget::eventFilter(watched, event);
 
439
}
 
440
 
 
441
/**
 
442
 * If an item was chosen (either by clicking or pressing enter) the rest of the word is completed.
 
443
 * The listbox is closed without destroying it.
 
444
 */
 
445
void CompletionList::completionItem(QListWidgetItem *item)
 
446
{
 
447
    hide();
 
448
    QString text = item->text();
 
449
    QTextCursor cursor = textEdit->textCursor();
 
450
    cursor.movePosition(QTextCursor::StartOfWord);
 
451
    cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
 
452
    cursor.insertText( text );
 
453
    textEdit->ensureCursorVisible();
 
454
}
 
455
 
 
456
#include "moc_TextEdit.cpp"