modernize: Use auto where the type is clear from context
[quassel.git] / src / uisupport / multilineedit.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 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 <QMessageBox>
23 #include <QScrollBar>
24
25 #include "actioncollection.h"
26 #include "bufferview.h"
27 #include "graphicalui.h"
28 #include "multilineedit.h"
29 #include "tabcompleter.h"
30
31 const int leftMargin = 3;
32
33 MultiLineEdit::MultiLineEdit(QWidget *parent)
34     : MultiLineEditParent(parent)
35 {
36     document()->setDocumentMargin(0);
37
38     setAcceptRichText(false);
39 #ifdef HAVE_KDE
40     enableFindReplace(false);
41 #endif
42
43 #if defined HAVE_SONNET && !defined HAVE_KDE
44     _spellCheckDecorator = new Sonnet::SpellCheckDecorator(this);
45     highlighter()->setActive(highlighter()->checkerEnabledByDefault());
46 #endif
47
48     setMode(SingleLine);
49     setLineWrapEnabled(false);
50     reset();
51
52     // Prevent QTextHtmlImporter::appendNodeText from eating whitespace
53     document()->setDefaultStyleSheet("span { white-space: pre-wrap; }");
54
55     connect(this, SIGNAL(textChanged()), this, SLOT(on_textChanged()));
56
57     _mircColorMap["00"] = "#ffffff";
58     _mircColorMap["01"] = "#000000";
59     _mircColorMap["02"] = "#000080";
60     _mircColorMap["03"] = "#008000";
61     _mircColorMap["04"] = "#ff0000";
62     _mircColorMap["05"] = "#800000";
63     _mircColorMap["06"] = "#800080";
64     _mircColorMap["07"] = "#ffa500";
65     _mircColorMap["08"] = "#ffff00";
66     _mircColorMap["09"] = "#00ff00";
67     _mircColorMap["10"] = "#008080";
68     _mircColorMap["11"] = "#00ffff";
69     _mircColorMap["12"] = "#4169e1";
70     _mircColorMap["13"] = "#ff00ff";
71     _mircColorMap["14"] = "#808080";
72     _mircColorMap["15"] = "#c0c0c0";
73 }
74
75
76 #if defined HAVE_SONNET && !defined HAVE_KDE
77 Sonnet::Highlighter *MultiLineEdit::highlighter() const
78 {
79     return _spellCheckDecorator->highlighter();
80 }
81
82
83 void MultiLineEdit::setSpellCheckEnabled(bool enabled)
84 {
85     highlighter()->setActive(enabled);
86     if (enabled) {
87         highlighter()->slotRehighlight();
88     }
89 }
90
91 void MultiLineEdit::contextMenuEvent(QContextMenuEvent *event)
92 {
93     QMenu *menu = createStandardContextMenu();
94     menu->addSeparator();
95
96     auto action = menu->addAction(tr("Auto Spell Check"));
97     action->setCheckable(true);
98     action->setChecked(highlighter()->isActive());
99     connect(action, SIGNAL(toggled(bool)), this, SLOT(setSpellCheckEnabled(bool)));
100
101     menu->exec(event->globalPos());
102     delete menu;
103 }
104
105 #endif
106
107
108 void MultiLineEdit::setCustomFont(const QFont &font)
109 {
110     setFont(font);
111     updateSizeHint();
112 }
113
114
115 void MultiLineEdit::setMode(Mode mode)
116 {
117     if (mode == _mode)
118         return;
119
120     _mode = mode;
121 }
122
123
124 void MultiLineEdit::setLineWrapEnabled(bool enable)
125 {
126     setLineWrapMode(enable ? WidgetWidth : NoWrap);
127     updateSizeHint();
128 }
129
130
131 void MultiLineEdit::setMinHeight(int lines)
132 {
133     if (lines == _minHeight)
134         return;
135
136     _minHeight = lines;
137     updateSizeHint();
138 }
139
140
141 void MultiLineEdit::setMaxHeight(int lines)
142 {
143     if (lines == _maxHeight)
144         return;
145
146     _maxHeight = lines;
147     updateSizeHint();
148 }
149
150
151 void MultiLineEdit::setScrollBarsEnabled(bool enable)
152 {
153     if (_scrollBarsEnabled == enable)
154         return;
155
156     _scrollBarsEnabled = enable;
157     updateScrollBars();
158 }
159
160
161 void MultiLineEdit::updateScrollBars()
162 {
163     QFontMetrics fm(font());
164     int _maxPixelHeight = fm.lineSpacing() * _maxHeight;
165     if (_scrollBarsEnabled && document()->size().height() > _maxPixelHeight)
166         setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
167     else
168         setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
169
170     if (!_scrollBarsEnabled || isSingleLine())
171         setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
172     else
173         setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
174 }
175
176
177 void MultiLineEdit::resizeEvent(QResizeEvent *event)
178 {
179     QTextEdit::resizeEvent(event);
180     updateSizeHint();
181     updateScrollBars();
182 }
183
184
185 void MultiLineEdit::updateSizeHint()
186 {
187     QFontMetrics fm(font());
188     int minPixelHeight = fm.lineSpacing() * _minHeight;
189     int maxPixelHeight = fm.lineSpacing() * _maxHeight;
190     int scrollBarHeight = horizontalScrollBar()->isVisible() ? horizontalScrollBar()->height() : 0;
191
192     // use the style to determine a decent size
193     int h = qMin(qMax((int)document()->size().height() + scrollBarHeight, minPixelHeight), maxPixelHeight) + 2 * frameWidth();
194
195     QStyleOptionFrame opt;
196     opt.initFrom(this);
197     opt.rect = QRect(0, 0, 100, h);
198     opt.lineWidth = lineWidth();
199     opt.midLineWidth = midLineWidth();
200     opt.state |= QStyle::State_Sunken;
201     QWidget *widget = this;
202 #ifdef Q_OS_MAC
203     widget = 0;
204 #endif
205     QSize s = style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(100, h).expandedTo(QApplication::globalStrut()), widget);
206     if (s != _sizeHint) {
207         _sizeHint = s;
208         updateGeometry();
209     }
210 }
211
212
213 QSize MultiLineEdit::sizeHint() const
214 {
215     if (!_sizeHint.isValid()) {
216         auto *that = const_cast<MultiLineEdit *>(this);
217         that->updateSizeHint();
218     }
219     return _sizeHint;
220 }
221
222
223 QSize MultiLineEdit::minimumSizeHint() const
224 {
225     return sizeHint();
226 }
227
228
229 void MultiLineEdit::setEmacsMode(bool enable)
230 {
231     _emacsMode = enable;
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         auto *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                     msg += lines[i].left(40).toHtmlEscaped();
731                     if (lines[i].count() > 40)
732                         msg += "...";
733                     msg += "<br />";
734                 }
735                 msg += "...</p>";
736                 QMessageBox question(QMessageBox::NoIcon, tr("Paste Protection"), msg, QMessageBox::Yes|QMessageBox::No);
737                 question.setDefaultButton(QMessageBox::No);
738 #ifdef Q_OS_MAC
739                 question.setWindowFlags(question.windowFlags() | Qt::Sheet);
740 #endif
741                 if (question.exec() != QMessageBox::Yes)
742                     return;
743             }
744
745             foreach(QString line, lines) {
746                 clear();
747                 insert(line);
748                 on_returnPressed();
749             }
750         }
751     }
752
753     _singleLine = (newText.indexOf('\n') < 0);
754
755     if (document()->size().height() != _lastDocumentHeight) {
756         _lastDocumentHeight = document()->size().height();
757         on_documentHeightChanged(_lastDocumentHeight);
758     }
759     updateSizeHint();
760     ensureCursorVisible();
761 }
762
763
764 void MultiLineEdit::on_documentHeightChanged(qreal)
765 {
766     updateScrollBars();
767 }
768
769
770 void MultiLineEdit::reset()
771 {
772     // every time the MultiLineEdit is cleared we also reset history index
773     _idx = _history.count();
774     clear();
775     QTextBlockFormat format = textCursor().blockFormat();
776     format.setLeftMargin(leftMargin); // we want a little space between the frame and the contents
777     textCursor().setBlockFormat(format);
778     updateScrollBars();
779 }
780
781
782 void MultiLineEdit::showHistoryEntry()
783 {
784     // if the user changed the history, display the changed line
785     setHtml(convertMircCodesToHtml(_tempHistory.contains(_idx) ? _tempHistory[_idx] : _history[_idx]));
786     QTextCursor cursor = textCursor();
787     QTextBlockFormat format = cursor.blockFormat();
788     format.setLeftMargin(leftMargin); // we want a little space between the frame and the contents
789     cursor.setBlockFormat(format);
790     cursor.movePosition(QTextCursor::End);
791     setTextCursor(cursor);
792     updateScrollBars();
793 }
794
795
796 void MultiLineEdit::addCompletionSpace()
797 {
798     // Inserting the space emits textChanged, which should not disable removal
799     _completionSpace = 2;
800     insertPlainText(" ");
801 }
802