Avoid deprecation warnings with Qt 5.7
[quassel.git] / src / uisupport / multilineedit.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2015 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include <QApplication>
22 #include <QMenu>
23 #include <QMessageBox>
24 #include <QScrollBar>
25
26 #include "actioncollection.h"
27 #include "bufferview.h"
28 #include "graphicalui.h"
29 #include "multilineedit.h"
30 #include "tabcompleter.h"
31
32 const int leftMargin = 3;
33
34 MultiLineEdit::MultiLineEdit(QWidget *parent)
35     : MultiLineEditParent(parent),
36     _idx(0),
37     _mode(SingleLine),
38     _singleLine(true),
39     _minHeight(1),
40     _maxHeight(5),
41     _scrollBarsEnabled(true),
42     _pasteProtectionEnabled(true),
43     _emacsMode(false),
44     _completionSpace(0),
45     _lastDocumentHeight(-1)
46 {
47     document()->setDocumentMargin(0);
48
49     setAcceptRichText(false);
50 #ifdef HAVE_KDE
51     enableFindReplace(false);
52 #endif
53
54     setMode(SingleLine);
55     setLineWrapEnabled(false);
56     reset();
57
58     // Prevent QTextHtmlImporter::appendNodeText from eating whitespace
59     document()->setDefaultStyleSheet("span { white-space: pre-wrap; }");
60
61     connect(this, SIGNAL(textChanged()), this, SLOT(on_textChanged()));
62
63     _mircColorMap["00"] = "#ffffff";
64     _mircColorMap["01"] = "#000000";
65     _mircColorMap["02"] = "#000080";
66     _mircColorMap["03"] = "#008000";
67     _mircColorMap["04"] = "#ff0000";
68     _mircColorMap["05"] = "#800000";
69     _mircColorMap["06"] = "#800080";
70     _mircColorMap["07"] = "#ffa500";
71     _mircColorMap["08"] = "#ffff00";
72     _mircColorMap["09"] = "#00ff00";
73     _mircColorMap["10"] = "#008080";
74     _mircColorMap["11"] = "#00ffff";
75     _mircColorMap["12"] = "#4169e1";
76     _mircColorMap["13"] = "#ff00ff";
77     _mircColorMap["14"] = "#808080";
78     _mircColorMap["15"] = "#c0c0c0";
79 }
80
81
82 MultiLineEdit::~MultiLineEdit()
83 {
84 }
85
86 #if defined HAVE_KF5 || defined HAVE_KDE4
87 void MultiLineEdit::createHighlighter()
88 {
89     KTextEdit::createHighlighter();
90     if (highlighter())
91         highlighter()->setAutomatic(false);
92 }
93 #endif
94
95 void MultiLineEdit::setCustomFont(const QFont &font)
96 {
97     setFont(font);
98     updateSizeHint();
99 }
100
101
102 void MultiLineEdit::setMode(Mode mode)
103 {
104     if (mode == _mode)
105         return;
106
107     _mode = mode;
108 }
109
110
111 void MultiLineEdit::setLineWrapEnabled(bool enable)
112 {
113     setLineWrapMode(enable ? WidgetWidth : NoWrap);
114     updateSizeHint();
115 }
116
117
118 void MultiLineEdit::setMinHeight(int lines)
119 {
120     if (lines == _minHeight)
121         return;
122
123     _minHeight = lines;
124     updateSizeHint();
125 }
126
127
128 void MultiLineEdit::setMaxHeight(int lines)
129 {
130     if (lines == _maxHeight)
131         return;
132
133     _maxHeight = lines;
134     updateSizeHint();
135 }
136
137
138 void MultiLineEdit::setScrollBarsEnabled(bool enable)
139 {
140     if (_scrollBarsEnabled == enable)
141         return;
142
143     _scrollBarsEnabled = enable;
144     updateScrollBars();
145 }
146
147
148 void MultiLineEdit::updateScrollBars()
149 {
150     QFontMetrics fm(font());
151     int _maxPixelHeight = fm.lineSpacing() * _maxHeight;
152     if (_scrollBarsEnabled && document()->size().height() > _maxPixelHeight)
153         setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
154     else
155         setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
156
157     if (!_scrollBarsEnabled || isSingleLine())
158         setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
159     else
160         setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
161 }
162
163
164 void MultiLineEdit::resizeEvent(QResizeEvent *event)
165 {
166     QTextEdit::resizeEvent(event);
167     updateSizeHint();
168     updateScrollBars();
169 }
170
171
172 void MultiLineEdit::updateSizeHint()
173 {
174     QFontMetrics fm(font());
175     int minPixelHeight = fm.lineSpacing() * _minHeight;
176     int maxPixelHeight = fm.lineSpacing() * _maxHeight;
177     int scrollBarHeight = horizontalScrollBar()->isVisible() ? horizontalScrollBar()->height() : 0;
178
179     // use the style to determine a decent size
180     int h = qMin(qMax((int)document()->size().height() + scrollBarHeight, minPixelHeight), maxPixelHeight) + 2 * frameWidth();
181 #if QT_VERSION < 0x050000
182     QStyleOptionFrameV2 opt;
183 #else
184     QStyleOptionFrame opt;
185 #endif
186     opt.initFrom(this);
187     opt.rect = QRect(0, 0, 100, h);
188     opt.lineWidth = lineWidth();
189     opt.midLineWidth = midLineWidth();
190     opt.state |= QStyle::State_Sunken;
191     QWidget *widget = this;
192 #ifdef Q_OS_MAC
193     widget = 0;
194 #endif
195     QSize s = style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(100, h).expandedTo(QApplication::globalStrut()), widget);
196     if (s != _sizeHint) {
197         _sizeHint = s;
198         updateGeometry();
199     }
200 }
201
202
203 QSize MultiLineEdit::sizeHint() const
204 {
205     if (!_sizeHint.isValid()) {
206         MultiLineEdit *that = const_cast<MultiLineEdit *>(this);
207         that->updateSizeHint();
208     }
209     return _sizeHint;
210 }
211
212
213 QSize MultiLineEdit::minimumSizeHint() const
214 {
215     return sizeHint();
216 }
217
218
219 void MultiLineEdit::setEmacsMode(bool enable)
220 {
221     _emacsMode = enable;
222 }
223
224
225 void MultiLineEdit::setSpellCheckEnabled(bool enable)
226 {
227 #ifdef HAVE_KDE
228     setCheckSpellingEnabled(enable);
229 #else
230     Q_UNUSED(enable)
231 #endif
232 }
233
234
235 void MultiLineEdit::setPasteProtectionEnabled(bool enable, QWidget *)
236 {
237     _pasteProtectionEnabled = enable;
238 }
239
240
241 void MultiLineEdit::historyMoveBack()
242 {
243     addToHistory(convertRichtextToMircCodes(), true);
244
245     if (_idx > 0) {
246         _idx--;
247         showHistoryEntry();
248     }
249 }
250
251
252 void MultiLineEdit::historyMoveForward()
253 {
254     addToHistory(convertRichtextToMircCodes(), true);
255
256     if (_idx < _history.count()) {
257         _idx++;
258         if (_idx < _history.count() || _tempHistory.contains(_idx)) // tempHistory might have an entry for idx == history.count() + 1
259             showHistoryEntry();
260         else
261             reset();        // equals clear() in this case
262     }
263     else {
264         addToHistory(convertRichtextToMircCodes());
265         reset();
266     }
267 }
268
269
270 bool MultiLineEdit::addToHistory(const QString &text, bool temporary)
271 {
272     if (text.isEmpty())
273         return false;
274
275     Q_ASSERT(0 <= _idx && _idx <= _history.count());
276
277     if (temporary) {
278         // if an entry of the history is changed, we remember it and show it again at this
279         // position until a line was actually sent
280         // sent lines get appended to the history
281         if (_history.isEmpty() || text != _history[_idx - (int)(_idx == _history.count())]) {
282             _tempHistory[_idx] = text;
283             return true;
284         }
285     }
286     else {
287         if (_history.isEmpty() || text != _history.last()) {
288             _history << text;
289             _tempHistory.clear();
290             return true;
291         }
292     }
293     return false;
294 }
295
296
297 bool MultiLineEdit::event(QEvent *e)
298 {
299     // We need to make sure that global shortcuts aren't eaten
300     if (e->type() == QEvent::ShortcutOverride) {
301         QKeyEvent *event = static_cast<QKeyEvent *>(e);
302         QKeySequence key = QKeySequence(event->key() | event->modifiers());
303         foreach(QAction *action, GraphicalUi::actionCollection()->actions()) {
304             if (action->shortcuts().contains(key)) {
305                 e->ignore();
306                 return false;
307             }
308         }
309     }
310
311     return MultiLineEditParent::event(e);
312 }
313
314
315 void MultiLineEdit::keyPressEvent(QKeyEvent *event)
316 {
317     if (event == QKeySequence::InsertLineSeparator) {
318         if (_mode == SingleLine) {
319             event->accept();
320             on_returnPressed();
321             return;
322         }
323         MultiLineEditParent::keyPressEvent(event);
324         return;
325     }
326
327     switch (event->key()) {
328     case Qt::Key_Up:
329         if (event->modifiers() & Qt::ShiftModifier)
330             break;
331         {
332             event->accept();
333             if (!(event->modifiers() & Qt::ControlModifier)) {
334                 int pos = textCursor().position();
335                 moveCursor(QTextCursor::Up);
336                 if (pos == textCursor().position()) // already on top line -> history
337                     historyMoveBack();
338             }
339             else
340                 historyMoveBack();
341             return;
342         }
343
344     case Qt::Key_Down:
345         if (event->modifiers() & Qt::ShiftModifier)
346             break;
347         {
348             event->accept();
349             if (!(event->modifiers() & Qt::ControlModifier)) {
350                 int pos = textCursor().position();
351                 moveCursor(QTextCursor::Down);
352                 if (pos == textCursor().position()) // already on bottom line -> history
353                     historyMoveForward();
354             }
355             else
356                 historyMoveForward();
357             return;
358         }
359
360     case Qt::Key_Return:
361     case Qt::Key_Enter:
362     case Qt::Key_Select:
363         event->accept();
364         on_returnPressed();
365         return;
366
367     // We don't want to have the tab key react even if no completer is installed
368     case Qt::Key_Tab:
369         event->accept();
370         return;
371
372     default:
373         ;
374     }
375
376     if (_emacsMode) {
377         if (event->modifiers() & Qt::ControlModifier) {
378             switch (event->key()) {
379             // move
380             case Qt::Key_A:
381                 moveCursor(QTextCursor::StartOfLine);
382                 return;
383             case Qt::Key_E:
384                 moveCursor(QTextCursor::EndOfLine);
385                 return;
386             case Qt::Key_F:
387                 moveCursor(QTextCursor::Right);
388                 return;
389             case Qt::Key_B:
390                 moveCursor(QTextCursor::Left);
391                 return;
392
393             // modify
394             case Qt::Key_Y:
395                 paste();
396                 return;
397             case Qt::Key_K:
398                 moveCursor(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
399                 cut();
400                 return;
401
402             default:
403                 break;
404             }
405         }
406         else if (event->modifiers() & Qt::MetaModifier ||
407                  event->modifiers() & Qt::AltModifier)
408         {
409             switch (event->key()) {
410             case Qt::Key_Right:
411                 moveCursor(QTextCursor::WordRight);
412                 return;
413             case Qt::Key_Left:
414                 moveCursor(QTextCursor::WordLeft);
415                 return;
416             case Qt::Key_F:
417                 moveCursor(QTextCursor::WordRight);
418                 return;
419             case Qt::Key_B:
420                 moveCursor(QTextCursor::WordLeft);
421                 return;
422             case Qt::Key_Less:
423                 moveCursor(QTextCursor::Start);
424                 return;
425             case Qt::Key_Greater:
426                 moveCursor(QTextCursor::End);
427                 return;
428
429             // modify
430             case Qt::Key_D:
431                 moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor);
432                 cut();
433                 return;
434
435             case Qt::Key_U: // uppercase word
436                 moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor);
437                 textCursor().insertText(textCursor().selectedText().toUpper());
438                 return;
439
440             case Qt::Key_L: // lowercase word
441                 moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor);
442                 textCursor().insertText(textCursor().selectedText().toLower());
443                 return;
444
445             case Qt::Key_C:
446             {           // capitalize word
447                 moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor);
448                 QString const text = textCursor().selectedText();
449                 textCursor().insertText(text.left(1).toUpper() + text.mid(1).toLower());
450                 return;
451             }
452
453             case Qt::Key_T:
454             {           // transpose words
455                 moveCursor(QTextCursor::StartOfWord);
456                 moveCursor(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
457                 QString const word1 = textCursor().selectedText();
458                 textCursor().clearSelection();
459                 moveCursor(QTextCursor::WordRight);
460                 moveCursor(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
461                 QString const word2 = textCursor().selectedText();
462                 if (!word2.isEmpty() && !word1.isEmpty()) {
463                     textCursor().insertText(word1);
464                     moveCursor(QTextCursor::WordLeft);
465                     moveCursor(QTextCursor::WordLeft);
466                     moveCursor(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
467                     textCursor().insertText(word2);
468                     moveCursor(QTextCursor::WordRight);
469                     moveCursor(QTextCursor::EndOfWord);
470                 }
471                 return;
472             }
473
474             default:
475                 break;
476             }
477         }
478     }
479
480 #ifdef HAVE_KDE
481     KTextEdit::keyPressEvent(event);
482 #else
483     QTextEdit::keyPressEvent(event);
484 #endif
485 }
486
487
488 QString MultiLineEdit::convertRichtextToMircCodes()
489 {
490     bool underline, bold, italic, color;
491     QString mircText, mircFgColor, mircBgColor;
492     QTextCursor cursor = textCursor();
493     QTextCursor peekcursor = textCursor();
494     cursor.movePosition(QTextCursor::Start);
495
496     underline = bold = italic = color = false;
497
498     while (cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor)) {
499         if (cursor.selectedText() == QString(QChar(QChar::LineSeparator))
500             || cursor.selectedText() == QString(QChar(QChar::ParagraphSeparator))) {
501             if (color) {
502                 color = false;
503                 mircText.append('\x03');
504             }
505             if (underline) {
506                 underline = false;
507                 mircText.append('\x1f');
508             }
509             if (italic) {
510                 italic = false;
511                 mircText.append('\x1d');
512             }
513             if (bold) {
514                 bold = false;
515                 mircText.append('\x02');
516             }
517             mircText.append('\n');
518         }
519         else {
520             if (!bold && cursor.charFormat().font().bold()) {
521                 bold = true;
522                 mircText.append('\x02');
523             }
524             if (!italic && cursor.charFormat().fontItalic()) {
525                 italic = true;
526                 mircText.append('\x1d');
527             }
528             if (!underline && cursor.charFormat().fontUnderline()) {
529                 underline = true;
530                 mircText.append('\x1f');
531             }
532             if (!color && (cursor.charFormat().foreground().isOpaque() || cursor.charFormat().background().isOpaque())) {
533                 color = true;
534                 mircText.append('\x03');
535                 mircFgColor = _mircColorMap.key(cursor.charFormat().foreground().color().name());
536                 mircBgColor = _mircColorMap.key(cursor.charFormat().background().color().name());
537
538                 if (mircFgColor.isEmpty()) {
539                     mircFgColor = "01"; //use black if the current foreground color can't be converted
540                 }
541
542                 mircText.append(mircFgColor);
543                 if (cursor.charFormat().background().isOpaque())
544                     mircText.append("," + mircBgColor);
545             }
546
547             mircText.append(cursor.selectedText());
548
549             peekcursor.setPosition(cursor.position());
550             peekcursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
551
552             if (mircCodesChanged(cursor, peekcursor)) {
553                 if (color) {
554                     color = false;
555                     mircText.append('\x03');
556                 }
557                 if (underline) {
558                     underline = false;
559                     mircText.append('\x1f');
560                 }
561                 if (italic) {
562                     italic = false;
563                     mircText.append('\x1d');
564                 }
565                 if (bold) {
566                     bold = false;
567                     mircText.append('\x02');
568                 }
569             }
570         }
571
572         cursor.clearSelection();
573     }
574
575     if (color)
576         mircText.append('\x03');
577
578     if (underline)
579         mircText.append('\x1f');
580
581     if (italic)
582         mircText.append('\x1d');
583
584     if (bold)
585         mircText.append('\x02');
586
587     return mircText;
588 }
589
590
591 bool MultiLineEdit::mircCodesChanged(QTextCursor &cursor, QTextCursor &peekcursor)
592 {
593     bool changed = false;
594     if (cursor.charFormat().font().bold() != peekcursor.charFormat().font().bold())
595         changed = true;
596     if (cursor.charFormat().fontItalic() != peekcursor.charFormat().fontItalic())
597         changed = true;
598     if (cursor.charFormat().fontUnderline() != peekcursor.charFormat().fontUnderline())
599         changed = true;
600     if (cursor.charFormat().foreground().color() != peekcursor.charFormat().foreground().color())
601         changed = true;
602     if (cursor.charFormat().background().color() != peekcursor.charFormat().background().color())
603         changed = true;
604     return changed;
605 }
606
607
608 QString MultiLineEdit::convertMircCodesToHtml(const QString &text)
609 {
610     QStringList words;
611     QRegExp mircCode = QRegExp("(\ 2|\1d|\1f|\ 3)", Qt::CaseSensitive);
612
613     int posLeft = 0;
614     int posRight = 0;
615
616     for (;;) {
617         posRight = mircCode.indexIn(text, posLeft);
618
619         if (posRight < 0) {
620             words << text.mid(posLeft);
621             break; // no more mirc color codes
622         }
623
624         if (posLeft < posRight) {
625             words << text.mid(posLeft, posRight - posLeft);
626             posLeft = posRight;
627         }
628
629         posRight = text.indexOf(mircCode.cap(), posRight + 1);
630         words << text.mid(posLeft, posRight + 1 - posLeft);
631         posLeft = posRight + 1;
632     }
633
634     for (int i = 0; i < words.count(); i++) {
635         QString style;
636         if (words[i].contains('\x02')) {
637             style.append(" font-weight:600;");
638             words[i].replace('\x02', "");
639         }
640         if (words[i].contains('\x1d')) {
641             style.append(" font-style:italic;");
642             words[i].replace('\x1d', "");
643         }
644         if (words[i].contains('\x1f')) {
645             style.append(" text-decoration: underline;");
646             words[i].replace('\x1f', "");
647         }
648         if (words[i].contains('\x03')) {
649             int pos = words[i].indexOf('\x03');
650             int len = 3;
651             QString fg = words[i].mid(pos + 1, 2);
652             QString bg;
653             if (words[i][pos+3] == ',')
654                 bg = words[i].mid(pos+4, 2);
655
656             style.append(" color:");
657             style.append(_mircColorMap[fg]);
658             style.append(";");
659
660             if (!bg.isEmpty()) {
661                 style.append(" background-color:");
662                 style.append(_mircColorMap[bg]);
663                 style.append(";");
664                 len = 6;
665             }
666             words[i].replace(pos, len, "");
667             words[i].replace('\x03', "");
668         }
669         words[i].replace("&", "&amp;");
670         words[i].replace("<", "&lt;");
671         words[i].replace(">", "&gt;");
672         words[i].replace("\"", "&quot;");
673         if (style.isEmpty()) {
674             words[i] = "<span>" + words[i] + "</span>";
675         }
676         else {
677             words[i] = "<span style=\"" + style + "\">" + words[i] + "</span>";
678         }
679     }
680     return words.join("").replace("\n", "<br />");
681 }
682
683
684 void MultiLineEdit::on_returnPressed()
685 {
686     on_returnPressed(convertRichtextToMircCodes());
687 }
688
689
690 void MultiLineEdit::on_returnPressed(QString text)
691 {
692     if (_completionSpace && text.endsWith(" ")) {
693         text.chop(1);
694     }
695
696     if (!text.isEmpty()) {
697         foreach(const QString &line, text.split('\n', QString::SkipEmptyParts)) {
698             if (line.isEmpty())
699                 continue;
700             addToHistory(line);
701             emit textEntered(line);
702         }
703         reset();
704         _tempHistory.clear();
705     }
706     else {
707         emit noTextEntered();
708     }
709 }
710
711
712 void MultiLineEdit::on_textChanged()
713 {
714     _completionSpace = qMax(_completionSpace - 1, 0);
715
716     QString newText = text();
717     newText.replace("\r\n", "\n");
718     newText.replace('\r', '\n');
719     if (_mode == SingleLine) {
720         if (!pasteProtectionEnabled())
721             newText.replace('\n', ' ');
722         else if (newText.contains('\n')) {
723             QStringList lines = newText.split('\n', QString::SkipEmptyParts);
724             clear();
725
726             if (lines.count() >= 4) {
727                 QString msg = tr("Do you really want to paste %n line(s)?", "", lines.count());
728                 msg += "<p>";
729                 for (int i = 0; i < 4; i++) {
730 #if QT_VERSION < 0x050000
731                     msg += Qt::escape(lines[i].left(40));
732 #else
733                     msg += lines[i].left(40).toHtmlEscaped();
734 #endif
735                     if (lines[i].count() > 40)
736                         msg += "...";
737                     msg += "<br />";
738                 }
739                 msg += "...</p>";
740                 QMessageBox question(QMessageBox::NoIcon, tr("Paste Protection"), msg, QMessageBox::Yes|QMessageBox::No);
741                 question.setDefaultButton(QMessageBox::No);
742 #ifdef Q_OS_MAC
743                 question.setWindowFlags(question.windowFlags() | Qt::Sheet);
744 #endif
745                 if (question.exec() != QMessageBox::Yes)
746                     return;
747             }
748
749             foreach(QString line, lines) {
750                 clear();
751                 insert(line);
752                 on_returnPressed();
753             }
754         }
755     }
756
757     _singleLine = (newText.indexOf('\n') < 0);
758
759     if (document()->size().height() != _lastDocumentHeight) {
760         _lastDocumentHeight = document()->size().height();
761         on_documentHeightChanged(_lastDocumentHeight);
762     }
763     updateSizeHint();
764     ensureCursorVisible();
765 }
766
767
768 void MultiLineEdit::on_documentHeightChanged(qreal)
769 {
770     updateScrollBars();
771 }
772
773
774 void MultiLineEdit::reset()
775 {
776     // every time the MultiLineEdit is cleared we also reset history index
777     _idx = _history.count();
778     clear();
779     QTextBlockFormat format = textCursor().blockFormat();
780     format.setLeftMargin(leftMargin); // we want a little space between the frame and the contents
781     textCursor().setBlockFormat(format);
782     updateScrollBars();
783 }
784
785
786 void MultiLineEdit::showHistoryEntry()
787 {
788     // if the user changed the history, display the changed line
789     setHtml(convertMircCodesToHtml(_tempHistory.contains(_idx) ? _tempHistory[_idx] : _history[_idx]));
790     QTextCursor cursor = textCursor();
791     QTextBlockFormat format = cursor.blockFormat();
792     format.setLeftMargin(leftMargin); // we want a little space between the frame and the contents
793     cursor.setBlockFormat(format);
794     cursor.movePosition(QTextCursor::End);
795     setTextCursor(cursor);
796     updateScrollBars();
797 }
798
799
800 void MultiLineEdit::addCompletionSpace()
801 {
802     // Inserting the space emits textChanged, which should not disable removal
803     _completionSpace = 2;
804     insertPlainText(" ");
805 }
806