Introduce multi-line editing for the inputline
authorManuel Nickschas <sputnick@quassel-irc.org>
Sun, 16 Aug 2009 10:58:07 +0000 (12:58 +0200)
committerManuel Nickschas <sputnick@quassel-irc.org>
Sun, 16 Aug 2009 11:03:28 +0000 (13:03 +0200)
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
src/qtui/inputwidget.cpp
src/qtui/inputwidget.h
src/qtui/mainwin.cpp
src/qtui/ui/inputwidget.ui
src/uisupport/CMakeLists.txt
src/uisupport/inputline.cpp [deleted file]
src/uisupport/multilineedit.cpp [new file with mode: 0644]
src/uisupport/multilineedit.h [moved from src/uisupport/inputline.h with 73% similarity]
src/uisupport/tabcompleter.cpp
src/uisupport/tabcompleter.h

index e771e7d..a58723d 100644 (file)
@@ -31,7 +31,7 @@
 #include "chatviewsearchcontroller.h"
 #include "client.h"
 #include "iconloader.h"
 #include "chatviewsearchcontroller.h"
 #include "client.h"
 #include "iconloader.h"
-#include "inputline.h"
+#include "multilineedit.h"
 #include "qtui.h"
 #include "settings.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) {
 
   // Intercept copy key presses
   if(keyEvent == QKeySequence::Copy) {
-    InputLine *inputLine = qobject_cast<InputLine *>(watched);
+    MultiLineEdit *inputLine = qobject_cast<MultiLineEdit *>(watched);
     if(!inputLine)
       return false;
     if(inputLine->hasSelectedText())
     if(!inputLine)
       return false;
     if(inputLine->hasSelectedText())
index 737387f..1383319 100644 (file)
@@ -22,6 +22,7 @@
 
 #include "action.h"
 #include "actioncollection.h"
 
 #include "action.h"
 #include "actioncollection.h"
+#include "bufferview.h"
 #include "client.h"
 #include "iconloader.h"
 #include "ircuser.h"
 #include "client.h"
 #include "iconloader.h"
 #include "ircuser.h"
 #include "networkmodel.h"
 #include "qtui.h"
 #include "qtuisettings.h"
 #include "networkmodel.h"
 #include "qtui.h"
 #include "qtuisettings.h"
+#include "tabcompleter.h"
 
 InputWidget::InputWidget(QWidget *parent)
   : AbstractItemView(parent),
     _networkId(0)
 {
   ui.setupUi(this);
 
 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)));
   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);
   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.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()));
   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);
 }
 
   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<BufferView *>(watched);
