tools/chatgui: input bar with emoji panel, custom tab bar, Ctrl+Enter multiline, scroll fixes
This commit is contained in:
1 parent
b667d6f352
commit
e50ca9978e
11 files changed
+521
-23
No files matched your search
@@ -22,6 +22,10 @@ add_executable(chatgui
|
||||
src/messagedelegate.cpp
|
||||
src/messagelist.cpp
|
||||
src/accountlist.cpp
|
||||
src/emoji.cpp
|
||||
src/emojipanel.cpp
|
||||
src/emojitabbar.cpp
|
||||
src/inputbar.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(chatgui PRIVATE ${QT_LIBS})
|
||||
@@ -0,0 +1,118 @@
|
||||
#include "emoji.h"
|
||||
|
||||
EmojiData EmojiData::unicodeEmoji(const QString &u, const QString &s) {
|
||||
EmojiData d;
|
||||
d.type = EmojiType::Unicode;
|
||||
d.unicode = u;
|
||||
d.shortcode = s;
|
||||
return d;
|
||||
}
|
||||
|
||||
static QVector<EmojiData> smileys() {
|
||||
return {
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x80", ":grinning:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x83", ":smiley:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x84", ":smile:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x81", ":grin:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x86", ":laughing:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x85", ":sweat_smile:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x82", ":joy:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA4\xA3", ":rofl:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x8A", ":blush:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x87", ":innocent:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x99\x82", ":slight_smile:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x89", ":wink:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x8D", ":heart_eyes:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA5\xB0", ":smiling_face_3_hearts:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x98", ":kissing_heart:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x97", ":kissing:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x99", ":kissing_smiling_eyes:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x9B", ":stuck_out_tongue:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x9D", ":stuck_out_tongue_closed_eyes:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x9C", ":stuck_out_tongue_wink:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA5\xB4", ":face_with_tongue:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA4\xAA", ":zany_face:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x8E", ":sunglasses:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA5\xB3", ":star_struck:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA4\xA9", ":hugging:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA4\x94", ":thinking:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA4\xAD", ":shushing:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA4\x90", ":zipper_mouth:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x90", ":neutral_face:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\x91", ":expressionless:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x99\x84", ":rolling_eyes:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\xAF", ":hushed:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\xB3", ":flushed:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA5\xB5", ":hot_face:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA5\xB6", ":cold_face:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\xA8", ":fearful:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\xB1", ":scream:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\xA2", ":cry:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\xAD", ":sob:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\xA4", ":triumph:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x98\xA0", ":angry:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\xA9", ":poop:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x91\xBB", ":ghost:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\x80", ":skull:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x91\xBD", ":alien:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA4\x96", ":robot:"),
|
||||
};
|
||||
}
|
||||
|
||||
static QVector<EmojiData> gestures() {
|
||||
return {
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x91\x8D", ":+1:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x91\x8E", ":-1:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x91\x8F", ":clap:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x99\x8C", ":raised_hands:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x99\x8F", ":pray:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA4\x9D", ":handshake:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\xAA", ":muscle:"),
|
||||
EmojiData::unicodeEmoji("\xE2\x9C\x8A", ":fist:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x91\x8B", ":wave:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x96\x90", ":ok_hand:"),
|
||||
EmojiData::unicodeEmoji("\xE2\x98\x9D\xEF\xB8\x8F", ":point_up:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x91\x86", ":point_up_2:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x91\x87", ":point_down:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x91\x88", ":point_left:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x91\x89", ":point_right:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA4\x98", ":metal:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA4\x9E", ":fingers_crossed:"),
|
||||
EmojiData::unicodeEmoji("\xE2\x9C\x8C\xEF\xB8\x8F", ":v:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA4\x9F", ":love_you_gesture:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x91\x8C", ":pinched_fingers:"),
|
||||
};
|
||||
}
|
||||
|
||||
static QVector<EmojiData> hearts() {
|
||||
return {
|
||||
EmojiData::unicodeEmoji("\xE2\x9D\xA4\xEF\xB8\x8F", ":heart:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA7\xA1", ":orange_heart:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\x9B", ":yellow_heart:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\x9A", ":green_heart:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\x99", ":blue_heart:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\x9C", ":purple_heart:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA4\x8E", ":brown_heart:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x96\xA4", ":black_heart:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\xA4\x8D", ":white_heart:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\x94", ":broken_heart:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\x95", ":two_hearts:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\x98", ":cupid:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\x97", ":heartbeat:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x94\xA5", ":fire:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\xAF", ":100:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\xA5", ":boom:"),
|
||||
EmojiData::unicodeEmoji("\xE2\xAD\x90", ":star:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x8C\x9F", ":glowing_star:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\xAB", ":dizzy:"),
|
||||
EmojiData::unicodeEmoji("\xF0\x9F\x92\xA4", ":zzz:"),
|
||||
};
|
||||
}
|
||||
|
||||
QVector<EmojiCategory> builtinEmojiSet() {
|
||||
return {
|
||||
{"Smileys & People", smileys()},
|
||||
{"Gestures", gestures()},
|
||||
{"Hearts & Symbols", hearts()},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
enum class EmojiType { Unicode, Image, Animation };
|
||||
|
||||
struct EmojiData {
|
||||
EmojiType type = EmojiType::Unicode;
|
||||
QString unicode;
|
||||
QString shortcode;
|
||||
QString imagePath;
|
||||
QString animPath;
|
||||
|
||||
static EmojiData unicodeEmoji(const QString &u, const QString &s);
|
||||
};
|
||||
|
||||
struct EmojiCategory {
|
||||
QString name;
|
||||
QVector<EmojiData> items;
|
||||
};
|
||||
|
||||
QVector<EmojiCategory> builtinEmojiSet();
|
||||
@@ -0,0 +1,86 @@
|
||||
#include "emojipanel.h"
|
||||
#include "emoji.h"
|
||||
#include "emojitabbar.h"
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QTabWidget>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace {
|
||||
|
||||
class EmojiTabWidget : public QTabWidget {
|
||||
public:
|
||||
using QTabWidget::setTabBar;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EmojiPanel::EmojiPanel(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setStyleSheet("EmojiPanel { background: palette(window); }");
|
||||
buildPanel(builtinEmojiSet());
|
||||
hide();
|
||||
}
|
||||
|
||||
void EmojiPanel::showEvent(QShowEvent *event) {
|
||||
if (parentWidget()) {
|
||||
setFixedHeight(parentWidget()->height() / 2);
|
||||
}
|
||||
QWidget::showEvent(event);
|
||||
}
|
||||
|
||||
void EmojiPanel::buildPanel(const QVector<EmojiCategory> &categories) {
|
||||
auto *tabs = new EmojiTabWidget();
|
||||
tabs->setDocumentMode(true);
|
||||
tabs->setTabBar(new EmojiTabBar(tabs));
|
||||
tabs->setStyleSheet("QTabWidget::pane { border: none; background: rgba(255,255,255,0.6); }");
|
||||
|
||||
for (const auto &cat : categories) {
|
||||
tabs->addTab(buildCategoryPage(cat), cat.name);
|
||||
}
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
layout->addWidget(tabs);
|
||||
}
|
||||
|
||||
QWidget *EmojiPanel::buildCategoryPage(const EmojiCategory &cat) {
|
||||
auto *scroll = new QScrollArea();
|
||||
scroll->setWidgetResizable(true);
|
||||
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
scroll->setStyleSheet("QScrollArea { border: none; }");
|
||||
|
||||
auto *container = new QWidget();
|
||||
auto *grid = new QGridLayout(container);
|
||||
grid->setContentsMargins(4, 4, 4, 4);
|
||||
grid->setSpacing(2);
|
||||
|
||||
int cols = 9;
|
||||
for (int i = 0; i < cat.items.size(); ++i) {
|
||||
auto *btn = new QPushButton(cat.items[i].unicode, container);
|
||||
btn->setFixedSize(32, 32);
|
||||
btn->setCursor(Qt::PointingHandCursor);
|
||||
btn->setStyleSheet(
|
||||
"QPushButton { border: none; border-radius: 4px; font-size: 18px; background: transparent; }"
|
||||
"QPushButton:hover { background: rgba(0,0,0,0.08); }"
|
||||
);
|
||||
btn->setToolTip(cat.items[i].shortcode);
|
||||
connect(btn, &QPushButton::clicked, this, &EmojiPanel::onEmojiClicked);
|
||||
grid->addWidget(btn, i / cols, i % cols);
|
||||
}
|
||||
grid->setRowStretch(grid->rowCount(), 1);
|
||||
|
||||
scroll->setWidget(container);
|
||||
return scroll;
|
||||
}
|
||||
|
||||
void EmojiPanel::onEmojiClicked() {
|
||||
auto *btn = qobject_cast<QPushButton *>(sender());
|
||||
if (btn) {
|
||||
emit emojiSelected(btn->text());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QVector>
|
||||
|
||||
class QPushButton;
|
||||
struct EmojiCategory;
|
||||
|
||||
class EmojiPanel : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit EmojiPanel(QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void emojiSelected(const QString &unicode);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void onEmojiClicked();
|
||||
|
||||
private:
|
||||
void buildPanel(const QVector<EmojiCategory> &categories);
|
||||
QWidget *buildCategoryPage(const EmojiCategory &cat);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
#include "emojitabbar.h"
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionTab>
|
||||
|
||||
EmojiTabBar::EmojiTabBar(QWidget *parent)
|
||||
: QTabBar(parent)
|
||||
{
|
||||
setDrawBase(false);
|
||||
setExpanding(false);
|
||||
setUsesScrollButtons(false);
|
||||
}
|
||||
|
||||
void EmojiTabBar::paintEvent(QPaintEvent *event) {
|
||||
Q_UNUSED(event);
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
int w = width();
|
||||
int h = height();
|
||||
static const QColor bgColor(255, 255, 255);
|
||||
static const QColor activeTextColor(0x33, 0x90, 0xEC);
|
||||
static const QColor inactiveTextColor(0x8E, 0x8E, 0x93);
|
||||
static const QColor activeBarColor(0x33, 0x90, 0xEC);
|
||||
static const QColor separatorColor(0xE0, 0xE0, 0xE0);
|
||||
|
||||
p.fillRect(rect(), bgColor);
|
||||
|
||||
QFont f = font();
|
||||
f.setPointSize(f.pointSize() - 1);
|
||||
p.setFont(f);
|
||||
|
||||
int current = currentIndex();
|
||||
|
||||
for (int i = 0; i < count(); ++i) {
|
||||
QRect r = tabRect(i);
|
||||
if (!r.isValid()) continue;
|
||||
|
||||
bool active = (i == current);
|
||||
p.setPen(active ? activeTextColor : inactiveTextColor);
|
||||
p.drawText(r, Qt::AlignHCenter | Qt::AlignVCenter, tabText(i));
|
||||
|
||||
if (active) {
|
||||
int barX = r.left() + 4;
|
||||
int barW = r.width() - 8;
|
||||
p.fillRect(QRect(barX, h - 3, barW, 3), activeBarColor);
|
||||
}
|
||||
}
|
||||
|
||||
p.fillRect(QRect(0, h - 1, w, 1), separatorColor);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <QTabBar>
|
||||
|
||||
class EmojiTabBar : public QTabBar {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit EmojiTabBar(QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
};
|
||||
@@ -0,0 +1,125 @@
|
||||
#include "inputbar.h"
|
||||
#include "emojipanel.h"
|
||||
#include <QTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <QHBoxLayout>
|
||||
#include <QFileDialog>
|
||||
#include <QDebug>
|
||||
#include <QKeyEvent>
|
||||
#include <QFontMetrics>
|
||||
#include <QScrollBar>
|
||||
#include <QApplication>
|
||||
|
||||
static QPushButton *makeIconBtn(const QString &text, const QString &tooltip, QWidget *parent) {
|
||||
auto *btn = new QPushButton(text, parent);
|
||||
btn->setFixedSize(34, 34);
|
||||
btn->setCursor(Qt::PointingHandCursor);
|
||||
btn->setToolTip(tooltip);
|
||||
btn->setStyleSheet(
|
||||
"QPushButton { border: none; border-radius: 4px; font-size: 18px; background: transparent; }"
|
||||
"QPushButton:hover { background: palette(light); }"
|
||||
);
|
||||
return btn;
|
||||
}
|
||||
|
||||
InputBar::InputBar(EmojiPanel *emojiPanel, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_input(new QTextEdit(this))
|
||||
, m_emojiBtn(nullptr)
|
||||
, m_emojiPanel(emojiPanel)
|
||||
{
|
||||
auto *layout = new QHBoxLayout(this);
|
||||
layout->setContentsMargins(8, 4, 8, 4);
|
||||
layout->setSpacing(4);
|
||||
|
||||
auto *attachBtn = makeIconBtn(QString::fromUtf8("\xF0\x9F\x93\x8E"),
|
||||
QString::fromUtf8("Прикрепить"), this);
|
||||
connect(attachBtn, &QPushButton::clicked, this, [this]() {
|
||||
QString path = QFileDialog::getOpenFileName(this, QString::fromUtf8("Прикрепить файл"));
|
||||
if (!path.isEmpty()) {
|
||||
qDebug() << "Attached:" << path;
|
||||
}
|
||||
});
|
||||
layout->addWidget(attachBtn, 0, Qt::AlignBottom);
|
||||
|
||||
m_input->setAcceptRichText(false);
|
||||
m_input->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_input->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_input->setTabChangesFocus(true);
|
||||
m_input->setStyleSheet(
|
||||
"QTextEdit { border: 1px solid palette(mid); border-radius: 6px;"
|
||||
" padding: 4px 8px; background: palette(base); }"
|
||||
);
|
||||
m_input->installEventFilter(this);
|
||||
QFontMetrics fm(m_input->font());
|
||||
m_input->setFixedHeight(fm.lineSpacing() + 12);
|
||||
layout->addWidget(m_input, 1);
|
||||
|
||||
m_emojiBtn = makeIconBtn(QString::fromUtf8("\xF0\x9F\x99\x82"),
|
||||
QString::fromUtf8("Смайлики"), this);
|
||||
layout->addWidget(m_emojiBtn, 0, Qt::AlignBottom);
|
||||
|
||||
auto *sendBtn = makeIconBtn(QString::fromUtf8("\xE2\x9E\xA4"),
|
||||
QString::fromUtf8("Отправить"), this);
|
||||
sendBtn->setStyleSheet(sendBtn->styleSheet() +
|
||||
"QPushButton { color: palette(highlight); font-weight: bold; }");
|
||||
layout->addWidget(sendBtn, 0, Qt::AlignBottom);
|
||||
|
||||
connect(m_input, &QTextEdit::textChanged, this, &InputBar::adjustInputHeight);
|
||||
connect(sendBtn, &QPushButton::clicked, this, &InputBar::onSendClicked);
|
||||
|
||||
if (m_emojiPanel) {
|
||||
connect(m_emojiBtn, &QPushButton::clicked, this, [this]() {
|
||||
m_emojiPanel->setVisible(!m_emojiPanel->isVisible());
|
||||
});
|
||||
connect(m_emojiPanel, &EmojiPanel::emojiSelected, this, [this](const QString &emoji) {
|
||||
m_input->insertPlainText(emoji);
|
||||
m_input->setFocus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool InputBar::eventFilter(QObject *obj, QEvent *event) {
|
||||
if (obj == m_input && event->type() == QEvent::KeyPress) {
|
||||
auto *ke = static_cast<QKeyEvent *>(event);
|
||||
if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
|
||||
if (ke->modifiers() & Qt::ControlModifier) {
|
||||
m_input->insertPlainText("\n");
|
||||
return true;
|
||||
} else {
|
||||
onSendClicked();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
void InputBar::adjustInputHeight() {
|
||||
m_input->document()->adjustSize();
|
||||
|
||||
int lineSpacing = m_input->fontMetrics().lineSpacing();
|
||||
int docH = (int)m_input->document()->size().height() + 12;
|
||||
int maxH = 12 * lineSpacing + 12;
|
||||
int h = qBound(lineSpacing + 12, docH, maxH);
|
||||
m_input->setFixedHeight(h);
|
||||
|
||||
bool needScroll = docH > maxH;
|
||||
m_input->setVerticalScrollBarPolicy(needScroll ? Qt::ScrollBarAlwaysOn
|
||||
: Qt::ScrollBarAlwaysOff);
|
||||
if (needScroll) {
|
||||
QTextCursor c = m_input->textCursor();
|
||||
if (c.atEnd())
|
||||
m_input->verticalScrollBar()->setValue(m_input->verticalScrollBar()->maximum());
|
||||
else
|
||||
m_input->ensureCursorVisible();
|
||||
}
|
||||
}
|
||||
|
||||
void InputBar::onSendClicked() {
|
||||
QString text = m_input->toPlainText().trimmed();
|
||||
if (text.isEmpty()) return;
|
||||
emit sendMessage(text);
|
||||
m_input->clear();
|
||||
if (m_emojiPanel) m_emojiPanel->hide();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QTextEdit;
|
||||
class QPushButton;
|
||||
class EmojiPanel;
|
||||
|
||||
class InputBar : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit InputBar(EmojiPanel *emojiPanel, QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void sendMessage(const QString &text);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void onSendClicked();
|
||||
void adjustInputHeight();
|
||||
|
||||
private:
|
||||
QTextEdit *m_input;
|
||||
QPushButton *m_emojiBtn;
|
||||
EmojiPanel *m_emojiPanel;
|
||||
};
|
||||
@@ -1,9 +1,12 @@
|
||||
#include "messagelist.h"
|
||||
#include "chatview.h"
|
||||
#include "messagedelegate.h"
|
||||
#include "inputbar.h"
|
||||
#include "emojipanel.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
#include <QDateTime>
|
||||
|
||||
static QPixmap makeAvatar(const QColor &color, const QString &letter, int size = 36) {
|
||||
QPixmap pix(size, size);
|
||||
@@ -33,28 +36,6 @@ static QVariant makeReaction(const QString &emoji, int count,
|
||||
return m;
|
||||
}
|
||||
|
||||
MessageList::MessageList(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_view(new ChatView(this))
|
||||
, m_model(new QStandardItemModel(this))
|
||||
{
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
m_view->setModel(m_model);
|
||||
m_view->setItemDelegate(new MessageDelegate(this));
|
||||
m_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
m_view->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
m_view->setFrameShape(QFrame::NoFrame);
|
||||
m_view->setBackgroundImage("../resources/bg.jpg");
|
||||
layout->addWidget(m_view);
|
||||
|
||||
populateStubData();
|
||||
}
|
||||
|
||||
static void setMsg(QStandardItem *item,
|
||||
const QString &author, const QString &text, const QString &time,
|
||||
const QPixmap &avatar, bool edited,
|
||||
@@ -74,6 +55,47 @@ static void setMsg(QStandardItem *item,
|
||||
item->setData(reactions, MsgReactionsRole);
|
||||
}
|
||||
|
||||
MessageList::MessageList(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_view(new ChatView(this))
|
||||
, m_model(new QStandardItemModel(this))
|
||||
, m_inputBar(nullptr)
|
||||
, m_emojiPanel(nullptr)
|
||||
{
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
m_view->setModel(m_model);
|
||||
m_view->setItemDelegate(new MessageDelegate(this));
|
||||
m_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
m_view->setSelectionMode(QAbstractItemView::NoSelection);
|
||||
m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_view->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
m_view->setFrameShape(QFrame::NoFrame);
|
||||
m_view->setBackgroundImage("../resources/bg.jpg");
|
||||
layout->addWidget(m_view, 1);
|
||||
|
||||
m_emojiPanel = new EmojiPanel(this);
|
||||
layout->addWidget(m_emojiPanel);
|
||||
|
||||
m_inputBar = new InputBar(m_emojiPanel, this);
|
||||
layout->addWidget(m_inputBar);
|
||||
|
||||
connect(m_inputBar, &InputBar::sendMessage, this, [this](const QString &text) {
|
||||
QColor youColor(74, 144, 226);
|
||||
auto av = makeAvatar(youColor, "Y");
|
||||
auto *item = new QStandardItem();
|
||||
setMsg(item,
|
||||
QString::fromUtf8("You"), text,
|
||||
QDateTime::currentDateTime().toString("HH:mm"),
|
||||
av, false, false, QString(), QString(), {});
|
||||
m_model->appendRow(item);
|
||||
m_view->scrollToBottom();
|
||||
});
|
||||
|
||||
populateStubData();
|
||||
}
|
||||
void MessageList::populateStubData() {
|
||||
QColor cAlice(74, 144, 226), cBob(126, 211, 33), cCarol(245, 166, 35);
|
||||
QColor cDave(144, 19, 254), cEve(0, 184, 148);
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <QStandardItemModel>
|
||||
|
||||
class ChatView;
|
||||
class InputBar;
|
||||
class EmojiPanel;
|
||||
|
||||
class MessageList : public QWidget {
|
||||
Q_OBJECT
|
||||
@@ -11,7 +13,9 @@ public:
|
||||
explicit MessageList(QWidget *parent = nullptr);
|
||||
|
||||
private:
|
||||
ChatView *m_view;
|
||||
ChatView *m_view;
|
||||
QStandardItemModel *m_model;
|
||||
InputBar *m_inputBar;
|
||||
EmojiPanel *m_emojiPanel;
|
||||
void populateStubData();
|
||||
};
|
||||
Reference in new issue
Block a user