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