X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fuisupport%2Fmultilineedit.cpp;h=a9a2ae248a75532ed3594a25d225d628cef30bbf;hp=c80c2fce901dc4abac5c6da88c758bf2eb5ff302;hb=b359cfe9fdd2427993dc0b2f3f605fd69bbe6bd2;hpb=a9c2f4157175cfb775980eb72c8312cdffcbfe00 diff --git a/src/uisupport/multilineedit.cpp b/src/uisupport/multilineedit.cpp index c80c2fce..a9a2ae24 100644 --- a/src/uisupport/multilineedit.cpp +++ b/src/uisupport/multilineedit.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005/06 by the Quassel Project * + * Copyright (C) 2005-2019 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -15,556 +15,758 @@ * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ +#include "multilineedit.h" + #include -#include #include #include +#include "actioncollection.h" #include "bufferview.h" #include "graphicalui.h" -#include "multilineedit.h" #include "tabcompleter.h" const int leftMargin = 3; -MultiLineEdit::MultiLineEdit(QWidget *parent) - : -#ifdef HAVE_KDE - KTextEdit(parent), -#else - QTextEdit(parent), -#endif - idx(0), - _mode(SingleLine), - _singleLine(true), - _minHeight(1), - _maxHeight(5), - _scrollBarsEnabled(true), - _lastDocumentHeight(-1) -{ -#if QT_VERSION >= 0x040500 - document()->setDocumentMargin(0); // new in Qt 4.5 and we really don't want it here -#endif +MultiLineEdit::MultiLineEdit(QWidget* parent) + : MultiLineEditParent(parent) +{ + document()->setDocumentMargin(0); - setAcceptRichText(false); + setAcceptRichText(false); #ifdef HAVE_KDE - enableFindReplace(false); + enableFindReplace(false); #endif - setMode(SingleLine); - setWordWrapEnabled(false); - reset(); - - connect(this, SIGNAL(textChanged()), this, SLOT(on_textChanged())); +#if defined HAVE_SONNET && !defined HAVE_KDE + _spellCheckDecorator = new Sonnet::SpellCheckDecorator(this); + highlighter()->setActive(highlighter()->checkerEnabledByDefault()); +#endif - mircColorMap["00"] = "#ffffff"; - mircColorMap["01"] = "#000000"; - mircColorMap["02"] = "#000080"; - mircColorMap["03"] = "#008000"; - mircColorMap["04"] = "#ff0000"; - mircColorMap["05"] = "#800000"; - mircColorMap["06"] = "#800080"; - mircColorMap["07"] = "#ffa500"; - mircColorMap["08"] = "#ffff00"; - mircColorMap["09"] = "#00ff00"; - mircColorMap["10"] = "#008080"; - mircColorMap["11"] = "#00ffff"; - mircColorMap["12"] = "#4169e1"; - mircColorMap["13"] = "#ff00ff"; - mircColorMap["14"] = "#808080"; - mircColorMap["15"] = "#c0c0c0"; + setMode(SingleLine); + setLineWrapEnabled(false); + reset(); + // Prevent QTextHtmlImporter::appendNodeText from eating whitespace + document()->setDefaultStyleSheet("span { white-space: pre-wrap; }"); + + connect(this, &QTextEdit::textChanged, this, &MultiLineEdit::on_textChanged); + + _mircColorMap["00"] = "#ffffff"; + _mircColorMap["01"] = "#000000"; + _mircColorMap["02"] = "#000080"; + _mircColorMap["03"] = "#008000"; + _mircColorMap["04"] = "#ff0000"; + _mircColorMap["05"] = "#800000"; + _mircColorMap["06"] = "#800080"; + _mircColorMap["07"] = "#ffa500"; + _mircColorMap["08"] = "#ffff00"; + _mircColorMap["09"] = "#00ff00"; + _mircColorMap["10"] = "#008080"; + _mircColorMap["11"] = "#00ffff"; + _mircColorMap["12"] = "#4169e1"; + _mircColorMap["13"] = "#ff00ff"; + _mircColorMap["14"] = "#808080"; + _mircColorMap["15"] = "#c0c0c0"; } -MultiLineEdit::~MultiLineEdit() { +#if defined HAVE_SONNET && !defined HAVE_KDE +Sonnet::Highlighter* MultiLineEdit::highlighter() const +{ + return _spellCheckDecorator->highlighter(); } -void MultiLineEdit::setCustomFont(const QFont &font) { - setFont(font); - updateSizeHint(); +void MultiLineEdit::setSpellCheckEnabled(bool enabled) +{ + highlighter()->setActive(enabled); + if (enabled) { + highlighter()->slotRehighlight(); + } } -void MultiLineEdit::setMode(Mode mode) { - if(mode == _mode) - return; +void MultiLineEdit::contextMenuEvent(QContextMenuEvent* event) +{ + QMenu* menu = createStandardContextMenu(); + menu->addSeparator(); + + auto action = menu->addAction(tr("Auto Spell Check")); + action->setCheckable(true); + action->setChecked(highlighter()->isActive()); + connect(action, &QAction::toggled, this, &MultiLineEdit::setSpellCheckEnabled); - _mode = mode; + menu->exec(event->globalPos()); + delete menu; } -void MultiLineEdit::setMinHeight(int lines) { - if(lines == _minHeight) - return; +#endif - _minHeight = lines; - updateSizeHint(); +void MultiLineEdit::setCustomFont(const QFont& font) +{ + setFont(font); + updateSizeHint(); } -void MultiLineEdit::setMaxHeight(int lines) { - if(lines == _maxHeight) - return; +void MultiLineEdit::setMode(Mode mode) +{ + if (mode == _mode) + return; - _maxHeight = lines; - updateSizeHint(); + _mode = mode; } -void MultiLineEdit::setScrollBarsEnabled(bool enable) { - if(_scrollBarsEnabled == enable) - return; - - _scrollBarsEnabled = enable; - updateScrollBars(); +void MultiLineEdit::setLineWrapEnabled(bool enable) +{ + setLineWrapMode(enable ? WidgetWidth : NoWrap); + updateSizeHint(); } -void MultiLineEdit::updateScrollBars() { - QFontMetrics fm(font()); - int _maxPixelHeight = fm.lineSpacing() * _maxHeight; - if(_scrollBarsEnabled && document()->size().height() > _maxPixelHeight) - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - else - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); +void MultiLineEdit::setMinHeight(int lines) +{ + if (lines == _minHeight) + return; - if(!_scrollBarsEnabled || isSingleLine()) - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - else - setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + _minHeight = lines; + updateSizeHint(); } -void MultiLineEdit::resizeEvent(QResizeEvent *event) { - QTextEdit::resizeEvent(event); - updateSizeHint(); - updateScrollBars(); +void MultiLineEdit::setMaxHeight(int lines) +{ + if (lines == _maxHeight) + return; + + _maxHeight = lines; + updateSizeHint(); } -void MultiLineEdit::updateSizeHint() { - QFontMetrics fm(font()); - int minPixelHeight = fm.lineSpacing() * _minHeight; - int maxPixelHeight = fm.lineSpacing() * _maxHeight; - int scrollBarHeight = horizontalScrollBar()->isVisible() ? horizontalScrollBar()->height() : 0; +void MultiLineEdit::setScrollBarsEnabled(bool enable) +{ + if (_scrollBarsEnabled == enable) + return; - // use the style to determine a decent size - int h = qMin(qMax((int)document()->size().height() + scrollBarHeight, minPixelHeight), maxPixelHeight) + 2 * frameWidth(); - QStyleOptionFrameV2 opt; - opt.initFrom(this); - opt.rect = QRect(0, 0, 100, h); - opt.lineWidth = lineWidth(); - opt.midLineWidth = midLineWidth(); - opt.state |= QStyle::State_Sunken; - QSize s = style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(100, h).expandedTo(QApplication::globalStrut()), this); - if(s != _sizeHint) { - _sizeHint = s; - updateGeometry(); - } + _scrollBarsEnabled = enable; + updateScrollBars(); } -QSize MultiLineEdit::sizeHint() const { - if(!_sizeHint.isValid()) { - MultiLineEdit *that = const_cast(this); - that->updateSizeHint(); - } - return _sizeHint; +void MultiLineEdit::updateScrollBars() +{ + QFontMetrics fm(font()); + int _maxPixelHeight = fm.lineSpacing() * _maxHeight; + if (_scrollBarsEnabled && document()->size().height() > _maxPixelHeight) + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + else + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + if (!_scrollBarsEnabled || isSingleLine()) + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + else + setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); } -QSize MultiLineEdit::minimumSizeHint() const { - return sizeHint(); +void MultiLineEdit::resizeEvent(QResizeEvent* event) +{ + QTextEdit::resizeEvent(event); + updateSizeHint(); + updateScrollBars(); } -void MultiLineEdit::setSpellCheckEnabled(bool enable) { -#ifdef HAVE_KDE - setCheckSpellingEnabled(enable); -#else - Q_UNUSED(enable) +void MultiLineEdit::updateSizeHint() +{ + QFontMetrics fm(font()); + int minPixelHeight = fm.lineSpacing() * _minHeight; + int maxPixelHeight = fm.lineSpacing() * _maxHeight; + int scrollBarHeight = horizontalScrollBar()->isVisible() ? horizontalScrollBar()->height() : 0; + + // use the style to determine a decent size + int h = qMin(qMax((int)document()->size().height() + scrollBarHeight, minPixelHeight), maxPixelHeight) + 2 * frameWidth(); + + QStyleOptionFrame opt; + opt.initFrom(this); + opt.rect = QRect(0, 0, 100, h); + opt.lineWidth = lineWidth(); + opt.midLineWidth = midLineWidth(); + opt.state |= QStyle::State_Sunken; + QWidget* widget = this; +#ifdef Q_OS_MAC + widget = 0; #endif + QSize s = style()->sizeFromContents(QStyle::CT_LineEdit, &opt, QSize(100, h).expandedTo(QApplication::globalStrut()), widget); + if (s != _sizeHint) { + _sizeHint = s; + updateGeometry(); + } } -void MultiLineEdit::setWordWrapEnabled(bool enable) { - setLineWrapMode(enable? WidgetWidth : NoWrap); - updateSizeHint(); +QSize MultiLineEdit::sizeHint() const +{ + if (!_sizeHint.isValid()) { + auto* that = const_cast(this); + that->updateSizeHint(); + } + return _sizeHint; } -void MultiLineEdit::setPasteProtectionEnabled(bool enable, QWidget *) { - _pasteProtectionEnabled = enable; +QSize MultiLineEdit::minimumSizeHint() const +{ + return sizeHint(); } -void MultiLineEdit::historyMoveBack() { - addToHistory(convertHtmlToMircCodes(html()), true); +void MultiLineEdit::setEmacsMode(bool enable) +{ + _emacsMode = enable; +} - if(idx > 0) { - idx--; - showHistoryEntry(); - } +void MultiLineEdit::setPasteProtectionEnabled(bool enable, QWidget*) +{ + _pasteProtectionEnabled = enable; } -void MultiLineEdit::historyMoveForward() { - addToHistory(convertHtmlToMircCodes(html()), true); +void MultiLineEdit::historyMoveBack() +{ + addToHistory(convertRichtextToMircCodes(), true); - if(idx < history.count()) { - idx++; - if(idx < history.count() || tempHistory.contains(idx)) // tempHistory might have an entry for idx == history.count() + 1 - showHistoryEntry(); - else - reset(); // equals clear() in this case - } else { - addToHistory(convertHtmlToMircCodes(html())); - reset(); - } + if (_idx > 0) { + _idx--; + showHistoryEntry(); + } } -bool MultiLineEdit::addToHistory(const QString &text, bool temporary) { - if(text.isEmpty()) - return false; - - Q_ASSERT(0 <= idx && idx <= history.count()); +void MultiLineEdit::historyMoveForward() +{ + addToHistory(convertRichtextToMircCodes(), true); + + if (_idx < _history.count()) { + _idx++; + if (_idx < _history.count() || _tempHistory.contains(_idx)) // tempHistory might have an entry for idx == history.count() + 1 + showHistoryEntry(); + else + reset(); // equals clear() in this case + } + else { + addToHistory(convertRichtextToMircCodes()); + reset(); + } +} - if(temporary) { - // if an entry of the history is changed, we remember it and show it again at this - // position until a line was actually sent - // sent lines get appended to the history - if(history.isEmpty() || text != history[idx - (int)(idx == history.count())]) { - tempHistory[idx] = text; - return true; +bool MultiLineEdit::addToHistory(const QString& text, bool temporary) +{ + if (text.isEmpty()) + return false; + + Q_ASSERT(0 <= _idx && _idx <= _history.count()); + + if (temporary) { + // if an entry of the history is changed, we remember it and show it again at this + // position until a line was actually sent + // sent lines get appended to the history + if (_history.isEmpty() || text != _history[_idx - (int)(_idx == _history.count())]) { + _tempHistory[_idx] = text; + return true; + } } - } else { - if(history.isEmpty() || text != history.last()) { - history << text; - tempHistory.clear(); - return true; + else { + if (_history.isEmpty() || text != _history.last()) { + _history << text; + _tempHistory.clear(); + return true; + } } - } - return false; + return false; } -void MultiLineEdit::keyPressEvent(QKeyEvent *event) { - // Workaround the fact that Qt < 4.5 doesn't know InsertLineSeparator yet -#if QT_VERSION >= 0x040500 - if(event == QKeySequence::InsertLineSeparator) { -#else +bool MultiLineEdit::event(QEvent* e) +{ + // We need to make sure that global shortcuts aren't eaten + if (e->type() == QEvent::ShortcutOverride) { + auto* event = static_cast(e); + QKeySequence key = QKeySequence(event->key() | event->modifiers()); + foreach (QAction* action, GraphicalUi::actionCollection()->actions()) { + if (action->shortcuts().contains(key)) { + e->ignore(); + return false; + } + } + } -# ifdef Q_WS_MAC - if((event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && event->modifiers() & Qt::META) { -# else - if((event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) && event->modifiers() & Qt::SHIFT) { -# endif -#endif + return MultiLineEditParent::event(e); +} - if(_mode == SingleLine) { - event->accept(); - on_returnPressed(); - return; - } -#ifdef HAVE_KDE - KTextEdit::keyPressEvent(event); -#else - QTextEdit::keyPressEvent(event); -#endif - return; - } - - switch(event->key()) { - case Qt::Key_Up: - if(event->modifiers() & Qt::ShiftModifier) - break; - { - event->accept(); - if(!(event->modifiers() & Qt::ControlModifier)) { - int pos = textCursor().position(); - moveCursor(QTextCursor::Up); - if(pos == textCursor().position()) // already on top line -> history - historyMoveBack(); - } else - historyMoveBack(); - return; +void MultiLineEdit::keyPressEvent(QKeyEvent* event) +{ + if (event == QKeySequence::InsertLineSeparator) { + if (_mode == SingleLine) { + event->accept(); + on_returnPressed(); + return; + } + MultiLineEditParent::keyPressEvent(event); + return; } - case Qt::Key_Down: - if(event->modifiers() & Qt::ShiftModifier) - break; - { - event->accept(); - if(!(event->modifiers() & Qt::ControlModifier)) { - int pos = textCursor().position(); - moveCursor(QTextCursor::Down); - if(pos == textCursor().position()) // already on bottom line -> history - historyMoveForward(); - } else - historyMoveForward(); - return; - } + switch (event->key()) { + case Qt::Key_Up: + if (event->modifiers() & Qt::ShiftModifier) + break; + { + event->accept(); + if (!(event->modifiers() & Qt::ControlModifier)) { + int pos = textCursor().position(); + moveCursor(QTextCursor::Up); + if (pos == textCursor().position()) // already on top line -> history + historyMoveBack(); + } + else + historyMoveBack(); + return; + } + + case Qt::Key_Down: + if (event->modifiers() & Qt::ShiftModifier) + break; + { + event->accept(); + if (!(event->modifiers() & Qt::ControlModifier)) { + int pos = textCursor().position(); + moveCursor(QTextCursor::Down); + if (pos == textCursor().position()) // already on bottom line -> history + historyMoveForward(); + } + else + historyMoveForward(); + return; + } - case Qt::Key_Return: - case Qt::Key_Enter: - case Qt::Key_Select: - event->accept(); - on_returnPressed(); - return; + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Select: + event->accept(); + on_returnPressed(); + return; - // We don't want to have the tab key react even if no completer is installed - case Qt::Key_Tab: - event->accept(); - return; + // We don't want to have the tab key react even if no completer is installed + case Qt::Key_Tab: + event->accept(); + return; - default: - ; - } + default: + ; + } + if (_emacsMode) { + if (event->modifiers() & Qt::ControlModifier) { + switch (event->key()) { + // move + case Qt::Key_A: + moveCursor(QTextCursor::StartOfLine); + return; + case Qt::Key_E: + moveCursor(QTextCursor::EndOfLine); + return; + case Qt::Key_F: + moveCursor(QTextCursor::Right); + return; + case Qt::Key_B: + moveCursor(QTextCursor::Left); + return; + + // modify + case Qt::Key_Y: + paste(); + return; + case Qt::Key_K: + moveCursor(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); + cut(); + return; + + default: + break; + } + } + else if (event->modifiers() & Qt::MetaModifier || event->modifiers() & Qt::AltModifier) { + switch (event->key()) { + case Qt::Key_Right: + moveCursor(QTextCursor::WordRight); + return; + case Qt::Key_Left: + moveCursor(QTextCursor::WordLeft); + return; + case Qt::Key_F: + moveCursor(QTextCursor::WordRight); + return; + case Qt::Key_B: + moveCursor(QTextCursor::WordLeft); + return; + case Qt::Key_Less: + moveCursor(QTextCursor::Start); + return; + case Qt::Key_Greater: + moveCursor(QTextCursor::End); + return; + + // modify + case Qt::Key_D: + moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor); + cut(); + return; + + case Qt::Key_U: // uppercase word + moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor); + textCursor().insertText(textCursor().selectedText().toUpper()); + return; + + case Qt::Key_L: // lowercase word + moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor); + textCursor().insertText(textCursor().selectedText().toLower()); + return; + + case Qt::Key_C: { // capitalize word + moveCursor(QTextCursor::WordRight, QTextCursor::KeepAnchor); + QString const text = textCursor().selectedText(); + textCursor().insertText(text.left(1).toUpper() + text.mid(1).toLower()); + return; + } + + case Qt::Key_T: { // transpose words + moveCursor(QTextCursor::StartOfWord); + moveCursor(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + QString const word1 = textCursor().selectedText(); + textCursor().clearSelection(); + moveCursor(QTextCursor::WordRight); + moveCursor(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + QString const word2 = textCursor().selectedText(); + if (!word2.isEmpty() && !word1.isEmpty()) { + textCursor().insertText(word1); + moveCursor(QTextCursor::WordLeft); + moveCursor(QTextCursor::WordLeft); + moveCursor(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + textCursor().insertText(word2); + moveCursor(QTextCursor::WordRight); + moveCursor(QTextCursor::EndOfWord); + } + return; + } + + default: + break; + } + } + } #ifdef HAVE_KDE - KTextEdit::keyPressEvent(event); + KTextEdit::keyPressEvent(event); #else - QTextEdit::keyPressEvent(event); + QTextEdit::keyPressEvent(event); #endif } -QString MultiLineEdit::convertHtmlToMircCodes(const QString &text) { - QRegExp regexLines = QRegExp("(?:(.*)

