876f0b9e0eaa145f2ecea076176720cc02102748
[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 MultiLineEdit::~MultiLineEdit()
77 {
78 }
79
80 #if defined HAVE_SONNET && !defined HAVE_KDE
81 Sonnet::Highlighter *MultiLineEdit::highlighter() const
82 {
83     return _spellCheckDecorator->highlighter();
84 }
85
86
87 void MultiLineEdit::setSpellCheckEnabled(bool enabled)
88 {
89     highlighter()->setActive(enabled);
90     if (enabled) {
91         highlighter()->slotRehighlight();
92     }
93 }
94
95 void MultiLineEdit::contextMenuEvent(QContextMenuEvent *event)
96 {
97     QMenu *menu = createStandardContextMenu();
98     menu->addSeparator();
99
100     auto action = menu->addAction(tr("Auto Spell Check"));
101     action->setCheckable(true);
102     action->setChecked(highlighter()->isActive());
103     connect(action, SIGNAL(toggled(bool)), this, SLOT(setSpellCheckEnabled(bool)));
104
105     menu->exec(event->globalPos());
106     delete menu;
107 }
108
109 #endif
110
111
112 void MultiLineEdit::setCustomFont(const QFont &font)
113 {
114     setFont(font);
115     updateSizeHint();
116 }
117
118
119 void MultiLineEdit::setMode(Mode mode)
120 {
121     if (mode == _mode)
122         return;
123
124     _mode = mode;
125 }
126
127
128 void MultiLineEdit::setLineWrapEnabled(bool enable)
129 {
130     setLineWrapMode(enable ? WidgetWidth : NoWrap);
131     updateSizeHint();
132 }
133
134
135 void MultiLineEdit::setMinHeight(int lines)
136 {
137     if (lines == _minHeight)
138         return;
139
140     _minHeight = lines;
141     updateSizeHint();
142 }
143
144
145 void MultiLineEdit::setMaxHeight(int lines)
146 {
147     if (lines == _maxHeight)
148         return;
149
150     _maxHeight = lines;
151     updateSizeHint();
152 }
153
154
155 void MultiLineEdit::setScrollBarsEnabled(bool enable)
156 {
157     if (_scrollBarsEnabled == enable)
158         return;
159
160     _scrollBarsEnabled = enable;
161     updateScrollBars();
162 }
163
164
165 void MultiLineEdit::updateScrollBars()
166 {
167     QFontMetrics fm(font());
168     int _maxPixelHeight = fm.lineSpacing() * _maxHeight;
169     if (_scrollBarsEnabled && document()->size().height() > _maxPixelHeight)
170         setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
171     else
172         setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
173
174     if (!_scrollBarsEnabled || isSingleLine())
175         setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
176     else
177         setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
178 }
179
180
181 void MultiLineEdit::resizeEvent(QResizeEvent *event)
182 {
183     QTextEdit::resizeEvent(event);
184     updateSizeHint();
185     updateScrollBars();
186 }
187
188
189 void MultiLineEdit::updateSizeHint()
190 {
191     QFontMetrics fm(font());
192     int minPixelHeight = fm.lineSpacing() * _minHeight;
193     int maxPixelHeight = fm.lineSpacing() * _maxHeight;
194     int scrollBarHeight = horizontalScrollBar()->isVisible() ? horizontalScrollBar()->height() : 0;
195
196     // use the style to determine a decent size
197     int h = qMin(qMax((int)document()->size().height() + scrollBarHeight, minPixelHeight), maxPixelHeight) + 2 * frameWidth();
198
199     QStyleOptionFrame opt;
200     opt.initFrom(this);
201     opt.rect = QRect(0, 0, 100, h);
202     opt.lineWidth = lineWidth();
203     opt.midLineWidth = midLineWidth();
204     opt.state |= QStyle::State_Sunken;
205     QWidget *widget = this;
206 #ifdef Q_OS_MAC
207     widget = 0;
208 #endif
209     QSize s = style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(100, h).expandedTo(QApplication::globalStrut()), widget);
210     if (s != _sizeHint) {
211         _sizeHint = s;
212         updateGeometry();
213     }
214 }
215
216
217 QSize MultiLineEdit::sizeHint() const
218 {
219     if (!_sizeHint.isValid()) {
220         MultiLineEdit *that = const_cast<MultiLineEdit *>(this);
221         that->updateSizeHint();
222     }
223     return _sizeHint;
224 }
225
226
227 QSize MultiLineEdit::minimumSizeHint() const
228 {
229     return sizeHint();
230 }
231
232
233 void MultiLineEdit::setEmacsMode(bool enable)
234 {
235     _emacsMode = enable;
236 }
237
238
239 void MultiLineEdit::setPasteProtectionEnabled(bool enable, QWidget *)
240 {
241     _pasteProtectionEnabled = enable;
242 }
243
244
245 void MultiLineEdit::historyMoveBack()
246 {
247     addToHistory(convertRichtextToMircCodes(), true);
248
249     if (_idx > 0) {
250         _idx--;
251         showHistoryEntry();
252     }
253 }
254
255
256 void MultiLineEdit::historyMoveForward()
257 {
258     addToHistory(convertRichtextToMircCodes(), true);
259
260     if (_idx < _history.count()) {
261         _idx++;
262         if (_idx < _history.count() || _tempHistory.contains(_idx)) // tempHistory might have an entry for idx == history.count() + 1
263             showHistoryEntry();
264         else
265             reset();        // equals clear() in this case
266     }
267     else {
268         addToHistory(convertRichtextToMircCodes());
269         reset();
270     }
271 }
272
273
274 bool MultiLineEdit::addToHistory(const QString &text, bool temporary)
275 {
276     if (text.isEmpty())
277         return false;
278
279     Q_ASSERT(0 <= _idx && _idx <= _history.count());
280
281     if (temporary) {
282         // if an entry of the history is changed, we remember it and show it again at this
283         // position until a line was actually sent
284         // sent lines get appended to the history
285         if (_history.isEmpty() || text != _history[_idx - (int)(_idx == _history.count())]) {
286             _tempHistory[_idx] = text;
287             return true;
288         }
289     }
290     else {
291         if (_history.isEmpty() || text != _history.last()) {
292             _history << text;
293             _tempHistory.clear();
294             return true;
295         }
296     }
297     return false;
298 }
299
300
301 bool MultiLineEdit::event(QEvent *e)
302 {
303     // We need to make sure that global shortcuts aren't eaten
304     if (e->type() == QEvent::ShortcutOverride) {
305         QKeyEvent *event = static_cast<QKeyEvent *>(e);
306         QKeySequence key = QKeySequence(event->key() | event->modifiers());
307         foreach(QAction *action, GraphicalUi::actionCollection()->actions()) {
308             if (action->shortcuts().contains(key)) {
309                 e->ignore();
310                 return false;
311             }
312         }
313     }
314
315     return MultiLineEditParent::event(e);
316 }
317
318
319 void MultiLineEdit::keyPressEvent(QKeyEvent *event)
320 {
321     if (event == QKeySequence::InsertLineSeparator) {
322         if (_mode == SingleLine) {
323             event->accept();
324             on_returnPressed();
325             return;
326         }
327         MultiLineEditParent::keyPressEvent(event);
328         return;
329     }
330
331     switch (event->key()) {
332     case Qt::Key_Up:
333         if (event->modifiers() & Qt::ShiftModifier)
334             break;
335         {
336             event->accept();
337             if (!(event->modifiers() & Qt::ControlModifier)) {
338                 int pos = textCursor().position();
339                 moveCursor(QTextCursor::Up);
340                 if (pos == textCursor().position()) // already on top line -> history
341                     historyMoveBack();
342             }
343             else
344                 historyMoveBack();
345             return;
346         }
347
348     case Qt::Key_Down:
349         if (event->modifiers() & Qt::ShiftModifier)
350             break;
351         {
352             event->accept();
353             if (!(event->modifiers() & Qt::ControlModifier)) {
354                 int pos = textCursor().position();
355                 moveCursor(QTextCursor::Down);
356                 if (pos == textCursor().position()) // already on bottom line -> history
357                     historyMoveForward();
358             }
359             else
360                 historyMoveForward();
361             return;
362         }
363
364     case Qt::Key_Return:
365     case Qt::Key_Enter:
366     case Qt::Key_Select:
367         event->accept();
368         on_returnPressed();
369         return;
370
371     // We don't want to have the tab key react even if no completer is installed
372     case Qt::Key_Tab:
373         event->accept();
374         return;
375
376     default:
377         ;
378     }
379
380     if (_emacsMode) {
381         if (event->modifiers() & Qt::ControlModifier) {
382             switch (event->key()) {
383             // move
384             case Qt::Key_A:
385                 moveCursor(QTextCursor::StartOfLine);
386                 return;
387             case Qt::Key_E:
388                 moveCursor(QTextCursor::EndOfLine);
389                 return;
390             case Qt::Key_F:
391                 moveCursor(QTextCursor::Right);
392                 return;
393             case Qt::Key_B:
394                 moveCursor(QTextCursor::Left);
395                 return;
396
397             // modify
398             case Qt::Key_Y:
399                 paste();
400                 return;
401             case Qt::Key_K:
402                 moveCursor(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
403                 cut();
404                 return;
405
406             default:
407                 break;
408             }
409         }
410         else if (event->modifiers() & Qt::MetaModifier ||
411                  event->modifiers() & Qt::AltModifier)
412         {
413             switch (event->key()) {
414             case Qt::Key_Right:
415                 moveCursor(QTextCursor::WordRight);
416                 return;
417             case Qt::Key_Left:
418                 moveCursor(QTextCursor::WordLeft);
419                 return;
420             case Qt::Key_F:
421                 moveCursor(QTextCursor::WordRight);
422                 return;
423             case Qt::Key_B:
424                 moveCursor(QTextCursor::WordLeft);
425                 return;
426             case Qt::Key_Less:
427                 moveCursor(QTextCursor::Start);
428                 return;
429             case Qt::Key_Greater:
430                 moveCursor(QTextCursor::End);
431                 return;
432
433             // modify
434             case Qt::Key_D:
435                 moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor);
436                 cut();
437                 return;
438
439             case Qt::Key_U: // uppercase word
440                 moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor);
441                 textCursor().insertText(textCursor().selectedText().toUpper());
442                 return;
443
444             case Qt::Key_L: // lowercase word
445                 moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor);
446                 textCursor().insertText(textCursor().selectedText().toLower());
447                 return;
448
449             case Qt::Key_C:
450             {           // capitalize word
451                 moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor);
452                 QString const text = textCursor().selectedText();
453                 textCursor().insertText(text.left(1).toUpper() + text.mid(1).toLower());
454                 return;
455             }
456
457             case Qt::Key_T:
458             {           // transpose words
459                 moveCursor(QTextCursor::StartOfWord);
460                 moveCursor(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
461                 QString const word1 = textCursor().selectedText();
462                 textCursor().clearSelection();
463                 moveCursor(QTextCursor::WordRight);
464                 moveCursor(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
465                 QString const word2 = textCursor().selectedText();
466                 if (!word2.isEmpty() && !word1.isEmpty()) {
467                     textCursor().insertText(word1);
468                     moveCursor(QTextCursor::WordLeft);
469                     moveCursor(QTextCursor::WordLeft);
470                     moveCursor(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
471                     textCursor().insertText(word2);
472                     moveCursor(QTextCursor::WordRight);
473                     moveCursor(QTextCursor::EndOfWord);
474                 }
475                 return;
476             }
477
478             default:
479                 break;
480             }
481         }
482     }
483
484 #ifdef HAVE_KDE
485     KTextEdit::keyPressEvent(event);
486 #else
487     QTextEdit::keyPressEvent(event);
488 #endif
489 }
490
491
492 QString MultiLineEdit::convertRichtextToMircCodes()
493 {
494     bool underline, bold, italic, color;
495     QString mircText, mircFgColor, mircBgColor;
496     QTextCursor cursor = textCursor();
497     QTextCursor peekcursor = textCursor();
498     cursor.movePosition(QTextCursor::Start);
499
500     underline = bold = italic = color = false;
501
502     while (cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor)) {
503         if (cursor.selectedText() == QString(QChar(QChar::LineSeparator))
504             || cursor.selectedText() == QString(QChar(QChar::ParagraphSeparator))) {
505             if (color) {
506                 color = false;
507                 mircText.append('\x03');
508             }
509             if (underline) {
510                 underline = false;
511                 mircText.append('\x1f');
512             }
513             if (italic) {
514                 italic = false;
515                 mircText.append('\x1d');
516             }
517             if (bold) {
518                 bold = false;
519                 mircText.append('\x02');
520             }
521             mircText.append('\n');
522         }
523         else {
524             if (!bold && cursor.charFormat().font().bold()) {
525                 bold = true;
526                 mircText.append('\x02');
527             }
528             if (!italic && cursor.charFormat().fontItalic()) {
529                 italic = true;
530                 mircText.append('\x1d');
531             }
532             if (!underline && cursor.charFormat().fontUnderline()) {
533                 underline = true;
534                 mircText.append('\x1f');
535             }
536             if (!color && (cursor.charFormat().foreground().isOpaque() || cursor.charFormat().background().isOpaque())) {
537                 color = true;
538                 mircText.append('\x03');
539                 mircFgColor = _mircColorMap.key(cursor.charFormat().foreground().color().name());
540                 mircBgColor = _mircColorMap.key(cursor.charFormat().background().color().name());
541
542                 if (mircFgColor.isEmpty()) {
543                     mircFgColor = "01"; //use black if the current foreground color can't be converted
544                 }
545
546                 mircText.append(mircFgColor);
547                 if (cursor.charFormat().background().isOpaque())
548                     mircText.append("," + mircBgColor);
549             }
550
551             mircText.append(cursor.selectedText());
552
553             peekcursor.setPosition(cursor.position());
554             peekcursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
555
556             if (mircCodesChanged(cursor, peekcursor)) {
557                 if (color) {
558                     color = false;
559                     mircText.append('\x03');
560                 }
561                 if (underline) {
562                     underline = false;
563                     mircText.append('\x1f');
564                 }
565                 if (italic) {
566                     italic = false;
567                     mircText.append('\x1d');
568                 }
569                 if (bold) {
570                     bold = false;
571                     mircText.append('\x02');
572                 }
573             }
574         }
575
576         cursor.clearSelection();
577     }
578
579     if (color)
580         mircText.append('\x03');
581
582     if (underline)
583         mircText.append('\x1f');
584
585     if (italic)
586         mircText.append('\x1d');
587
588     if (bold)
589         mircText.append('\x02');
590
591     return mircText;
592 }
593
594
595 bool MultiLineEdit::mircCodesChanged(QTextCursor &cursor, QTextCursor &peekcursor)
596 {
597     bool changed = false;
598     if (cursor.charFormat().font().bold() != peekcursor.charFormat().font().bold())
599         changed = true;
600     if (cursor.charFormat().fontItalic() != peekcursor.charFormat().fontItalic())
601         changed = true;
602     if (cursor.charFormat().fontUnderline() != peekcursor.charFormat().fontUnderline())
603         changed = true;
604     if (cursor.charFormat().foreground().color() != peekcursor.charFormat().foreground().color())
605         changed = true;
606     if (cursor.charFormat().background().color() != peekcursor.charFormat().background().color())
607         changed = true;
608     return changed;
609 }
610
611
612 QString MultiLineEdit::convertMircCodesToHtml(const QString &text)
613 {
614     QStringList words;
615     QRegExp mircCode = QRegExp("(\ 2|\1d|\1f|\ 3)", Qt::CaseSensitive);
616
617     int posLeft = 0;
618     int posRight = 0;
619
620     for (;;) {
621         posRight = mircCode.indexIn(text, posLeft);
622
623         if (posRight < 0) {
624             words << text.mid(posLeft);
625             break; // no more mirc color codes
626         }
627
628         if (posLeft < posRight) {
629             words << text.mid(posLeft, posRight - posLeft);
630             posLeft = posRight;
631         }
632
633         posRight = text.indexOf(mircCode.cap(), posRight + 1);
634         words << text.mid(posLeft, posRight + 1 - posLeft);
635         posLeft = posRight + 1;
636     }
637
638     for (int i = 0; i < words.count(); i++) {
639         QString style;
640         if (words[i].contains('\x02')) {
641             style.append(" font-weight:600;");
642             words[i].replace('\x02', "");
643         }
644         if (words[i].contains('\x1d')) {
645             style.append(" font-style:italic;");
646             words[i].replace('\x1d', "");
647         }
648         if (words[i].contains('\x1f')) {
649             style.append(" text-decoration: underline;");
650             words[i].replace('\x1f', "");
651         }
652         if (words[i].contains('\x03')) {
653             int pos = words[i].indexOf('\x03');
654             int len = 3;
655             QString fg = words[i].mid(pos + 1, 2);
656             QString bg;
657             if (words[i][pos+3] == ',')
658                 bg = words[i].mid(pos+4, 2);
659
660             style.append(" color:");
661             style.append(_mircColorMap[fg]);
662             style.append(";");
663
664             if (!bg.isEmpty()) {
665                 style.append(" background-color:");
666                 style.append(_mircColorMap[bg]);
667                 style.append(";");
668                 len = 6;
669             }
670             words[i].replace(pos, len, "");
671             words[i].replace('\x03', "");
672         }
673         words[i].replace("&", "&amp;");
674         words[i].replace("<", "&lt;");
675         words[i].replace(">", "&gt;");
676         words[i].replace("\"", "&quot;");
677         if (style.isEmpty()) {
678             words[i] = "<span>" + words[i] + "</span>";
679         }
680         else {
681             words[i] = "<span style=\"" + style + "\">" + words[i] + "</span>";
682         }
683     }
684     return words.join("").replace("\n", "<br />");
685 }
686
687
688 void MultiLineEdit::on_returnPressed()
689 {
690     on_returnPressed(convertRichtextToMircCodes());
691 }
692
693
694 void MultiLineEdit::on_returnPressed(QString text)
695 {
696     if (_completionSpace && text.endsWith(" ")) {
697         text.chop(1);
698     }
699
700     if (!text.isEmpty()) {
701         foreach(const QString &line, text.split('\n', QString::SkipEmptyParts)) {
702             if (line.isEmpty())
703                 continue;
704             addToHistory(line);
705             emit textEntered(line);
706         }
707         reset();
708         _tempHistory.clear();
709     }
710     else {
711         emit noTextEntered();
712     }
713 }
714
715
716 void MultiLineEdit::on_textChanged()
717 {
718     _completionSpace = qMax(_completionSpace - 1, 0);
719
720     QString newText = text();
721     newText.replace("\r\n", "\n");
722     newText.replace('\r', '\n');
723     if (_mode == SingleLine) {
724         if (!pasteProtectionEnabled())
725             newText.replace('\n', ' ');
726         else if (newText.contains('\n')) {
727             QStringList lines = newText.split('\n', QString::SkipEmptyParts);
728             clear();
729
730             if (lines.count() >= 4) {
731                 QString msg = tr("Do you really want to paste %n line(s)?", "", lines.count());
732                 msg += "<p>";
733                 for (int i = 0; i < 4; i++) {
734                     msg += lines[i].left(40).toHtmlEscaped();
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