Files
MyChat_Client/mainwidget.cpp
2025-06-17 18:40:02 +08:00

533 lines
19 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "mainwidget.h"
#include "./ui_mainwidget.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include "sessiondetailwidget.h"
MainWidget *MainWidget::instance = nullptr;
MainWidget *MainWidget::getInstance()
{
if(instance == nullptr) {
//此处不传入参数,以桌面为父窗口
//
instance = new MainWidget();
}
return instance;
}
MainWidget::MainWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::MainWidget)
{
ui->setupUi(this);
// this->setStyleSheet("MainWidget {min-height: 6000px; }");
this->resize(QSize(1280, 800));
this->setWindowTitle("MyChat");
this->setWindowIcon(QIcon(":/resource/image/logo.png"));
//初始化主窗口的样式布局
initMainWindow();
//初始化左侧窗口的样式布局
initLeftWindow();
//初始化中间窗口的样式布局
initMidWindow();
//初始化右侧窗口的样式布局
initRightWindow();
//初始化信号槽
initSignalSlot();
}
MainWidget::~MainWidget()
{
delete ui;
}
void MainWidget::initMainWindow()
{
QHBoxLayout* layout = new QHBoxLayout();
//layout内部的元素间隔设置为0
layout->setSpacing(0);
//layout内部元素四个方向边界的距离设置
layout->setContentsMargins(0, 0, 0, 0);
this->setLayout(layout);
windowLeft = new QWidget();
windowMid = new QWidget();
windowRight = new QWidget();
windowLeft->setFixedWidth(70);
windowMid->setFixedWidth(320);
windowRight->setMinimumWidth(600);
windowLeft->setStyleSheet("QWidget { background-color: rgb(46, 46, 46); }");
windowMid->setStyleSheet("QWidget { background-color: rgb(247, 247, 247); }");
windowRight->setStyleSheet("QWidget { background-color: rgb(245, 245, 245); }");
layout->addWidget(windowLeft);
layout->addWidget(windowMid);
layout->addWidget(windowRight);
}
void MainWidget::initLeftWindow()
{
QVBoxLayout* layout = new QVBoxLayout();
layout->setSpacing(20);
layout->setContentsMargins(0, 50, 0, 0);
windowLeft->setLayout(layout);
//添加用户头像
userAvatar = new QPushButton();
userAvatar->setFixedSize(45, 45);
userAvatar->setIconSize(QSize(45, 45));
//由于要从网络获取头像信息,所以这里就不能再设置默认的头像了,;避免头像意外变化
//userAvatar->setIcon(QIcon(":/resource/image/defaultAv.png"));
userAvatar->setStyleSheet("QPushButton {background-color: transparent; }");
layout->addWidget(userAvatar, 1, Qt::AlignTop | Qt::AlignCenter);
//添加会话标签页按钮
sessionTabBtn = new QPushButton();
sessionTabBtn->setFixedSize(45, 45);
sessionTabBtn->setIconSize(QSize(35, 35));
sessionTabBtn->setIcon(QIcon(":/resource/image/session_active.png"));
sessionTabBtn->setStyleSheet("QPushButton {background-color: transparent; }");
layout->addWidget(sessionTabBtn, 1, Qt::AlignTop | Qt::AlignCenter);
//添加好友标签页按钮
friendTabBtn = new QPushButton();
friendTabBtn->setFixedSize(45, 45);
friendTabBtn->setIconSize(QSize(35, 35));
friendTabBtn->setIcon(QIcon(":/resource/image/friend_inactive.png"));
friendTabBtn->setStyleSheet("QPushButton {background-color: transparent; }");
layout->addWidget(friendTabBtn, 1, Qt::AlignTop | Qt::AlignCenter);
//添加好友申请标签页按钮
applyTabBtn = new QPushButton();
applyTabBtn->setFixedSize(45, 45);
applyTabBtn->setIconSize(QSize(35, 35));
applyTabBtn->setIcon(QIcon(":/resource/image/apply_inactive.png"));
applyTabBtn->setStyleSheet("QPushButton {background-color: transparent; }");
layout->addWidget(applyTabBtn, 1, Qt::AlignTop | Qt::AlignCenter);
layout->addStretch(20);
}
void MainWidget::initMidWindow()
{
QGridLayout* layout = new QGridLayout();
//距离上方有20px另外的三个方向都不要边距
layout->setContentsMargins(0, 20, 0, 0);
layout->setSpacing(0);
windowMid->setLayout(layout);
searchEdit = new QLineEdit();
searchEdit->setFixedHeight(30);
searchEdit->setPlaceholderText("搜索");
searchEdit->setStyleSheet("QLineEdit { border-radius: 5px; color: rgb(129, 129, 129); background-color: rgb(226, 226, 226); padding-left: 5px; }");
addFriendBtn = new QPushButton();
addFriendBtn->setFixedSize(30, 30);
addFriendBtn->setIcon(QIcon(":/resource/image/cross.png"));
QString style = R"(QPushButton { border-radius: 5px; background-color: rgb(226, 226, 226); }
QPushButton::pressed { background-color: rgb(240, 240, 240); })";
addFriendBtn->setStyleSheet(style);
sessionFriendArea = new SessionFriendArea();
//为了更加灵活的控制边距,只影响搜索框和按钮的这一行,
//创建空白的widget填充到布局管理器上
QWidget* spacer1 = new QWidget();
spacer1->setFixedWidth(10);
QWidget* spacer2 = new QWidget();
spacer2->setFixedWidth(10);
QWidget* spacer3 = new QWidget();
spacer3->setFixedWidth(10);
QWidget* spacer4 = new QWidget();
spacer4->setFixedHeight(10);
layout->addWidget(spacer1, 0, 0);
layout->addWidget(searchEdit, 0, 1);
layout->addWidget(spacer2, 0, 2);
layout->addWidget(addFriendBtn, 0 ,3);
layout->addWidget(spacer3, 0, 4);
layout->addWidget(spacer4, 1, 0, 1, 5);
layout->addWidget(sessionFriendArea, 2, 0, 1, 5);
}
void MainWidget::initRightWindow()
{
//创建右侧窗口的布局管理器
QVBoxLayout* vlayout = new QVBoxLayout();
vlayout->setSpacing(0);
vlayout->setContentsMargins(0, 0, 0, 0);
vlayout->setAlignment(Qt::AlignTop);
windowRight->setLayout(vlayout);
//创建上方的标题栏
QWidget* titleWidget = new QWidget();
titleWidget->setFixedHeight(62);
titleWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
titleWidget->setObjectName("titleWidget");
titleWidget->setStyleSheet(R"(#titleWidget { border-bottom: 2px solid rgb(231, 231, 231);
border-left: 1px solid rgb(231, 231, 231); }
)");
vlayout->addWidget(titleWidget);
//给标题栏添加 label 和 button
QHBoxLayout* hlayout = new QHBoxLayout();
hlayout->setSpacing(0);
hlayout->setContentsMargins(10, 0, 10, 0);
titleWidget->setLayout(hlayout);
sessionTitleLabel = new QLabel();
sessionTitleLabel->setStyleSheet("QLabel { font-size: 22px; border-bottom: 1px solid rgb(230, 230, 230); }");
#if TEST_UI
//为了测试仅填充从服务器获取的数据
sessionTitleLabel->setText("TEST TITLE");
#endif
hlayout->addWidget(sessionTitleLabel);
extraBtn = new QPushButton();
extraBtn->setFixedSize(30, 30);
extraBtn->setIconSize(QSize(30, 30));
extraBtn->setIcon(QIcon(":/resource/image/more.png"));
extraBtn->setStyleSheet(R"(QPushButton { border: none; background-color: transparent; }
QPushButton:pressed { background-color: rgb(210, 210 ,210); }
)");
hlayout->addWidget(extraBtn);
//添加消息展示区
messageShowArea = new MessageShowArea();
vlayout->addWidget(messageShowArea);
//添加消息编辑区
messageEditArea = new MessageEditArea();
//确保消息编辑区处于窗口正下方
vlayout->addWidget(messageEditArea, 0, Qt::AlignBottom);
}
void MainWidget::initSignalSlot()
{
model::DataCenter* dataCenter = model::DataCenter::getInstance();
/////////////////////////////////////
//连接信号槽,处理标签页切换
/////////////////////////////////////
connect(sessionTabBtn, &QPushButton::clicked, this, &MainWidget::switchTabToSession);
connect(friendTabBtn, &QPushButton::clicked, this, &MainWidget::switchTabToFriend);
connect(applyTabBtn, &QPushButton::clicked, this, &MainWidget::switchTabToApply);
/////////////////////////////////////
// 点击自己的头像,弹出对话框显示个人的主页
/////////////////////////////////////
connect(userAvatar, &QPushButton::clicked, this, [=]()->void {
SelfInfoWidget* selfInfoWidget = new SelfInfoWidget(this);
selfInfoWidget->setStyleSheet("QDialog { background-color: rgb(245, 245, 245); }");
selfInfoWidget->exec(); //弹出模态对话框
//selfInfoWidget->show(); //弹出非模态对加框
});
/////////////////////////////////////
// 点击右上角的更多按钮,扩展会话的详细信息
/////////////////////////////////////
connect(extraBtn, &QPushButton::clicked, this, [=]() {
//判定当前的会话是单聊还是群聊
#if TEST_GROUP_SESSION_DETAIL
bool isSingleChat = false; //要根据当前选中的实际的会话来确定
#else
bool isSingleChat = true; //要根据当前选中的实际的会话来确定
#endif
if (isSingleChat) {
//说明是单聊
SessionDetailWidget* sessionDetailWidget = new SessionDetailWidget(this);
sessionDetailWidget->exec();
}
else {
//说明是群聊
GroupSessionDetailWidget* groupSessionDetailWidget = new GroupSessionDetailWidget(this);
groupSessionDetailWidget->exec();
}
});
/////////////////////////////////////
// 点击添加好友按钮,弹出添加好友的窗口
/////////////////////////////////////
connect(addFriendBtn, &QPushButton::clicked, this, [=]() {
AddFriendDialog* addFriendDialog = new AddFriendDialog(this);
addFriendDialog->exec();
});
/////////////////////////////////////
// 修改搜索框内容,设置到新弹出的输入框里面
/////////////////////////////////////
connect(searchEdit, &QLineEdit::textEdited, this, [=]() {
const QString& searchKey = searchEdit->text();
AddFriendDialog* addFriendDialog = new AddFriendDialog(this);
addFriendDialog->setSearchKey(searchKey);
searchEdit->setText("");
addFriendDialog->exec();
});
/////////////////////////////////////
// 获取个人的信息
/////////////////////////////////////
//提供一个具体的方法,来获取到网络的数据
connect(dataCenter, &DataCenter::getMyselfDone, this, [=]() {
//从DataCenter中拿到响应的结果myself把里面的头像取出来显示到界面上
//由于响应的逻辑已经执行完毕,说明从网络拿到了信息,则直接同步获取即可
auto myself = dataCenter->getMyselfsync();
userAvatar->setIcon(myself->avatar);
});
dataCenter->getMyselfAsync();
/////////////////////////////////////
// 获取好友列表
/////////////////////////////////////
loadFriendList();
/////////////////////////////////////
// 获取会话列表
/////////////////////////////////////
loadSessionList();
/////////////////////////////////////
// 获取好友申请的列表
/////////////////////////////////////
loadApplyList();
}
void MainWidget::switchTabToSession()
{
//记录当前切换到了哪个标签页
activeTab = SESSION_LIST;
//调整图标显示情况
sessionTabBtn->setIcon(QIcon(":/resource/image/session_active.png"));
friendTabBtn->setIcon(QIcon(":/resource/image/friend_inactive.png"));
applyTabBtn->setIcon(QIcon(":/resource/image/apply_inactive.png"));
// 在主窗口的中间部分,加载出会话列表数据
this->loadSessionList();
}
void MainWidget::switchTabToFriend()
{
//记录当前切换到了哪个标签页
activeTab = FRIEND_LIST;
//调整图标显示情况
sessionTabBtn->setIcon(QIcon(":/resource/image/session_inactive.png"));
friendTabBtn->setIcon(QIcon(":/resource/image/friend_active.png"));
applyTabBtn->setIcon(QIcon(":/resource/image/apply_inactive.png"));
// 在主窗口的中间部分,加载出会话列表数据
this->loadFriendList();
}
void MainWidget::switchTabToApply()
{
//记录当前切换到了哪个标签页
activeTab = APPLY_LIST;
//调整图标显示情况
sessionTabBtn->setIcon(QIcon(":/resource/image/session_inactive.png"));
friendTabBtn->setIcon(QIcon(":/resource/image/friend_inactive.png"));
applyTabBtn->setIcon(QIcon(":/resource/image/apply_active.png"));
// 在主窗口的中间部分,加载出会话列表数据
this->loadApplyList();
}
//加载会话列表
void MainWidget::loadSessionList()
{
//会判定会话是否存在本地的DataCenter中若存在则直接构造界面
//若不存在则从服务器获取数据
DataCenter* dataCenter = DataCenter::getInstance();
if (dataCenter->getChatSessionList() != nullptr) {
//本地内存中获取数据
LOG() << "本地已存在会话列表数据,更新加载本地会话";
updateChatSessionList();
}
else {
// 从网路中加载数据
LOG() << "本地会话列表数据不存在,正在从网络获取...";
connect(dataCenter, &DataCenter::getChatSessionListDone, this, &MainWidget::updateChatSessionList, Qt::UniqueConnection);
dataCenter->getChatSessionListAsync();
}
}
//加载好友列表
void MainWidget::loadFriendList()
{
//好友列表数据是在DataCenter中存储的
//首先需要判定DataCenter中是否已经有数据了如果有则直接加载本地数据
//如果没有,则从服务器读取
DataCenter* dataCenter = DataCenter::getInstance();
if (dataCenter->getFriendList() != nullptr) {
//从内存中加载数据
LOG() << "本地已存在好友列表数据,更新加载本地好友";
updateFriendList();
}
else {
//通过网络加载数据 通过这个参数确保信号槽不会别重复绑定
LOG() << "本地好友列表数据不存在,正在从网络获取...";
connect(dataCenter, &DataCenter::getFriendListDone, this, &MainWidget::updateFriendList, Qt::UniqueConnection);
dataCenter->getFriendListAsync();
}
}
//加载申请列表
void MainWidget::loadApplyList()
{
//好友申请列表在DataCenter中存储
//同理首先需要判定DataCenter中是否已经有数据了如果有则直接加载本地数据
//如果没有,则从服务器读取
DataCenter* dataCenter = DataCenter::getInstance();
if (dataCenter->getApplyList() != nullptr) {
//从内存中加载数据
LOG() << "本地已存在用户申请列表数据,更新加载申请列表";
updateApplyList();
}
else {
//通过网络加载数据
LOG() << "用户申请列表数据不存在,正在从网络获取...";
connect(dataCenter, &DataCenter::getApplyListDone, this, &MainWidget::updateApplyList, Qt::UniqueConnection);
dataCenter->getApplyListAsync();
}
}
void MainWidget::updateFriendList()
{
if (activeTab != FRIEND_LIST) {
//当前的标签页不是好友列表,就不渲染任何数据到界面上
return;
}
DataCenter* dataCenter = DataCenter::getInstance();
QList<UserInfo>* friendList = dataCenter->getFriendList();
//清空一下之前的界面的数据
sessionFriendArea->clear();
//遍历好友列表,添加到界面上
for (const auto& f : *friendList) {
sessionFriendArea->addItem(FriendItemType, f.userId, f.avatar, f.nickname, f.description);
}
}
void MainWidget::updateChatSessionList()
{
if (activeTab != SESSION_LIST) {
//当前的标签页不是会话列表,不渲染任何数据到界面上
return;
}
DataCenter* dataCenter = DataCenter::getInstance();
QList<ChatSessionInfo>* chatSessionList = dataCenter->getChatSessionList();
sessionFriendArea->clear();
for (const auto& c : *chatSessionList) {
switch (c.lastMessage.messageType)
{
case TEXT_TYPE:
sessionFriendArea->addItem(SessionItemType, c.chatSessionId, c.avatar, c.chatSessionName, c.lastMessage.content);
break;
case IMAGE_TYPE:
sessionFriendArea->addItem(SessionItemType, c.chatSessionId, c.avatar, c.chatSessionName, "[图片]");
break;
case FILE_TYPE:
sessionFriendArea->addItem(SessionItemType, c.chatSessionId, c.avatar, c.chatSessionName, "[文件]");
break;
case SPEECH_TYPE:
sessionFriendArea->addItem(SessionItemType, c.chatSessionId, c.avatar, c.chatSessionName, "[语音]");
break;
default:
LOG() << "updateSessionList 收到了错误的“最后一条消息的类型”";
break;
}
}
}
void MainWidget::updateApplyList()
{
if (activeTab != APPLY_LIST) {
//当前的标签页不是“好友申请列表”
return;
}
DataCenter* dataCenter = DataCenter::getInstance();
QList<UserInfo>* applyList = dataCenter->getApplyList();
sessionFriendArea->clear();
for (const auto& u : *applyList) {
//直接把这个UserInfo对象添加到界面之上不涉及PB和自己结构的转换
//此处的UserInfo的desc不需要填写进来好友的申请列表中 不显示用户的标签
sessionFriendArea->addItem(ApplyItemType, u.userId, u.avatar, u.nickname, "");
}
}
void MainWidget::loadRecentMessage(const QString& chatSessionId)
{
//也是先判定,本地内存中是否已经存在对应的消息列表数据
//有的话直接显示到界面之上,没有的话从网络中获取
DataCenter* dataCenter = DataCenter::getInstance();
if (dataCenter->getRecentMessageList(chatSessionId) != nullptr) {
//直接拿着本地数据更新界面
LOG() << "本地已存在好友聊天数据,更新加载好友聊天数据";
updateRecentMessage(chatSessionId);
}
else {
//本地没有数据,从网络加载
LOG() << "好友聊天数据不存在,正在从网络获取...";
connect(dataCenter, &DataCenter::getRecentMessageListDone, this, &MainWidget::updateRecentMessage, Qt::UniqueConnection);
dataCenter->getRecnetMessageListAsync(chatSessionId);
}
}
void MainWidget::updateRecentMessage(const QString& chatSessionId)
{
//拿到该会话最近的消息列表
DataCenter* dataCenter = DataCenter::getInstance();
auto* recentMessageList = dataCenter->getRecentMessageList(chatSessionId);
//清空原有界面上的消息列表
messageShowArea->clear();
//根据当前拿到的消息列表,显示到界面上
//此处把数据显示到界面上,可以使用头插也可以尾插
//对于消息列表来说,用户最先看到的应该是最近的消息,也就是末尾的消息
for (int i = recentMessageList->size() - 1; i >= 0; i--) {
const Message& message = recentMessageList->at(i);
bool isLeft = message.sender.userId != dataCenter->getMyselfsync()->userId;
messageShowArea->addFrontMessage(isLeft, message);
}
//设置会话标题
ChatSessionInfo* chatSessionInfo = dataCenter->findChatSessionById(chatSessionId);
if (chatSessionInfo != nullptr) {
//把会话名称显示到界面之上
sessionTitleLabel->setText(chatSessionInfo->chatSessionName);
}
//保存当前选中的会话是哪个
dataCenter->setCurrentChatSessionId(chatSessionId);
//自动把滚动条滚动到末尾
messageShowArea->scrollToEnd();
}