\\n?)+", Qt::CaseInsensitive); - regexLines.setMinimal(true); - - QRegExp regexStyles = QRegExp("(?:(()(.*)))", Qt::CaseInsensitive); - regexStyles.setMinimal(true); - - QRegExp regexColors = QRegExp("((?:background-)?color):(#[0-9a-f]{6})", Qt::CaseInsensitive); - regexStyles.setMinimal(true); - - QStringList result; - int posLines = 0; - QString line, line2, styleText, style, content; - - while ((posLines = regexLines.indexIn(text, posLines)) != -1) { - line = line2 = regexLines.cap(1); - int posStyles = 0; - while ((posStyles = regexStyles.indexIn(line2, posStyles)) != -1) { - styleText = regexStyles.cap(1); - style = regexStyles.cap(2); - content = regexStyles.cap(3); - - if (style.contains("font-weight:600;")) { - content.prepend('\x02'); - content.append('\x02'); - } - if (style.contains("font-style:italic;")) { - content.prepend('\x1d'); - content.append('\x1d'); - } - if (style.contains("text-decoration: underline;")) { - content.prepend('\x1f'); - content.append('\x1f'); - } - if (style.contains("color:#")) { // we have either foreground or background color or both - int posColors = 0; - QString mircFgColor, mircBgColor; - while ((posColors = regexColors.indexIn(style, posColors)) != -1) { - QString colorType = regexColors.cap(1); - QString color = regexColors.cap(2); - - if (colorType == "color") - mircFgColor = mircColorMap.key(color); - - if (colorType == "background-color") - mircBgColor = mircColorMap.key(color); - - posColors += regexColors.matchedLength(); +QString MultiLineEdit::convertRichtextToMircCodes() +{ + bool underline, bold, italic, color; + QString mircText, mircFgColor, mircBgColor; + QTextCursor cursor = textCursor(); + QTextCursor peekcursor = textCursor(); + cursor.movePosition(QTextCursor::Start); + + underline = bold = italic = color = false; + + while (cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor)) { + if (cursor.selectedText() == QString(QChar(QChar::LineSeparator)) + || cursor.selectedText() == QString(QChar(QChar::ParagraphSeparator))) { + if (color) { + color = false; + mircText.append('\x03'); + } + if (underline) { + underline = false; + mircText.append('\x1f'); + } + if (italic) { + italic = false; + mircText.append('\x1d'); + } + if (bold) { + bold = false; + mircText.append('\x02'); + } + mircText.append('\n'); } - if (!mircBgColor.isEmpty()) - content.prepend("," + mircBgColor); - - // we need a fg color to be able to use a bg color - if (mircFgColor.isEmpty()) { - //FIXME try to use the current forecolor - mircFgColor = mircColorMap.key(textColor().name()); - if (mircFgColor.isEmpty()) - mircFgColor = "01"; //use black if the current foreground color can't be converted + else { + if (!bold && cursor.charFormat().font().bold()) { + bold = true; + mircText.append('\x02'); + } + if (!italic && cursor.charFormat().fontItalic()) { + italic = true; + mircText.append('\x1d'); + } + if (!underline && cursor.charFormat().fontUnderline()) { + underline = true; + mircText.append('\x1f'); + } + if (!color && (cursor.charFormat().foreground().isOpaque() || cursor.charFormat().background().isOpaque())) { + color = true; + mircText.append('\x03'); + mircFgColor = _mircColorMap.key(cursor.charFormat().foreground().color().name()); + mircBgColor = _mircColorMap.key(cursor.charFormat().background().color().name()); + + if (mircFgColor.isEmpty()) { + mircFgColor = "01"; // use black if the current foreground color can't be converted + } + + mircText.append(mircFgColor); + if (cursor.charFormat().background().isOpaque()) + mircText.append("," + mircBgColor); + } + + mircText.append(cursor.selectedText()); + + peekcursor.setPosition(cursor.position()); + peekcursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); + + if (mircCodesChanged(cursor, peekcursor)) { + if (color) { + color = false; + mircText.append('\x03'); + } + if (underline) { + underline = false; + mircText.append('\x1f'); + } + if (italic) { + italic = false; + mircText.append('\x1d'); + } + if (bold) { + bold = false; + mircText.append('\x02'); + } + } } - content.prepend(mircFgColor); - content.prepend('\x03'); - content.append('\x03'); - } - - line.replace(styleText, content); - posStyles += regexStyles.matchedLength(); + cursor.clearSelection(); } - // get rid of all remaining html tags - QRegExp regexTags = QRegExp("<.*>",Qt::CaseInsensitive); - regexTags.setMinimal(true); - line.replace(regexTags, ""); + if (color) + mircText.append('\x03'); + + if (underline) + mircText.append('\x1f'); + + if (italic) + mircText.append('\x1d'); - line.replace("&","&"); - line.replace("<","<"); - line.replace(">",">"); + if (bold) + mircText.append('\x02'); - result << line; - posLines += regexLines.matchedLength(); - } + return mircText; +} - return result.join("\n").replace("
", "\n"); +bool MultiLineEdit::mircCodesChanged(QTextCursor& cursor, QTextCursor& peekcursor) +{ + bool changed = false; + if (cursor.charFormat().font().bold() != peekcursor.charFormat().font().bold()) + changed = true; + if (cursor.charFormat().fontItalic() != peekcursor.charFormat().fontItalic()) + changed = true; + if (cursor.charFormat().fontUnderline() != peekcursor.charFormat().fontUnderline()) + changed = true; + if (cursor.charFormat().foreground().color() != peekcursor.charFormat().foreground().color()) + changed = true; + if (cursor.charFormat().background().color() != peekcursor.charFormat().background().color()) + changed = true; + return changed; } -QString MultiLineEdit::convertMircCodesToHtml(const QString &text) { - QStringList words; - QRegExp mircCode = QRegExp("(|||)", Qt::CaseSensitive); +QString MultiLineEdit::convertMircCodesToHtml(const QString& text) +{ + QStringList words; + QRegExp mircCode = QRegExp("(|||)", Qt::CaseSensitive); - int posLeft = 0; - int posRight = 0; + int posLeft = 0; + int posRight = 0; - for(;;) { - posRight = mircCode.indexIn(text, posLeft); + for (;;) { + posRight = mircCode.indexIn(text, posLeft); - if(posRight < 0) { - words << text.mid(posLeft); - break; // no more mirc color codes - } + if (posRight < 0) { + words << text.mid(posLeft); + break; // no more mirc color codes + } + + if (posLeft < posRight) { + words << text.mid(posLeft, posRight - posLeft); + posLeft = posRight; + } - if (posLeft < posRight) { - words << text.mid(posLeft, posRight - posLeft); - posLeft = posRight; + posRight = text.indexOf(mircCode.cap(), posRight + 1); + if (posRight == -1) { + words << text.mid(posLeft); + break; // unclosed color code; can't process + } + words << text.mid(posLeft, posRight + 1 - posLeft); + posLeft = posRight + 1; } - posRight = text.indexOf(mircCode.cap(), posRight + 1); - words << text.mid(posLeft, posRight + 1 - posLeft); - posLeft = posRight + 1; - } - - for (int i = 0; i < words.count(); i++) { - QString style; - if (words[i].contains('\x02')) { - style.append(" font-weight:600;"); - words[i].replace('\x02',""); - } - if (words[i].contains('\x1d')) { - style.append(" font-style:italic;"); - words[i].replace('\x1d',""); - } - if (words[i].contains('\x1f')) { - style.append(" text-decoration: underline;"); - words[i].replace('\x1f',""); - } - if (words[i].contains('\x03')) { - int pos = words[i].indexOf('\x03'); - int len = 3; - QString fg = words[i].mid(pos + 1,2); - QString bg; - if (words[i][pos+3] == ',') - bg = words[i].mid(pos+4,2); - - style.append(" color:"); - style.append(mircColorMap[fg]); - style.append(";"); - - if (!bg.isEmpty()) { - style.append(" background-color:"); - style.append(mircColorMap[bg]); - style.append(";"); - len = 6; + for (int i = 0; i < words.count(); i++) { + QString style; + if (words[i].contains('\x02')) { + style.append(" font-weight:600;"); + words[i].replace('\x02', ""); + } + if (words[i].contains('\x1d')) { + style.append(" font-style:italic;"); + words[i].replace('\x1d', ""); + } + if (words[i].contains('\x1f')) { + style.append(" text-decoration: underline;"); + words[i].replace('\x1f', ""); + } + if (words[i].contains('\x03')) { + int pos = words[i].indexOf('\x03'); + int len = 3; + QString fg = words[i].mid(pos + 1, 2); + QString bg; + if (words[i][pos + 3] == ',') + bg = words[i].mid(pos + 4, 2); + + style.append(" color:"); + style.append(_mircColorMap[fg]); + style.append(";"); + + if (!bg.isEmpty()) { + style.append(" background-color:"); + style.append(_mircColorMap[bg]); + style.append(";"); + len = 6; + } + words[i].replace(pos, len, ""); + words[i].replace('\x03', ""); + } + words[i].replace("&", "&"); + words[i].replace("<", "<"); + words[i].replace(">", ">"); + words[i].replace("\"", """); + if (style.isEmpty()) { + words[i] = "" + words[i] + ""; + } + else { + words[i] = "" + words[i] + ""; } - words[i].replace(pos, len, ""); - words[i].replace('\x03',""); - } - words[i].replace("&","&"); - words[i].replace("<", "<"); - words[i].replace(">", ">"); - if (style.isEmpty()) { - words[i] = "" + words[i] + ""; - } - else { - words[i] = "" + words[i] + ""; - } - } - return words.join(""); -} - -void MultiLineEdit::on_returnPressed() { - on_returnPressed(convertHtmlToMircCodes(html())); -} - -void MultiLineEdit::on_returnPressed(const QString & text) { - if(!text.isEmpty()) { - foreach(const QString &line, text.split('\n', QString::SkipEmptyParts)) { - if(line.isEmpty()) - continue; - addToHistory(line); - emit textEntered(line); } - reset(); - tempHistory.clear(); - } else { - emit noTextEntered(); - } -} - -void MultiLineEdit::on_textChanged() { - QString newText = text(); - newText.replace("\r\n", "\n"); - newText.replace('\r', '\n'); - if(_mode == SingleLine) { - if(!pasteProtectionEnabled()) - newText.replace('\n', ' '); - else if(newText.contains('\n')) { - QStringList lines = newText.split('\n', QString::SkipEmptyParts); - clear(); - - if(lines.count() >= 4) { - QString msg = tr("Do you really want to paste %n lines?", "", lines.count()); - msg += "

"; - for(int i = 0; i < 4; i++) { - msg += Qt::escape(lines[i].left(40)); - if(lines[i].count() > 40) - msg += "..."; - msg += "
"; + return words.join("").replace("\n", "
"); +} + +void MultiLineEdit::on_returnPressed() +{ + on_returnPressed(convertRichtextToMircCodes()); +} + +void MultiLineEdit::on_returnPressed(QString text) +{ + if (_completionSpace && text.endsWith(" ")) { + text.chop(1); + } + + if (!text.isEmpty()) { + foreach (const QString& line, text.split('\n', QString::SkipEmptyParts)) { + if (line.isEmpty()) + continue; + addToHistory(line); + emit textEntered(line); } - msg += "...

"; - QMessageBox question(QMessageBox::NoIcon, tr("Paste Protection"), msg, QMessageBox::Yes|QMessageBox::No); - question.setDefaultButton(QMessageBox::No); -#ifdef Q_WS_MAC - question.setWindowFlags(question.windowFlags() | Qt::Sheet); + reset(); + _tempHistory.clear(); + } + else { + emit noTextEntered(); + } +} + +void MultiLineEdit::on_textChanged() +{ + _completionSpace = qMax(_completionSpace - 1, 0); + + QString newText = text(); + newText.replace("\r\n", "\n"); + newText.replace('\r', '\n'); + if (_mode == SingleLine) { + if (!pasteProtectionEnabled()) + newText.replace('\n', ' '); + else if (newText.contains('\n')) { + QStringList lines = newText.split('\n', QString::SkipEmptyParts); + clear(); + + if (lines.count() >= 4) { + QString msg = tr("Do you really want to paste %n line(s)?", "", lines.count()); + msg += "

"; + for (int i = 0; i < 4; i++) { + msg += lines[i].left(40).toHtmlEscaped(); + if (lines[i].count() > 40) + msg += "..."; + msg += "
"; + } + msg += "...

"; + QMessageBox question(QMessageBox::NoIcon, tr("Paste Protection"), msg, QMessageBox::Yes | QMessageBox::No); + question.setDefaultButton(QMessageBox::No); +#ifdef Q_OS_MAC + question.setWindowFlags(question.windowFlags() | Qt::Sheet); #endif - if(question.exec() != QMessageBox::Yes) - return; - } + if (question.exec() != QMessageBox::Yes) + return; + } + + foreach (QString line, lines) { + clear(); + insert(line); + on_returnPressed(); + } + } + } - foreach(QString line, lines) { - clear(); - insert(line); - on_returnPressed(); - } + _singleLine = (newText.indexOf('\n') < 0); + + if (document()->size().height() != _lastDocumentHeight) { + _lastDocumentHeight = document()->size().height(); + on_documentHeightChanged(_lastDocumentHeight); } - } - - _singleLine = (newText.indexOf('\n') < 0); - - if(document()->size().height() != _lastDocumentHeight) { - _lastDocumentHeight = document()->size().height(); - on_documentHeightChanged(_lastDocumentHeight); - } - updateSizeHint(); - ensureCursorVisible(); -} - -void MultiLineEdit::on_documentHeightChanged(qreal) { - updateScrollBars(); -} - -void MultiLineEdit::reset() { - // every time the MultiLineEdit is cleared we also reset history index - idx = history.count(); - clear(); - QTextBlockFormat format = textCursor().blockFormat(); - format.setLeftMargin(leftMargin); // we want a little space between the frame and the contents - textCursor().setBlockFormat(format); - updateScrollBars(); -} - -void MultiLineEdit::showHistoryEntry() { - // if the user changed the history, display the changed line - setHtml(convertMircCodesToHtml(tempHistory.contains(idx) ? tempHistory[idx] : history[idx])); - //setPlainText(tempHistory.contains(idx) ? tempHistory[idx] : history[idx]); - QTextCursor cursor = textCursor(); - QTextBlockFormat format = cursor.blockFormat(); - format.setLeftMargin(leftMargin); // we want a little space between the frame and the contents - cursor.setBlockFormat(format); - cursor.movePosition(QTextCursor::End); - setTextCursor(cursor); - updateScrollBars(); + updateSizeHint(); + ensureCursorVisible(); +} + +void MultiLineEdit::on_documentHeightChanged(qreal) +{ + updateScrollBars(); +} + +void MultiLineEdit::reset() +{ + // every time the MultiLineEdit is cleared we also reset history index + _idx = _history.count(); + clear(); + QTextBlockFormat format = textCursor().blockFormat(); + format.setLeftMargin(leftMargin); // we want a little space between the frame and the contents + textCursor().setBlockFormat(format); + updateScrollBars(); +} + +void MultiLineEdit::showHistoryEntry() +{ + // if the user changed the history, display the changed line + setHtml(convertMircCodesToHtml(_tempHistory.contains(_idx) ? _tempHistory[_idx] : _history[_idx])); + QTextCursor cursor = textCursor(); + QTextBlockFormat format = cursor.blockFormat(); + format.setLeftMargin(leftMargin); // we want a little space between the frame and the contents + cursor.setBlockFormat(format); + cursor.movePosition(QTextCursor::End); + setTextCursor(cursor); + updateScrollBars(); +} + +void MultiLineEdit::addCompletionSpace() +{ + // Inserting the space emits textChanged, which should not disable removal + _completionSpace = 2; + insertPlainText(" "); }