简述:

QT实现自定义表情包,通过对(能够设置表情的行列数 , 表情的大小,表情的个数、最大行数等)

效果:

代码如下:

EmoticonsWidget主要实现表情包窗口。

EmoticonsWidget.cpp

#include "EmoticonsWidget.h"
#include "SmileyPack.h"#include <QFile>
#include <QGridLayout>
#include <QLayout>
#include <QMouseEvent>
#include <QPushButton>
#include <QRadioButton>
#include <QDebug>#include <math.h>QString getStylesheet(const QString& filename)
{QFile file(filename);if (!file.open(QFile::ReadOnly | QFile::Text)) {qWarning() << "Stylesheet " << filename << " not found";return QString();}return file.readAll();
}EmoticonsWidget::EmoticonsWidget(QWidget* parent): QMenu(parent)
{setStyleSheet(getStylesheet(":/ui/EmoticonWidget.css"));setLayout(&layout);layout.addWidget(&stack);QWidget* pageButtonsContainer = new QWidget;QHBoxLayout* buttonLayout = new QHBoxLayout;pageButtonsContainer->setLayout(buttonLayout);layout.addWidget(pageButtonsContainer);const int maxCols = 8;const int maxRows = 8;const int itemsPerPage = maxRows * maxCols;const QList<QStringList>& emoticons = SmileyPack::getInstance().getEmoticons();int itemCount = emoticons.size();int pageCount = ceil(float(itemCount) / float(itemsPerPage));int currPage = 0;int currItem = 0;int row = 0;int col = 0;// 配置表情大小const int px = 40;const QSize size(px, px);// 创建页面buttonLayout->addStretch();for (int i = 0; i < pageCount; ++i) {QGridLayout* pageLayout = new QGridLayout;pageLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding),maxRows, 0);pageLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum), 0,maxCols);QWidget* page = new QWidget;page->setLayout(pageLayout);stack.addWidget(page);// 只有当页面超过1个时才需要页面按钮if (pageCount > 1) {QRadioButton* pageButton = new QRadioButton;pageButton->setProperty("pageIndex", i);pageButton->setCursor(Qt::PointingHandCursor);pageButton->setChecked(i == 0);buttonLayout->addWidget(pageButton);connect(pageButton, &QRadioButton::clicked, this, &EmoticonsWidget::onPageButtonClicked);}}buttonLayout->addStretch();SmileyPack& smileyPack = SmileyPack::getInstance();for (const QStringList& set : emoticons) {QPushButton* button = new QPushButton;std::shared_ptr<QIcon> icon = smileyPack.getAsIcon(set[0]);emoticonsIcons.append(icon);button->setIcon(icon->pixmap(size));button->setToolTip(set.join(" "));button->setProperty("sequence", set[0]);button->setCursor(Qt::PointingHandCursor);button->setFlat(true);button->setIconSize(size);button->setFixedSize(size);connect(button, &QPushButton::clicked, this, &EmoticonsWidget::onSmileyClicked);qobject_cast<QGridLayout*>(stack.widget(currPage)->layout())->addWidget(button, row, col);++col;++currItem;// next rowif (col >= maxCols) {col = 0;++row;}// next pageif (currItem >= itemsPerPage) {row = 0;currItem = 0;++currPage;}}// 计算sizeHintlayout.activate();
}void EmoticonsWidget::onSmileyClicked()
{QWidget* sender = qobject_cast<QWidget*>(QObject::sender());if (sender) {QString sequence =sender->property("sequence").toString().replace("&lt;", "<").replace("&gt;", ">");emit insertEmoticon(sequence);}
}void EmoticonsWidget::onPageButtonClicked()
{QWidget* sender = qobject_cast<QRadioButton*>(QObject::sender());if (sender) {int page = sender->property("pageIndex").toInt();stack.setCurrentIndex(page);}
}QSize EmoticonsWidget::sizeHint() const
{return layout.sizeHint();
}void EmoticonsWidget::mouseReleaseEvent(QMouseEvent* ev)
{if (!rect().contains(ev->pos()))hide();
}void EmoticonsWidget::mousePressEvent(QMouseEvent*)
{
}void EmoticonsWidget::wheelEvent(QWheelEvent* e)
{if (e->orientation() == Qt::Vertical) {if (e->delta() < 0) {stack.setCurrentIndex(stack.currentIndex() + 1);} else {stack.setCurrentIndex(stack.currentIndex() - 1);}emit PageButtonsUpdate();}
}void EmoticonsWidget::PageButtonsUpdate()
{QList<QRadioButton*> pageButtons = this->findChildren<QRadioButton*>(QString());foreach (QRadioButton* t_pageButton, pageButtons) {if (t_pageButton->property("pageIndex").toInt() == stack.currentIndex())t_pageButton->setChecked(true);elset_pageButton->setChecked(false);}
}void EmoticonsWidget::keyPressEvent(QKeyEvent* e)
{Q_UNUSED(e)hide();
}