+  if(view) {
+    QKeyEvent *keyEvent = static_cast<QKeyEvent*>(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 &current, const QModelIndex &previous) {
   Q_UNUSED(previous)
   NetworkId networkId = current.data(NetworkModel::NetworkIdRole).value<NetworkId>();
 void InputWidget::currentChanged(const QModelIndex &current, const QModelIndex &previous) {
   Q_UNUSED(previous)
   NetworkId networkId = current.data(NetworkModel::NetworkIdRole).value<NetworkId>();
index fae3d7f..d60ee34 100644 (file)
@@ -29,7 +29,7 @@
 #include "identity.h"
 #include "network.h"
 
 #include "identity.h"
 #include "network.h"
 
-class InputLine;
+class MultiLineEdit;
 
 class InputWidget : public AbstractItemView {
   Q_OBJECT
 
 class InputWidget : public AbstractItemView {
   Q_OBJECT
@@ -40,7 +40,11 @@ public:
 
   const Network *currentNetwork() const;
 
 
   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 &current, const QModelIndex &previous);
 
 protected slots:
   virtual void currentChanged(const QModelIndex &current, const QModelIndex &previous);
index 59ad97f..07d2bae 100644 (file)
@@ -63,7 +63,6 @@
 #include "flatproxymodel.h"
 #include "iconloader.h"
 #include "inputwidget.h"
 #include "flatproxymodel.h"
 #include "iconloader.h"
 #include "inputwidget.h"
-#include "inputline.h"
 #include "irclistmodel.h"
 #include "ircconnectionwizard.h"
 #include "jumpkeyhandler.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);
   //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);
   view->show();
 
   Client::bufferModel()->synchronizeView(view);
index 3fc5e9b..a657166 100644 (file)
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>761</width>
-    <height>194</height>
+    <width>585</width>
+    <height>52</height>
    </rect>
   </property>
   <property name="sizePolicy">
    </rect>
   </property>
   <property name="sizePolicy">
   <property name="windowTitle">
    <string>Form</string>
   </property>
   <property name="windowTitle">
    <string>Form</string>
   </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
-   <property name="spacing">
-    <number>-1</number>
-   </property>
+  <layout class="QHBoxLayout" name="horizontalLayout">
    <property name="margin">
     <number>0</number>
    </property>
    <item>
    <property name="margin">
     <number>0</number>
    </property>
    <item>
-    <layout class="QHBoxLayout" name="horizontalLayout">
-     <item>
-      <widget class="QComboBox" name="ownNick"/>
-     </item>
-     <item>
-      <widget class="InputLine" name="inputEdit">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
-       <property name="verticalScrollBarPolicy">
-        <enum>Qt::ScrollBarAlwaysOff</enum>
-       </property>
-       <property name="horizontalScrollBarPolicy">
-        <enum>Qt::ScrollBarAlwaysOff</enum>
-       </property>
-       <property name="lineWrapMode">
-        <enum>QTextEdit::NoWrap</enum>
-       </property>
-      </widget>
-     </item>
-    </layout>
+    <widget class="QComboBox" name="ownNick"/>
+   </item>
+   <item>
+    <widget class="MultiLineEdit" name="inputEdit">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="verticalScrollBarPolicy">
+      <enum>Qt::ScrollBarAsNeeded</enum>
+     </property>
+     <property name="horizontalScrollBarPolicy">
+      <enum>Qt::ScrollBarAsNeeded</enum>
+     </property>
+     <property name="lineWrapMode">
+      <enum>QTextEdit::NoWrap</enum>
+     </property>
+    </widget>
    </item>
   </layout>
  </widget>
  <customwidgets>
   <customwidget>
    </item>
   </layout>
  </widget>
  <customwidgets>
   <customwidget>
-   <class>InputLine</class>
+   <class>MultiLineEdit</class>
    <extends>QTextEdit</extends>
    <extends>QTextEdit</extends>
-   <header>inputline.h</header>
+   <header>multilineedit.h</header>
   </customwidget>
  </customwidgets>
  <resources/>
   </customwidget>
  </customwidgets>
  <resources/>
index f366be5..549cee2 100644 (file)
@@ -22,7 +22,7 @@ set(SOURCES
     graphicalui.cpp
     icon.cpp
     iconloader.cpp
     graphicalui.cpp
     icon.cpp
     iconloader.cpp
-    inputline.cpp
+    multilineedit.cpp
     networkmodelcontroller.cpp
     nickview.cpp
     nickviewfilter.cpp
     networkmodelcontroller.cpp
     nickview.cpp
     nickviewfilter.cpp
@@ -52,7 +52,7 @@ set(MOC_HDRS
     fontselector.h
     graphicalui.h
     iconloader.h
     fontselector.h
     graphicalui.h
     iconloader.h
-    inputline.h
+    multilineedit.h
     networkmodelcontroller.h
     nickview.h
     nickviewfilter.h
     networkmodelcontroller.h
     nickview.h
     nickviewfilter.h
diff --git a/src/uisupport/inputline.cpp b/src/uisupport/inputline.cpp
deleted file mode 100644 (file)
index 501ee60..0000000
+++ /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 <QApplication>
-#include <QMenu>
-#include <QMessageBox>
-
-#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<BufferView *>(watched);
-  if(view) {
-    QKeyEvent *keyEvent = static_cast<QKeyEvent*>(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 += "<p>";
-    for(int i = 0; i < 3; i++) {
-      msg += lines[i].left(40);
-      if(lines[i].count() > 40)
-        msg += "...";
-      msg += "<br />";
-    }
-    msg += "...</p>";
-    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 (file)
index 0000000..1a18f32
--- /dev/null
@@ -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 <QApplication>
+#include <QMenu>
+#include <QMessageBox>
+#include <QScrollBar>
+
+#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);
+}
similarity index 73%
rename from src/uisupport/inputline.h
rename to src/uisupport/multilineedit.h
index 6bbfee2..1348592 100644 (file)
  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
  ***************************************************************************/
 
  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
  ***************************************************************************/
 
-#ifndef INPUTLINE_H_
-#define INPUTLINE_H_
+#ifndef MULTILINEEDIT_H_
+#define MULTILINEEDIT_H_
 
 
-#include <QHash>
 #include <QKeyEvent>
 #include <QKeyEvent>
+#include <QHash>
 #include <QTextEdit>
 
 #ifdef HAVE_KDE
 #include <QTextEdit>
 
 #ifdef HAVE_KDE
-#include <KDE/KTextEdit>
+#  include <KDE/KTextEdit>
 #endif
 
 #endif
 
+class QKeyEvent;
 class TabCompleter;
 
 class TabCompleter;
 
-class InputLine : public
+class MultiLineEdit : public
 #ifdef HAVE_KDE
                   KTextEdit
 #else
 #ifdef HAVE_KDE
                   KTextEdit
 #else
@@ -41,8 +42,13 @@ class InputLine : public
   Q_OBJECT
 
 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
 
 
   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;
 
   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);
 protected:
   virtual void keyPressEvent(QKeyEvent * event);
-  virtual bool eventFilter(QObject *watched, QEvent *event);
+  virtual void resizeEvent(QResizeEvent *event);
 
 private slots:
   void on_returnPressed();
 
 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);
 
   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<int, QString> tempHistory;
   qint32 idx;
 
 private:
   QStringList history;
   QHash<int, QString> 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 showHistoryEntry();
+  void updateScrollBars();
+
+  inline int numLines() const { return _numLines; }
+  void setNumLines(int);
 };
 
 #endif
 };
 
 #endif
index 2b60cdd..b9b6eb1 100644 (file)
 
 #include "tabcompleter.h"
 
 
 #include "tabcompleter.h"
 
-#include "inputline.h"
-#include "client.h"
 #include "buffermodel.h"
 #include "buffermodel.h"
-#include "networkmodel.h"
-#include "network.h"
+#include "client.h"
 #include "ircchannel.h"
 #include "ircuser.h"
 #include "ircchannel.h"
 #include "ircuser.h"
+#include "multilineedit.h"
+#include "network.h"
+#include "networkmodel.h"
 #include "uisettings.h"
 
 #include <QRegExp>
 #include "uisettings.h"
 
 #include <QRegExp>
 const Network *TabCompleter::_currentNetwork;
 BufferId TabCompleter::_currentBufferId;
 
 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.
 }
 
 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();
 
   // 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;
 
   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<BufferInfo::Type>(currentIndex.data(NetworkModel::BufferTypeRole).toInt())) {
   QRegExp regex(QString("^[^a-zA-Z]*").append(QRegExp::escape(tabAbbrev)), Qt::CaseInsensitive);
 
   switch(static_cast<BufferInfo::Type>(currentIndex.data(NetworkModel::BufferTypeRole).toInt())) {
@@ -72,68 +72,68 @@ void TabCompleter::buildCompletionList() {
         return;
       foreach(IrcUser *ircUser, channel->ircUsers()) {
         if(regex.indexIn(ircUser->nick()) > -1)
         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)
       }
     }
     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)
   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;
   }
 
     break;
   default:
     return;
   }
 
