11 changed files with 520 additions and 22 deletions
@ -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; |
||||||
|
}; |
||||||
Loading…
Reference in new issue