EmoticonsWidget.h

#ifndef EMOTICONSWIDGET_H
#define EMOTICONSWIDGET_H#include <QMenu>
#include <QStackedWidget>
#include <QVBoxLayout>
#include <QVector>#include <memory>class QIcon;class EmoticonsWidget : public QMenu
{Q_OBJECT
public:explicit EmoticonsWidget(QWidget* parent = 0);signals:void insertEmoticon(QString str);private slots:void onSmileyClicked();void onPageButtonClicked();void PageButtonsUpdate();protected:void mouseReleaseEvent(QMouseEvent* ev) final override;void mousePressEvent(QMouseEvent* ev) final override;void wheelEvent(QWheelEvent* event) final override;void keyPressEvent(QKeyEvent* e) final override;private:QStackedWidget stack;QVBoxLayout layout;QList<std::shared_ptr<QIcon>> emoticonsIcons;public:QSize sizeHint() const override;
};#endif // EMOTICONSWIDGET_H

SmileyPack主要对表情包的封装,支持自定义修改及表情包的扩展。

SmileyPack.cpp

#include "SmileyPack.h"#include <QDir>
#include <QDomElement>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QtConcurrent/QtConcurrentRun>
#include <QTimer>/*** @class SmileyPack* @brief 将表情符号映射到笑脸** @var SmileyPack::filenameTable* @brief 将表情符号与对应的笑脸相匹配  ":)" -> "happy.png"** @var SmileyPack::iconCache* @brief 一个笑脸的表示 "happy.png" -> data** @var SmileyPack::emoticons* @brief {{ ":)", ":-)" }, {":(", ...}, ... }** @var SmileyPack::path* @brief 包含cfg和图像文件的目录** @var SmileyPack::defaultPaths* @brief   包含所有可以找到笑脸的目录*/QStringList loadDefaultPaths();static const QStringList DEFAULT_PATHS = loadDefaultPaths();static const QString RICH_TEXT_PATTERN = QStringLiteral("<img title=\"%1\" src=\"key:%1\"\\>");static const QString EMOTICONS_FILE_NAME = QStringLiteral("emoticons.xml");static constexpr int CLEANUP_TIMEOUT = 5 * 60 * 1000; // 5 minutes/*** @brief 用"表情符号"子目录构建标准目录列表,不管这些目录存不存在* @return 默认的表情符号目录列表*/
QStringList loadDefaultPaths()
{const QString EMOTICONS_SUB_PATH = QDir::separator() + QStringLiteral("emoticons");QStringList paths{":/smileys", "~/.kde4/share/emoticons", "~/.kde/share/emoticons",EMOTICONS_SUB_PATH};// 表情符号QStandardPaths::StandardLocation location;location = QStandardPaths::AppDataLocation;QStringList locations = QStandardPaths::standardLocations(location);// 系统范围表情符号locations.append(QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation));for (QString qtoxPath : locations) {qtoxPath.append(EMOTICONS_SUB_PATH);if (!paths.contains(qtoxPath)) {paths.append(qtoxPath);}}return paths;
}/*** @brief 把传递的字符串变成笑脸HTML图像引用* @param key指需要哪个笑脸* @return 包装成图像的键*/
QString getAsRichText(const QString& key)
{return RICH_TEXT_PATTERN.arg(key);
}SmileyPack::SmileyPack(): cleanupTimer{new QTimer(this)}
{loadingMutex.lock();smileyPack = ":/smileys/Universe/emoticons.xml";QtConcurrent::run(this, &SmileyPack::load, smileyPack);connect(cleanupTimer, &QTimer::timeout, this, &SmileyPack::cleanupIconsCache);cleanupTimer->start(CLEANUP_TIMEOUT);
}SmileyPack::~SmileyPack()
{delete cleanupTimer;
}// 清除Icon缓存
void SmileyPack::cleanupIconsCache()
{QMutexLocker locker(&loadingMutex);for (auto it = cachedIcon.begin(); it != cachedIcon.end();) {std::shared_ptr<QIcon>& icon = it->second;if (icon.use_count() == 1) {it = cachedIcon.erase(it);} else {++it;}}
}/*** @brief 返回单例实例*/
SmileyPack& SmileyPack::getInstance()
{static SmileyPack smileyPack;return smileyPack;
}QList<QPair<QString, QString>> SmileyPack::listSmileyPacks()
{return listSmileyPacks(DEFAULT_PATHS);
}/*** @brief 在每个传递的路径中以2的深度搜索所有名为"emoticons.xml"的文件* @param paths 搜索文件的路径* @return Vector of pairs: {directoryName, absolutePathToFile}*/
QList<QPair<QString, QString>> SmileyPack::listSmileyPacks(const QStringList& paths)
{QList<QPair<QString, QString>> smileyPacks;const QString homePath = QDir::homePath();for (QString path : paths) {if (path.startsWith('~')) {path.replace(0, 1, homePath);}QDir dir(path);if (!dir.exists()) {continue;}for (const QString& subdirectory : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {dir.cd(subdirectory);if (dir.exists(EMOTICONS_FILE_NAME)) {QString absPath = dir.absolutePath() + QDir::separator() + EMOTICONS_FILE_NAME;QPair<QString, QString> p{dir.dirName(), absPath};if (!smileyPacks.contains(p)) {smileyPacks.append(p);}}dir.cdUp();}}return smileyPacks;
}/*** @brief 加载笑脸包* @note 调用者必须锁定loadingMutex并在线程中运行它* @param filename 笑脸包文件名* @return 如果不能打开文件,则为false,否则为true。*/
bool SmileyPack::load(const QString& filename)
{QFile xmlFile(filename);if (!xmlFile.exists() || !xmlFile.open(QIODevice::ReadOnly)) {loadingMutex.unlock();return false;}QDomDocument doc;doc.setContent(xmlFile.readAll());xmlFile.close();/* 解析cfg文件* 例子:* <?xml version='1.0'?>* <messaging-emoticon-map>*   <emoticon file="smile.png" >*       <string>:)</string>*       <string>:-)</string>*   </emoticon>*   <emoticon file="sad.png" >*       <string>:(</string>*       <string>:-(</string>*   </emoticon>* </messaging-emoticon-map>*/path = QFileInfo(filename).absolutePath();QDomNodeList emoticonElements = doc.elementsByTagName("emoticon");const QString itemName = QStringLiteral("file");const QString childName = QStringLiteral("string");const int iconsCount = emoticonElements.size();emoticons.clear();emoticonToPath.clear();cachedIcon.clear();for (int i = 0; i < iconsCount; ++i) {QDomNode node = emoticonElements.at(i);QString iconName = node.attributes().namedItem(itemName).nodeValue();QString iconPath = QDir{path}.filePath(iconName);QDomElement stringElement = node.firstChildElement(childName);QStringList emoticonList;while (!stringElement.isNull()) {QString emoticon = stringElement.text().replace("<", "&lt;").replace(">", "&gt;");emoticonToPath.insert(emoticon, iconPath);emoticonList.append(emoticon);stringElement = stringElement.nextSibling().toElement();}emoticons.append(emoticonList);}loadingMutex.unlock();return true;
}/*** @brief 用其相应的图标文件名将所有找到的文本表情符号替换为HTML引用* @param msg 提示在哪里搜索表情符号* @return 消息的格式化副本*/
QString SmileyPack::smileyfied(const QString& msg)
{QMutexLocker locker(&loadingMutex);QString result(msg);for ( auto r = emoticonToPath.begin(); r != emoticonToPath.end(); ++r) {QRegularExpression exp;if (r.key().toUcs4().length() == 1) {// UTF-8 emojiexp.setPattern(r.key());}else {// 像" :)"或":smile:"之类的模式,在单词内不匹配,否则会打标点和html标签exp.setPattern(QStringLiteral(R"((?<=^|\s))") + QRegularExpression::escape(r.key()) + QStringLiteral(R"((?=$|\s))"));}int replaceDiff = 0;QRegularExpressionMatchIterator iter = exp.globalMatch(result);while (iter.hasNext()) {QRegularExpressionMatch match = iter.next();int startPos = match.capturedStart();int keyLength = r.key().length();QString imgRichText = getAsRichText(r.key());result.replace(startPos + replaceDiff, keyLength, imgRichText);replaceDiff += imgRichText.length() - keyLength;}}return result;
}/*** @brief 返回从文件中提取的所有表情符号,按图标文件分组*/
QList<QStringList> SmileyPack::getEmoticons() const
{QMutexLocker locker(&loadingMutex);return emoticons;
}/*** @brief 根据传递的emoticon获取图标* @param emoticon* @return 根据传递的emoticon返回缓存的图标,如果没有映射到该emoticon的图标,则返回null*/
std::shared_ptr<QIcon> SmileyPack::getAsIcon(const QString& emoticon) const
{QMutexLocker locker(&loadingMutex);if (cachedIcon.find(emoticon) != cachedIcon.end()) {return cachedIcon[emoticon];}const auto iconPathIt = emoticonToPath.find(emoticon);if (iconPathIt == emoticonToPath.end()) {return std::make_shared<QIcon>();}const QString& iconPath = iconPathIt.value();auto icon = std::make_shared<QIcon>(iconPath);cachedIcon[emoticon] = icon;return icon;
}void SmileyPack::onSmileyPackChanged(const QString& path)
{loadingMutex.lock();QtConcurrent::run(this, &SmileyPack::load, path);
}

SmileyPack.h

#ifndef SMILEYPACK_H
#define SMILEYPACK_H#include <QIcon>
#include <QMap>
#include <QMutex>#include <memory>class QTimer;class SmileyPack : public QObject
{Q_OBJECTpublic:static SmileyPack& getInstance();static QList<QPair<QString, QString>> listSmileyPacks(const QStringList& paths);static QList<QPair<QString, QString>> listSmileyPacks();QString smileyfied(const QString& msg);QList<QStringList> getEmoticons() const;std::shared_ptr<QIcon> getAsIcon(const QString& key) const;public slots:void onSmileyPackChanged(const QString& path);private slots:void cleanupIconsCache();private:SmileyPack();SmileyPack(SmileyPack&) = delete;SmileyPack& operator=(const SmileyPack&) = delete;~SmileyPack() override;bool load(const QString& filename);mutable std::map<QString, std::shared_ptr<QIcon>> cachedIcon;QHash<QString, QString> emoticonToPath;QList<QStringList> emoticons;QString path;QTimer* cleanupTimer;mutable QMutex loadingMutex;QString smileyPack;
};#endif // SMILEYPACK_H

Widget实现调用表情包的一个简单例子。

#include "widget.h"
#include "ui_widget.h"#include "EmoticonsWidget.h"
#include "SmileyPack.h"Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);ui->comboBox->addItem(QStringLiteral("表情包1"));ui->comboBox->addItem(QStringLiteral("表情包2"));ui->comboBox->addItem(QStringLiteral("表情包3"));ui->comboBox->addItem(QStringLiteral("表情包4"));ui->comboBox->addItem(QStringLiteral("表情包5"));ui->comboBox->addItem(QStringLiteral("表情包6"));connect(ui->comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onIndexChanged(int)));connect(this, SIGNAL(smileyChanged(const QString&)), &SmileyPack::getInstance(), SLOT(onSmileyPackChanged(const QString&)));
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{// 是否有表情包if (SmileyPack::getInstance().getEmoticons().empty())return;EmoticonsWidget widget;connect(&widget, SIGNAL(insertEmoticon(QString)), this, SLOT(onEmoteInsertRequested(QString)));widget.installEventFilter(this);QWidget* sender = qobject_cast<QWidget*>(QObject::sender());if (sender) {QPoint pos =-QPoint(widget.sizeHint().width() / 2, widget.sizeHint().height()) - QPoint(0, 10);widget.exec(sender->mapToGlobal(pos));}
}void Widget::onEmoteInsertRequested(QString str)
{// 插入表情符号QWidget* sender = qobject_cast<QWidget*>(QObject::sender());if (sender)ui->textEdit->insertPlainText(str);ui->textEdit->setFocus(); // 重新对焦,这样我们就可以继续打字了
}void Widget::onIndexChanged(int index)
{QString smiley = ":/smileys/Universe/emoticons.xml";if (index == 0){smiley = ":/smileys/Universe/emoticons.xml";}else if (index == 1)smiley = ":/smileys/emojione/emoticons.xml";else if (index == 2)smiley = ":/smileys/Classic/emoticons.xml";else if (index == 3)smiley = ":/smileys/Basic/emoticons.xml";else if (index == 4)smiley = ":/smileys/ASCII+Universe/emoticons.xml";else if (index == 5)smiley = ":/smileys/ASCII+emojione/emoticons.xml";emit smileyChanged(smiley);
}

Qt 实现聊天软件中自定义表情包(随笔记录)相关推荐

  1. 有什么好用的gif制作软件 制作GIF表情包教程

    说到gif,我们应该都不会陌生,平时网上聊天经常会用到各式各样的gif 动图,每个人喜欢的类型也都有所不同,曾经gif刚出来的时候,有一股斗图风盛行,那说了这么多,你知道如何制作gif动图,有什么好用 ...

  2. 小程序源码:聊天斗图微信表情包

    这是一款微信表情包小程序 支持自定义搜索,另外支持长按发送给好友 当然也支持长按保存表情包 这个源码比较单调一点,没有那么多的分类 小程序源码下载地址: 小程序源码:聊天斗图微信表情包-小程序文档类资 ...

  3. 自制表情包!android,diy表情包制作软件下载-diy表情包 安卓版v2.6.0-PC6安卓网

    diy表情包app是一款表情包制作软件,diy表情包制作软件精选大量无字表情包模板且可添加个人图片,diy表情包软件为你打造专属于你自己的魔性表情包! 软件介绍 手机qq表情包diy可以在大量自带的表 ...

  4. 关于友盟IM自定义表情包的使用

    介绍 友盟的IM是支持自定义表情的,在友盟IM自定义表情包里面,是有一个淘公仔的表情包,为了我们可以自己添加自己的的表情包,我们先看一下友盟提供的淘公仔表情包的相关文件配置. 首先我们先看一下自定义表 ...

  5. 聊天软件中的窗口上滑和下滑提示上下线

    聊天软件中右下角窗口上滑提示有好友上线,窗口下滑提示有好友下线. 在 Qt 下实现此功能,用到的类有 QPoint  QTimer mainwindow.h 1 #ifndef MAINWINDOW_ ...

  6. 小程序源码:聊天斗图微信表情包-多玩法安装简单

    这是一款微信表情包小程序 支持自定义搜索,另外支持长按发送给好友 当然也支持长按保存表情包 这个源码比较单调一点,没有那么多的分类 小程序源码下载地址: 小程序源码:聊天斗图微信表情包-多玩法安装简单 ...

  7. AM8 自定义表情包的实现方法

    AM8 自定义表情包的实现方法 效果描述 AM8 安装后,在\Activesoft\AMm8\emotions 目录内存储的是默认的表情符号.但有的时候我们需要增加一些新的表情符号,AM8 系统支持自 ...

  8. android 自定义表情包,android基于环信的聊天和表情自定义

    环信sdk的导入 自定义聊天界面 此处只有静态图,请谅解. 自定义表情发送 自定义聊天界面 简单说下自定义的聊天界面,一个带有recyclerview和的xml文件,和对应的adapter即可.rec ...

  9. 基于Qt的聊天软件设计实现手把手教学——高仿QQUI设计(一)

    文章目录 前言 一.使用工具 1. Qt Creator 二.项目介绍 1.客户端 1.1 Socket套接字 2.服务端 2.1 数据库 2.2 数据处理 3.效果图 4.总体系统架构图 小结 前言 ...

最新文章

  1. ESPNet系列:自动驾驶领域轻量级分割模型
  2. 2015年美军将具备60分钟内打击全球目标能力(图)
  3. 蓝桥杯练习系统习题-算法训练1
  4. bugzilla perl mysql apache windows,windows下apache安装bugzilla
  5. 双主双从(2m-2s)集群介绍和工作流程说明
  6. 达拉斯大学计算机硕士专业排名,美国大学研究生专业排名:人机交互
  7. 不常用却很有妙用的事件及方法
  8. pycharm快捷键大全
  9. sqlserver 提示“用户sa 登录失败 18456”问题解决过程
  10. 秋天是一个思念的季节
  11. 小鸟云服务器FTP上传中断是什么原因?解决方法总结
  12. oracle分门别类的统计列数据
  13. JsonEquals - JSON 差异比较工具的使用
  14. 智能指针(一)—— 智能指针的底层原理(RAII特性)
  15. win7计算机桌面快捷键显示,Win7的显示桌面在哪 Win7显示桌面快捷键是什么
  16. 3.1-图像分割引言
  17. C++一维数组5只小猪称体重(比较数值)
  18. 计算机网络基础之表示层的功能和服务
  19. “水火”之间:投资与投机
  20. 【原创】关于视频播放器如何做到边播边缓存?【如何用Vitamio做一款功能强大的视频播放器补充篇】

热门文章

  1. clearInterval清除定时器失效的原因
  2. 软考高项 : (12)论项目风险管理
  3. Cannot creat oci environment.
  4. 13 Linux下的基础IO
  5. LevelDB的sstable解读
  6. JavaWeb04(验证码登录新闻增加)
  7. 清理桌面右键新建菜单项
  8. NYOJ-6-喷水装置(一)
  9. wordpress博客搬家操作教程详解
  10. 穿孔纸带 Punched tape