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