summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Saunders <ben.e.saunders@gmail.com>2017-11-05 13:01:21 -0800
committermujx <mujx@users.noreply.github.com>2017-11-05 23:01:21 +0200
commit4ccb5ed81f1787a3cca12e5ca705fad4e3fa8ca5 (patch)
tree397e54883914fc354c06b27640282e7e941c7bd1
parent2929d4a3a43d5780da37b34feac86c92d08a17f1 (diff)
Add input history, enable multi-line input, refactor commands (#119)
This also fixes the transmission of mis-typed commands as messages, fixes inability to send messages that start with a command, and does some initial work towards automatically resizing the input field to fit the input message.
-rw-r--r--include/TextInputWidget.h32
-rw-r--r--src/TextInputWidget.cc164
2 files changed, 135 insertions, 61 deletions
diff --git a/include/TextInputWidget.h b/include/TextInputWidget.h
index 32da6ba3..70b1c213 100644
--- a/include/TextInputWidget.h
+++ b/include/TextInputWidget.h
@@ -17,6 +17,8 @@
#pragma once
+#include <deque>
+
#include <QHBoxLayout>
#include <QPaintEvent>
#include <QTextEdit>
@@ -29,26 +31,36 @@
namespace msgs = matrix::events::messages;
-static const QString EMOTE_COMMAND("/me ");
-static const QString JOIN_COMMAND("/join ");
-
class FilteredTextEdit : public QTextEdit
{
Q_OBJECT
-private:
- QTimer *typingTimer_;
-
public:
explicit FilteredTextEdit(QWidget *parent = nullptr);
- void keyPressEvent(QKeyEvent *event);
void stopTyping();
+ QSize sizeHint() const override;
+ QSize minimumSizeHint() const override;
+
+ void submit();
+
signals:
- void enterPressed();
void startedTyping();
void stoppedTyping();
+ void message(QString);
+ void command(QString name, QString args);
+
+protected:
+ void keyPressEvent(QKeyEvent *event) override;
+
+private:
+ std::deque<QString> true_history_, working_history_;
+ size_t history_index_;
+ QTimer *typingTimer_;
+
+ void textChanged();
+ void afterCompletion(int);
};
class TextInputWidget : public QFrame
@@ -62,7 +74,6 @@ public:
void stopTyping();
public slots:
- void onSendButtonClicked();
void openFileSelection();
void hideUploadSpinner();
void focusLineEdit() { input_->setFocus(); };
@@ -84,8 +95,7 @@ protected:
private:
void showUploadSpinner();
- QString parseEmoteCommand(const QString &cmd);
- QString parseJoinCommand(const QString &cmd);
+ void command(QString name, QString args);
QHBoxLayout *topLayout_;
FilteredTextEdit *input_;
diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc
index cad54d96..e7c48b5f 100644
--- a/src/TextInputWidget.cc
+++ b/src/TextInputWidget.cc
@@ -15,6 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <QAbstractTextDocumentLayout>
#include <QDebug>
#include <QFile>
#include <QFileDialog>
@@ -25,9 +26,21 @@
#include "Config.h"
#include "TextInputWidget.h"
+static constexpr size_t INPUT_HISTORY_SIZE = 127;
+
FilteredTextEdit::FilteredTextEdit(QWidget *parent)
- : QTextEdit(parent)
+ : QTextEdit{ parent }
+ , history_index_{ 0 }
{
+ connect(document()->documentLayout(),
+ &QAbstractTextDocumentLayout::documentSizeChanged,
+ this,
+ &FilteredTextEdit::updateGeometry);
+ QSizePolicy policy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ policy.setHeightForWidth(true);
+ setSizePolicy(policy);
+ working_history_.push_back("");
+ connect(this, &QTextEdit::textChanged, this, &FilteredTextEdit::textChanged);
setAcceptRichText(false);
typingTimer_ = new QTimer(this);
@@ -49,12 +62,40 @@ FilteredTextEdit::keyPressEvent(QKeyEvent *event)
typingTimer_->start();
}
- if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) {
- stopTyping();
-
- emit enterPressed();
- } else {
+ switch (event->key()) {
+ case Qt::Key_Return:
+ case Qt::Key_Enter:
+ if (!(event->modifiers() & Qt::ShiftModifier)) {
+ stopTyping();
+ submit();
+ } else {
+ QTextEdit::keyPressEvent(event);
+ }
+ break;
+ case Qt::Key_Up: {
+ auto initial_cursor = textCursor();
+ QTextEdit::keyPressEvent(event);
+ if (textCursor() == initial_cursor &&
+ history_index_ + 1 < working_history_.size()) {
+ ++history_index_;
+ setPlainText(working_history_[history_index_]);
+ moveCursor(QTextCursor::End);
+ }
+ break;
+ }
+ case Qt::Key_Down: {
+ auto initial_cursor = textCursor();
QTextEdit::keyPressEvent(event);
+ if (textCursor() == initial_cursor && history_index_ > 0) {
+ --history_index_;
+ setPlainText(working_history_[history_index_]);
+ moveCursor(QTextCursor::End);
+ }
+ break;
+ }
+ default:
+ QTextEdit::keyPressEvent(event);
+ break;
}
}
@@ -65,6 +106,65 @@ FilteredTextEdit::stopTyping()
emit stoppedTyping();
}
+QSize
+FilteredTextEdit::sizeHint() const
+{
+ ensurePolished();
+ auto margins = viewportMargins();
+ margins += document()->documentMargin();
+ QSize size = document()->size().toSize();
+ size.rwidth() += margins.left() + margins.right();
+ size.rheight() += margins.top() + margins.bottom();
+ return size;
+}
+
+QSize
+FilteredTextEdit::minimumSizeHint() const
+{
+ ensurePolished();
+ auto margins = viewportMargins();
+ margins += document()->documentMargin();
+ margins += contentsMargins();
+ QSize size(fontMetrics().averageCharWidth() * 10,
+ fontMetrics().lineSpacing() + margins.top() + margins.bottom());
+ return size;
+}
+
+void
+FilteredTextEdit::submit()
+{
+ if (true_history_.size() == INPUT_HISTORY_SIZE)
+ true_history_.pop_back();
+ true_history_.push_front(toPlainText());
+ working_history_ = true_history_;
+ working_history_.push_front("");
+ history_index_ = 0;
+
+ QString text = toPlainText();
+ if (text.startsWith('/')) {
+ int command_end = text.indexOf(' ');
+ if (command_end == -1)
+ command_end = text.size();
+ auto name = text.mid(1, command_end - 1);
+ auto args = text.mid(command_end + 1);
+ if (name.isEmpty() || name == "/") {
+ message(args);
+ } else {
+ command(name, args);
+ }
+ } else {
+ message(std::move(text));
+ }
+
+ clear();
+}
+
+void
+FilteredTextEdit::textChanged()
+{
+ working_history_[history_index_] = toPlainText();
+}
+
TextInputWidget::TextInputWidget(QWidget *parent)
: QFrame(parent)
{
@@ -122,9 +222,10 @@ TextInputWidget::TextInputWidget(QWidget *parent)
setLayout(topLayout_);
- connect(sendMessageBtn_, SIGNAL(clicked()), this, SLOT(onSendButtonClicked()));
+ connect(sendMessageBtn_, &FlatButton::clicked, input_, &FilteredTextEdit::submit);
connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection()));
- connect(input_, SIGNAL(enterPressed()), sendMessageBtn_, SIGNAL(clicked()));
+ connect(input_, &FilteredTextEdit::message, this, &TextInputWidget::sendTextMessage);
+ connect(input_, &FilteredTextEdit::command, this, &TextInputWidget::command);
connect(emojiBtn_,
SIGNAL(emojiSelected(const QString &)),
this,
@@ -160,50 +261,13 @@ TextInputWidget::addSelectedEmoji(const QString &emoji)
}
void
-TextInputWidget::onSendButtonClicked()
+TextInputWidget::command(QString command, QString args)
{
- auto msgText = input_->document()->toPlainText().trimmed();
-
- if (msgText.isEmpty())
- return;
-
- if (msgText.startsWith(EMOTE_COMMAND)) {
- auto text = parseEmoteCommand(msgText);
-
- if (!text.isEmpty())
- emit sendEmoteMessage(text);
- } else if (msgText.startsWith(JOIN_COMMAND)) {
- auto room = parseJoinCommand(msgText);
-
- if (!room.isEmpty())
- emit sendJoinRoomRequest(room);
- } else {
- emit sendTextMessage(msgText);
+ if (command == "me") {
+ sendEmoteMessage(args);
+ } else if (command == "join") {
+ sendJoinRoomRequest(args);
}
-
- input_->clear();
-}
-
-QString
-TextInputWidget::parseJoinCommand(const QString &cmd)
-{
- auto room = cmd.right(cmd.size() - JOIN_COMMAND.size()).trimmed();
-
- if (!room.isEmpty())
- return room;
-
- return QString("");
-}
-
-QString
-TextInputWidget::parseEmoteCommand(const QString &cmd)
-{
- auto text = cmd.right(cmd.size() - EMOTE_COMMAND.size()).trimmed();
-
- if (!text.isEmpty())
- return text;
-
- return QString("");
}
void