-  nextCompletion = completionMap.begin();
-  lastCompletionLength = tabAbbrev.length();
+  _nextCompletion = _completionMap.begin();
+  _lastCompletionLength = tabAbbrev.length();
 }
 
 void TabCompleter::complete() {
   TabCompletionSettings s;
 }
 
 void TabCompleter::complete() {
   TabCompletionSettings s;
-  nickSuffix = s.completionSuffix();
+  _nickSuffix = s.completionSuffix();
 
 
-  if(!enabled) {
+  if(!_enabled) {
     buildCompletionList();
     buildCompletionList();
-    enabled = true;
+    _enabled = true;
   }
 
   }
 
-  if (nextCompletion != completionMap.end()) {
+  if (_nextCompletion != _completionMap.end()) {
     // clear previous completion
     // clear previous completion
-    for (int i = 0; i < lastCompletionLength; i++) {
-      inputLine->backspace();
+    for (int i = 0; i < _lastCompletionLength; i++) {
+      _lineEdit->backspace();
     }
 
     // insert completion
     }
 
     // insert completion
-    inputLine->insert(*nextCompletion);
+    _lineEdit->insert(*_nextCompletion);
 
     // remember charcount to delete next time and advance to next completion
 
     // 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
 
     // 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 {
     }
 
   // 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() {
       complete();
     }
   }
 }
 
 void TabCompleter::reset() {
-  enabled = false;
+  _enabled = false;
 }
 
 bool TabCompleter::eventFilter(QObject *obj, QEvent *event) {
 }
 
 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<QKeyEvent *>(event);
     return QObject::eventFilter(obj, event);
 
   QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
index eb066e7..d249943 100644 (file)
@@ -27,7 +27,7 @@
 
 #include "types.h"
 
 
 #include "types.h"
 
-class InputLine;
+class MultiLineEdit;
 class IrcUser;
 class Network;
 
 class IrcUser;
 class Network;
 
@@ -35,7 +35,7 @@ class TabCompleter : public QObject {
   Q_OBJECT
 
 public:
   Q_OBJECT
 
 public:
-  TabCompleter(InputLine *inputLine_);
+  explicit TabCompleter(MultiLineEdit *inputLine_);
 
   void reset();
   void complete();
 
   void reset();
   void complete();
@@ -49,18 +49,18 @@ private:
     QString nick;
   };
 
     QString nick;
   };
 
-  QPointer<InputLine> inputLine;
-  bool enabled;
-  QString nickSuffix;
+  QPointer<MultiLineEdit> _lineEdit;
+  bool _enabled;
+  QString _nickSuffix;
 
   static const Network *_currentNetwork;
   static BufferId _currentBufferId;
 
 
   static const Network *_currentNetwork;
   static BufferId _currentBufferId;
 
-  QMap<CompletionKey, QString> completionMap;
+  QMap<CompletionKey, QString> _completionMap;
   // QStringList completionTemplates;
 
   // QStringList completionTemplates;
 
-  QMap<CompletionKey, QString>::Iterator nextCompletion;
-  int lastCompletionLength;
+  QMap<CompletionKey, QString>::Iterator _nextCompletion;
+  int _lastCompletionLength;
 
   void buildCompletionList();
 
 
   void buildCompletionList();