From f047a4f7add72ef7e32ad33f884e62a5773ecc72 Mon Sep 17 00:00:00 2001 From: Manuel Nickschas Date: Sun, 16 Aug 2009 12:58:07 +0200 Subject: [PATCH] Introduce multi-line editing for the inputline This introduces the new MultiLineEdit that basically enhances the old InputLine widget by being able to expand to show more than one line. All non-editing related things have been moved out of the MultiLineEdit to ease reuse in other widgets. For example, InputWidget-specific keypresses are now handled in InputWidget rather than MultiLineEdit. Closes #209, closes #213, fixes #386. Thanks to Squider for input (I saw your patch too late, but I did refactor things anyway...) --- src/qtui/bufferwidget.cpp | 4 +- src/qtui/inputwidget.cpp | 47 ++- src/qtui/inputwidget.h | 8 +- src/qtui/mainwin.cpp | 3 +- src/qtui/ui/inputwidget.ui | 57 ++-- src/uisupport/CMakeLists.txt | 4 +- src/uisupport/inputline.cpp | 267 --------------- src/uisupport/multilineedit.cpp | 310 ++++++++++++++++++ .../{inputline.h => multilineedit.h} | 64 ++-- src/uisupport/tabcompleter.cpp | 68 ++-- src/uisupport/tabcompleter.h | 16 +- 11 files changed, 475 insertions(+), 373 deletions(-) delete mode 100644 src/uisupport/inputline.cpp create mode 100644 src/uisupport/multilineedit.cpp rename src/uisupport/{inputline.h => multilineedit.h} (73%) diff --git a/src/qtui/bufferwidget.cpp b/src/qtui/bufferwidget.cpp index e771e7d3..a58723da 100644 --- a/src/qtui/bufferwidget.cpp +++ b/src/qtui/bufferwidget.cpp @@ -31,7 +31,7 @@ #include "chatviewsearchcontroller.h" #include "client.h" #include "iconloader.h" -#include "inputline.h" +#include "multilineedit.h" #include "qtui.h" #include "settings.h" @@ -163,7 +163,7 @@ bool BufferWidget::eventFilter(QObject *watched, QEvent *event) { // Intercept copy key presses if(keyEvent == QKeySequence::Copy) { - InputLine *inputLine = qobject_cast(watched); + MultiLineEdit *inputLine = qobject_cast(watched); if(!inputLine) return false; if(inputLine->hasSelectedText()) diff --git a/src/qtui/inputwidget.cpp b/src/qtui/inputwidget.cpp index 737387fb..13833192 100644 --- a/src/qtui/inputwidget.cpp +++ b/src/qtui/inputwidget.cpp @@ -22,6 +22,7 @@ #include "action.h" #include "actioncollection.h" +#include "bufferview.h" #include "client.h" #include "iconloader.h" #include "ircuser.h" @@ -29,20 +30,32 @@ #include "networkmodel.h" #include "qtui.h" #include "qtuisettings.h" +#include "tabcompleter.h" InputWidget::InputWidget(QWidget *parent) : AbstractItemView(parent), _networkId(0) { ui.setupUi(this); - connect(ui.inputEdit, SIGNAL(sendText(QString)), this, SLOT(sendText(QString))); + connect(ui.inputEdit, SIGNAL(textEntered(QString)), this, SLOT(sendText(QString))); connect(ui.ownNick, SIGNAL(activated(QString)), this, SLOT(changeNick(QString))); + + layout()->setAlignment(ui.ownNick, Qt::AlignBottom); + layout()->setAlignment(ui.inputEdit, Qt::AlignBottom); + setFocusProxy(ui.inputEdit); + ui.ownNick->setFocusProxy(ui.inputEdit); ui.ownNick->setSizeAdjustPolicy(QComboBox::AdjustToContents); ui.ownNick->installEventFilter(new MouseWheelFilter(this)); ui.inputEdit->installEventFilter(new JumpKeyHandler(this)); + ui.inputEdit->setMinHeight(1); + ui.inputEdit->setMaxHeight(5); + ui.inputEdit->setMode(MultiLineEdit::MultiLine); + + new TabCompleter(ui.inputEdit); + QtUiStyleSettings s("Fonts"); s.notify("InputLine", this, SLOT(setCustomFont(QVariant))); setCustomFont(s.value("InputLine", QFont())); @@ -65,6 +78,38 @@ void InputWidget::setCustomFont(const QVariant &v) { ui.inputEdit->setCustomFont(font); } +bool InputWidget::eventFilter(QObject *watched, QEvent *event) { + if(event->type() != QEvent::KeyPress) + return false; + + // keys from BufferView should be sent to (and focus) the input line + BufferView *view = qobject_cast(watched); + if(view) { + QKeyEvent *keyEvent = static_cast(event); + if(keyEvent->text().length() == 1 && !(keyEvent->modifiers() & (Qt::ControlModifier ^ Qt::AltModifier)) ) { // normal key press + QChar c = keyEvent->text().at(0); + if(c.isLetterOrNumber() || c.isSpace() || c.isPunct() || c.isSymbol()) { + setFocus(); + QCoreApplication::sendEvent(inputLine(), keyEvent); + return true; + } else + return false; + } + } + return false; +} + +void InputWidget::keyPressEvent(QKeyEvent * event) { + if(event->matches(QKeySequence::Find)) { + QAction *act = GraphicalUi::actionCollection()->action("ToggleSearchBar"); + if(act) { + act->toggle(); + return; + } + } + AbstractItemView::keyPressEvent(event); +} + void InputWidget::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { Q_UNUSED(previous) NetworkId networkId = current.data(NetworkModel::NetworkIdRole).value(); diff --git a/src/qtui/inputwidget.h b/src/qtui/inputwidget.h index fae3d7f0..d60ee340 100644 --- a/src/qtui/inputwidget.h +++ b/src/qtui/inputwidget.h @@ -29,7 +29,7 @@ #include "identity.h" #include "network.h" -class InputLine; +class MultiLineEdit; class InputWidget : public AbstractItemView { Q_OBJECT @@ -40,7 +40,11 @@ public: const Network *currentNetwork() const; - inline InputLine* inputLine() const { return ui.inputEdit; } + inline MultiLineEdit* inputLine() const { return ui.inputEdit; } + +protected: + virtual bool eventFilter(QObject *watched, QEvent *event); + virtual void keyPressEvent(QKeyEvent * event); protected slots: virtual void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); diff --git a/src/qtui/mainwin.cpp b/src/qtui/mainwin.cpp index 59ad97fa..07d2baeb 100644 --- a/src/qtui/mainwin.cpp +++ b/src/qtui/mainwin.cpp @@ -63,7 +63,6 @@ #include "flatproxymodel.h" #include "iconloader.h" #include "inputwidget.h" -#include "inputline.h" #include "irclistmodel.h" #include "ircconnectionwizard.h" #include "jumpkeyhandler.h" @@ -411,7 +410,7 @@ void MainWin::addBufferView(ClientBufferViewConfig *config) { //create the view and initialize it's filter BufferView *view = new BufferView(dock); view->setFilteredModel(Client::bufferModel(), config); - view->installEventFilter(_inputWidget->inputLine()); // for key presses + view->installEventFilter(_inputWidget); // for key presses view->show(); Client::bufferModel()->synchronizeView(view); diff --git a/src/qtui/ui/inputwidget.ui b/src/qtui/ui/inputwidget.ui index 3fc5e9b4..a6571662 100644 --- a/src/qtui/ui/inputwidget.ui +++ b/src/qtui/ui/inputwidget.ui @@ -6,8 +6,8 @@ 0 0 - 761 - 194 + 585 + 52 @@ -19,46 +19,39 @@ Form - - - -1 - + 0 - - - - - - - - - 0 - 0 - - - - Qt::ScrollBarAlwaysOff - - - Qt::ScrollBarAlwaysOff - - - QTextEdit::NoWrap - - - - + + + + + + + 0 + 0 + + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAsNeeded + + + QTextEdit::NoWrap + + - InputLine + MultiLineEdit QTextEdit -
inputline.h
+
multilineedit.h
diff --git a/src/uisupport/CMakeLists.txt b/src/uisupport/CMakeLists.txt index f366be51..549cee2a 100644 --- a/src/uisupport/CMakeLists.txt +++ b/src/uisupport/CMakeLists.txt @@ -22,7 +22,7 @@ set(SOURCES graphicalui.cpp icon.cpp iconloader.cpp - inputline.cpp + multilineedit.cpp networkmodelcontroller.cpp nickview.cpp nickviewfilter.cpp @@ -52,7 +52,7 @@ set(MOC_HDRS fontselector.h graphicalui.h iconloader.h - inputline.h + multilineedit.h networkmodelcontroller.h nickview.h nickviewfilter.h diff --git a/src/uisupport/inputline.cpp b/src/uisupport/inputline.cpp deleted file mode 100644 index 501ee605..00000000 --- a/src/uisupport/inputline.cpp +++ /dev/null @@ -1,267 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2005/06 by the Quassel Project * - * devel@quassel-irc.org * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) version 3. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * 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. * - ***************************************************************************/ - -#include -#include -#include - -#include "bufferview.h" -#include "graphicalui.h" -#include "inputline.h" -#include "tabcompleter.h" - -const int leftMargin = 3; - -InputLine::InputLine(QWidget *parent) - : -#ifdef HAVE_KDE - KTextEdit(parent), -#else - QTextEdit(parent), -#endif - idx(0), - tabCompleter(new TabCompleter(this)) -{ - // Make the QTextEdit look like a QLineEdit -#if QT_VERSION >= 0x040500 - document()->setDocumentMargin(0); // new in Qt 4.5 and we really don't want it here -#endif - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setAcceptRichText(false); - setLineWrapMode(NoWrap); -#ifdef HAVE_KDE - enableFindReplace(false); -#endif - resetLine(); - - connect(this, SIGNAL(textChanged()), this, SLOT(on_textChanged())); - connect(this, SIGNAL(returnPressed()), this, SLOT(on_returnPressed())); - connect(this, SIGNAL(textChanged(QString)), this, SLOT(on_textChanged(QString))); -} - -InputLine::~InputLine() { -} - -void InputLine::setCustomFont(const QFont &font) { - setFont(font); -} - -QSize InputLine::sizeHint() const { - // use the style to determine a decent size - QFontMetrics fm(font()); - int h = fm.lineSpacing() + 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); - return s; -} - -QSize InputLine::minimumSizeHint() const { - return sizeHint(); -} - -bool InputLine::eventFilter(QObject *watched, QEvent *event) { - if(event->type() != QEvent::KeyPress) - return false; - - // keys from BufferView should be sent to (and focus) the input line - BufferView *view = qobject_cast(watched); - if(view) { - QKeyEvent *keyEvent = static_cast(event); - if(keyEvent->text().length() == 1 && !(keyEvent->modifiers() & (Qt::ControlModifier ^ Qt::AltModifier)) ) { // normal key press - QChar c = keyEvent->text().at(0); - if(c.isLetterOrNumber() || c.isSpace() || c.isPunct() || c.isSymbol()) { - setFocus(); - keyPressEvent(keyEvent); - return true; - } else - return false; - } - } - return false; -} - -void InputLine::keyPressEvent(QKeyEvent * event) { - if(event->matches(QKeySequence::Find)) { - QAction *act = GraphicalUi::actionCollection()->action("ToggleSearchBar"); - if(act) { - act->toggle(); - event->accept(); - return; - } - } - - switch(event->key()) { - case Qt::Key_Up: - event->accept(); - - addToHistory(text(), true); - - if(idx > 0) { - idx--; - showHistoryEntry(); - } - - break; - - case Qt::Key_Down: - event->accept(); - - addToHistory(text(), 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 - resetLine(); // equals clear() in this case - } else { - addToHistory(text()); - resetLine(); - } - - break; - - case Qt::Key_Return: - case Qt::Key_Enter: - case Qt::Key_Select: - event->accept(); - emit returnPressed(); - break; - - default: - QTextEdit::keyPressEvent(event); - } -} - -bool InputLine::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; - } - } - return false; -} - -void InputLine::on_returnPressed() { - if(!text().isEmpty()) { - addToHistory(text()); - emit sendText(text()); - resetLine(); - } -} - -void InputLine::on_textChanged(QString newText) { - QStringList lineSeparators; - lineSeparators << QString("\r\n") - << QString('\n') - << QString('\r'); - - QString lineSep; - foreach(QString separator, lineSeparators) { - if(newText.contains(separator)) { - lineSep = separator; - break; - } - } - - if(lineSep.isEmpty()) - return; - - QStringList lines = newText.split(lineSep, QString::SkipEmptyParts); - - if(lines.count() >= 4) { - QString msg = tr("Do you really want to paste %n lines?", "", lines.count()); - msg += "

"; - for(int i = 0; i < 3; i++) { - msg += lines[i].left(40); - 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_WS_MAC - question.setWindowFlags(question.windowFlags() | Qt::Sheet); // Qt::Sheet is not ignored on other platforms as it should :/ -#endif - if(question.exec() == QMessageBox::No) { - clear(); - return; - } - } - - foreach(QString line, lines) { - if(!line.isEmpty()) { - resetLine(); - insert(line); - emit returnPressed(); - } - } - -// if(newText.contains(lineSep)) { -// clear(); -// QString line = newText.section(lineSep, 0, 0); -// QString remainder = newText.section(lineSep, 1); -// insert(line); -// emit returnPressed(); -// insert(remainder); -// } -} - -void InputLine::resetLine() { - // every time the InputLine 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); -} - -void InputLine::showHistoryEntry() { - // if the user changed the history, display the changed line - 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); -} diff --git a/src/uisupport/multilineedit.cpp b/src/uisupport/multilineedit.cpp new file mode 100644 index 00000000..1a18f328 --- /dev/null +++ b/src/uisupport/multilineedit.cpp @@ -0,0 +1,310 @@ +/*************************************************************************** + * Copyright (C) 2005/06 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * 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. * + ***************************************************************************/ + +#include +#include +#include +#include + +#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), + _wrapMode(QTextOption::NoWrap), + _numLines(1), + _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 + + setAcceptRichText(false); + setWordWrapMode(QTextOption::NoWrap); +#ifdef HAVE_KDE + enableFindReplace(false); +#endif + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + setMode(SingleLine); + + reset(); + + connect(this, SIGNAL(textChanged()), this, SLOT(on_textChanged())); +} + +MultiLineEdit::~MultiLineEdit() { +} + +void MultiLineEdit::setCustomFont(const QFont &font) { + setFont(font); + updateGeometry(); +} + +void MultiLineEdit::setMode(Mode mode) { + if(mode == _mode) + return; + + _mode = mode; +} + +void MultiLineEdit::setWrapMode(QTextOption::WrapMode wrapMode) { + if(_wrapMode == wrapMode) + return; + + _wrapMode = wrapMode; + setWordWrapMode(wrapMode); +} + +void MultiLineEdit::setMinHeight(int lines) { + if(lines == _minHeight) + return; + + _minHeight = lines; + updateGeometry(); +} + +void MultiLineEdit::setMaxHeight(int lines) { + if(lines == _maxHeight) + return; + + _maxHeight = lines; + updateGeometry(); +} + +void MultiLineEdit::enableScrollBars(bool enable) { + if(_scrollBarsEnabled == enable) + return; + + _scrollBarsEnabled = enable; + if(enable && numLines() > 1) { + // the vertical scrollbar must be enabled/disabled manually; + // ScrollBarAsNeeded leads to flicker because of the dynamic widget resize + setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + } else { + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } + updateScrollBars(); +} + +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::resizeEvent(QResizeEvent *event) { + updateScrollBars(); + QTextEdit::resizeEvent(event); +} + +QSize MultiLineEdit::sizeHint() const { + QFontMetrics fm(font()); + int _minPixelHeight = fm.lineSpacing() * _minHeight; + int _maxPixelHeight = fm.lineSpacing() * _maxHeight; + + // use the style to determine a decent size + int h = qMin(qMax((int)document()->size().height(), _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); + return s; +} + +QSize MultiLineEdit::minimumSizeHint() const { + return sizeHint(); +} + +void MultiLineEdit::historyMoveBack() { + addToHistory(text(), true); + + if(idx > 0) { + idx--; + showHistoryEntry(); + } +} + +void MultiLineEdit::historyMoveForward() { + addToHistory(text(), 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 + } +} + +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; + } + } + return false; +} + +void MultiLineEdit::keyPressEvent(QKeyEvent *event) { + if(event == QKeySequence::InsertLineSeparator) { + if(_mode == SingleLine) + return; +#ifdef HAVE_KDE + KTextEdit::keyPressEvent(event); +#else + QTextEdit::keyPressEvent(event); +#endif + return; + } + + switch(event->key()) { + case Qt::Key_Up: { + 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(); + break; + } + + case Qt::Key_Down: { + 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(); + break; + } + + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Select: + event->accept(); + on_returnPressed(); + break; + + // We don't want to have the tab key react if no completer is installed + case Qt::Key_Tab: + event->accept(); + break; + + default: +#ifdef HAVE_KDE + KTextEdit::keyPressEvent(event); +#else + QTextEdit::keyPressEvent(event); +#endif + } +} + +void MultiLineEdit::on_returnPressed() { + if(!text().isEmpty()) { + foreach(const QString &line, text().split('\n', QString::SkipEmptyParts)) { + if(line.isEmpty()) + continue; + addToHistory(line); + emit textEntered(line); + } + reset(); + tempHistory.clear(); + } +} + +void MultiLineEdit::on_textChanged() { + QString newText = text(); + newText.replace("\r\n", "\n"); + newText.replace('\r', '\n'); + if(_mode == SingleLine) + newText.replace('\n', ' '); + + if(document()->size().height() != _lastDocumentHeight) { + _lastDocumentHeight = document()->size().height(); + on_documentHeightChanged(_lastDocumentHeight); + } + updateGeometry(); +} + +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); +} + +void MultiLineEdit::showHistoryEntry() { + // if the user changed the history, display the changed line + 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); +} diff --git a/src/uisupport/inputline.h b/src/uisupport/multilineedit.h similarity index 73% rename from src/uisupport/inputline.h rename to src/uisupport/multilineedit.h index 6bbfee2f..13485924 100644 --- a/src/uisupport/inputline.h +++ b/src/uisupport/multilineedit.h @@ -18,20 +18,21 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -#ifndef INPUTLINE_H_ -#define INPUTLINE_H_ +#ifndef MULTILINEEDIT_H_ +#define MULTILINEEDIT_H_ -#include #include +#include #include #ifdef HAVE_KDE -#include +# include #endif +class QKeyEvent; class TabCompleter; -class InputLine : public +class MultiLineEdit : public #ifdef HAVE_KDE KTextEdit #else @@ -41,8 +42,13 @@ class InputLine : public Q_OBJECT public: - InputLine(QWidget *parent = 0); - ~InputLine(); + enum Mode { + SingleLine, + MultiLine + }; + + MultiLineEdit(QWidget *parent = 0); + ~MultiLineEdit(); void setCustomFont(const QFont &); // should be used instead setFont(), so we can set our size correctly @@ -56,37 +62,49 @@ public: virtual QSize sizeHint() const; virtual QSize minimumSizeHint() const; +public slots: + void setMode(Mode mode); + void setWrapMode(QTextOption::WrapMode = QTextOption::NoWrap); + void setMinHeight(int numLines); + void setMaxHeight(int numLines); + void enableScrollBars(bool enable = true); + +signals: + void textEntered(const QString &text); + protected: virtual void keyPressEvent(QKeyEvent * event); - virtual bool eventFilter(QObject *watched, QEvent *event); + virtual void resizeEvent(QResizeEvent *event); private slots: void on_returnPressed(); - void on_textChanged(QString newText); - - // Needed to emulate the signal that QLineEdit has - inline void on_textChanged() { emit textChanged(toPlainText()); }; + void on_textChanged(); + void on_documentHeightChanged(qreal height); bool addToHistory(const QString &text, bool temporary = false); - -signals: - void sendText(QString text); - - // QTextEdit does not provide this signal, so we manually emit it in keyPressEvent() - void returnPressed(); - void textChanged(QString newText); + void historyMoveForward(); + void historyMoveBack(); private: QStringList history; QHash tempHistory; qint32 idx; - TabCompleter *tabCompleter; + Mode _mode; + QTextOption::WrapMode _wrapMode; + int _numLines; + int _minHeight; + int _maxHeight; + bool _scrollBarsEnabled; - int bindModifier; - int jumpModifier; + QSize _sizeHint; + qreal _lastDocumentHeight; - void resetLine(); + void reset(); void showHistoryEntry(); + void updateScrollBars(); + + inline int numLines() const { return _numLines; } + void setNumLines(int); }; #endif diff --git a/src/uisupport/tabcompleter.cpp b/src/uisupport/tabcompleter.cpp index 2b60cdd6..b9b6eb14 100644 --- a/src/uisupport/tabcompleter.cpp +++ b/src/uisupport/tabcompleter.cpp @@ -20,13 +20,13 @@ #include "tabcompleter.h" -#include "inputline.h" -#include "client.h" #include "buffermodel.h" -#include "networkmodel.h" -#include "network.h" +#include "client.h" #include "ircchannel.h" #include "ircuser.h" +#include "multilineedit.h" +#include "network.h" +#include "networkmodel.h" #include "uisettings.h" #include @@ -34,19 +34,19 @@ const Network *TabCompleter::_currentNetwork; BufferId TabCompleter::_currentBufferId; -TabCompleter::TabCompleter(InputLine *inputLine_) - : QObject(inputLine_), - inputLine(inputLine_), - enabled(false), - nickSuffix(": ") +TabCompleter::TabCompleter(MultiLineEdit *_lineEdit) + : QObject(_lineEdit), + _lineEdit(_lineEdit), + _enabled(false), + _nickSuffix(": ") { - inputLine->installEventFilter(this); + _lineEdit->installEventFilter(this); } void TabCompleter::buildCompletionList() { // ensure a safe state in case we return early. - completionMap.clear(); - nextCompletion = completionMap.begin(); + _completionMap.clear(); + _nextCompletion = _completionMap.begin(); // this is the first time tab is pressed -> build up the completion list and it's iterator QModelIndex currentIndex = Client::bufferModel()->currentIndex(); @@ -61,7 +61,7 @@ void TabCompleter::buildCompletionList() { if(!_currentNetwork) return; - QString tabAbbrev = inputLine->text().left(inputLine->cursorPosition()).section(' ',-1,-1); + QString tabAbbrev = _lineEdit->text().left(_lineEdit->cursorPosition()).section(' ',-1,-1); QRegExp regex(QString("^[^a-zA-Z]*").append(QRegExp::escape(tabAbbrev)), Qt::CaseInsensitive); switch(static_cast(currentIndex.data(NetworkModel::BufferTypeRole).toInt())) { @@ -72,68 +72,68 @@ void TabCompleter::buildCompletionList() { return; foreach(IrcUser *ircUser, channel->ircUsers()) { if(regex.indexIn(ircUser->nick()) > -1) - completionMap[ircUser->nick().toLower()] = ircUser->nick(); + _completionMap[ircUser->nick().toLower()] = ircUser->nick(); } } break; case BufferInfo::QueryBuffer: if(regex.indexIn(bufferName) > -1) - completionMap[bufferName.toLower()] = bufferName; + _completionMap[bufferName.toLower()] = bufferName; case BufferInfo::StatusBuffer: if(!_currentNetwork->myNick().isEmpty() && regex.indexIn(_currentNetwork->myNick()) > -1) - completionMap[_currentNetwork->myNick().toLower()] = _currentNetwork->myNick(); + _completionMap[_currentNetwork->myNick().toLower()] = _currentNetwork->myNick(); break; default: return; } - nextCompletion = completionMap.begin(); - lastCompletionLength = tabAbbrev.length(); + _nextCompletion = _completionMap.begin(); + _lastCompletionLength = tabAbbrev.length(); } void TabCompleter::complete() { TabCompletionSettings s; - nickSuffix = s.completionSuffix(); + _nickSuffix = s.completionSuffix(); - if(!enabled) { + if(!_enabled) { buildCompletionList(); - enabled = true; + _enabled = true; } - if (nextCompletion != completionMap.end()) { + if (_nextCompletion != _completionMap.end()) { // clear previous completion - for (int i = 0; i < lastCompletionLength; i++) { - inputLine->backspace(); + for (int i = 0; i < _lastCompletionLength; i++) { + _lineEdit->backspace(); } // insert completion - inputLine->insert(*nextCompletion); + _lineEdit->insert(*_nextCompletion); // remember charcount to delete next time and advance to next completion - lastCompletionLength = nextCompletion->length(); - nextCompletion++; + _lastCompletionLength = _nextCompletion->length(); + _nextCompletion++; // we're completing the first word of the line - if(inputLine->cursorPosition() == lastCompletionLength) { - inputLine->insert(nickSuffix); - lastCompletionLength += nickSuffix.length(); + if(_lineEdit->cursorPosition() == _lastCompletionLength) { + _lineEdit->insert(_nickSuffix); + _lastCompletionLength += _nickSuffix.length(); } // we're at the end of the list -> start over again } else { - if(!completionMap.isEmpty()) { - nextCompletion = completionMap.begin(); + if(!_completionMap.isEmpty()) { + _nextCompletion = _completionMap.begin(); complete(); } } } void TabCompleter::reset() { - enabled = false; + _enabled = false; } bool TabCompleter::eventFilter(QObject *obj, QEvent *event) { - if(obj != inputLine || event->type() != QEvent::KeyPress) + if(obj != _lineEdit || event->type() != QEvent::KeyPress) return QObject::eventFilter(obj, event); QKeyEvent *keyEvent = static_cast(event); diff --git a/src/uisupport/tabcompleter.h b/src/uisupport/tabcompleter.h index eb066e75..d249943e 100644 --- a/src/uisupport/tabcompleter.h +++ b/src/uisupport/tabcompleter.h @@ -27,7 +27,7 @@ #include "types.h" -class InputLine; +class MultiLineEdit; class IrcUser; class Network; @@ -35,7 +35,7 @@ class TabCompleter : public QObject { Q_OBJECT public: - TabCompleter(InputLine *inputLine_); + explicit TabCompleter(MultiLineEdit *inputLine_); void reset(); void complete(); @@ -49,18 +49,18 @@ private: QString nick; }; - QPointer inputLine; - bool enabled; - QString nickSuffix; + QPointer _lineEdit; + bool _enabled; + QString _nickSuffix; static const Network *_currentNetwork; static BufferId _currentBufferId; - QMap completionMap; + QMap _completionMap; // QStringList completionTemplates; - QMap::Iterator nextCompletion; - int lastCompletionLength; + QMap::Iterator _nextCompletion; + int _lastCompletionLength; void buildCompletionList(); -- 2.20.1