Some basic message painting

This commit is contained in:
Blue 2020-08-21 00:32:30 +03:00
parent e1eea2f3a2
commit e0ef1ef797
16 changed files with 296 additions and 44 deletions

View File

@ -40,6 +40,7 @@ set(squawkUI_SRC
utils/comboboxdelegate.cpp utils/comboboxdelegate.cpp
utils/dropshadoweffect.cpp utils/dropshadoweffect.cpp
utils/feedview.cpp utils/feedview.cpp
utils/messagedelegate.cpp
) )
# Tell CMake to create the helloworld executable # Tell CMake to create the helloworld executable

View File

@ -231,7 +231,7 @@ void Models::Account::toOfflineState()
Item::toOfflineState(); Item::toOfflineState();
} }
QString Models::Account::getAvatarPath() QString Models::Account::getAvatarPath() const
{ {
return avatarPath; return avatarPath;
} }

View File

@ -57,7 +57,7 @@ namespace Models {
QString getError() const; QString getError() const;
void setAvatarPath(const QString& path); void setAvatarPath(const QString& path);
QString getAvatarPath(); QString getAvatarPath() const;
void setAvailability(Shared::Availability p_avail); void setAvailability(Shared::Availability p_avail);
void setAvailability(unsigned int p_avail); void setAvailability(unsigned int p_avail);

View File

@ -27,7 +27,7 @@ Models::Element::Element(Type p_type, const Models::Account* acc, const QString&
avatarPath(), avatarPath(),
avatarState(Shared::Avatar::empty), avatarState(Shared::Avatar::empty),
account(acc), account(acc),
feed(new MessageFeed()) feed(new MessageFeed(this))
{ {
connect(feed, &MessageFeed::requestArchive, this, &Element::requestArchive); connect(feed, &MessageFeed::requestArchive, this, &Element::requestArchive);
@ -149,3 +149,8 @@ void Models::Element::responseArchive(const std::list<Shared::Message> list)
{ {
feed->responseArchive(list); feed->responseArchive(list);
} }
bool Models::Element::isRoom() const
{
return type != contact;
}

View File

@ -42,6 +42,7 @@ public:
void changeMessage(const QString& id, const QMap<QString, QVariant>& data); void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
unsigned int getMessagesCount() const; unsigned int getMessagesCount() const;
void responseArchive(const std::list<Shared::Message> list); void responseArchive(const std::list<Shared::Message> list);
bool isRoom() const;
signals: signals:
void requestArchive(const QString& before); void requestArchive(const QString& before);

View File

@ -283,6 +283,15 @@ Shared::ConnectionState Models::Item::getAccountConnectionState() const
return acc->getState(); return acc->getState();
} }
QString Models::Item::getAccountAvatarPath() const
{
const Account* acc = getParentAccount();
if (acc == nullptr) {
return "";
}
return acc->getAvatarPath();
}
QString Models::Item::getDisplayedName() const QString Models::Item::getDisplayedName() const
{ {
return name; return name;

View File

@ -80,6 +80,7 @@ class Item : public QObject{
QString getAccountName() const; QString getAccountName() const;
QString getAccountJid() const; QString getAccountJid() const;
QString getAccountResource() const; QString getAccountResource() const;
QString getAccountAvatarPath() const;
Shared::ConnectionState getAccountConnectionState() const; Shared::ConnectionState getAccountConnectionState() const;
Shared::Availability getAccountAvailability() const; Shared::Availability getAccountAvailability() const;

View File

@ -17,35 +17,39 @@
*/ */
#include "messagefeed.h" #include "messagefeed.h"
#include "element.h"
#include "room.h"
#include <QDebug> #include <QDebug>
const QHash<int, QByteArray> MessageFeed::roles = { const QHash<int, QByteArray> Models::MessageFeed::roles = {
{Text, "text"}, {Text, "text"},
{Sender, "sender"}, {Sender, "sender"},
{Date, "date"}, {Date, "date"},
{DeliveryState, "deliveryState"}, {DeliveryState, "deliveryState"},
{Correction, "correction"}, {Correction, "correction"},
{SentByMe,"sentByMe"} {SentByMe,"sentByMe"},
{Avatar, "avatar"}
}; };
MessageFeed::MessageFeed(QObject* parent): Models::MessageFeed::MessageFeed(const Element* ri, QObject* parent):
QAbstractListModel(parent), QAbstractListModel(parent),
storage(), storage(),
indexById(storage.get<id>()), indexById(storage.get<id>()),
indexByTime(storage.get<time>()), indexByTime(storage.get<time>()),
rosterItem(ri),
syncState(incomplete) syncState(incomplete)
{ {
} }
MessageFeed::~MessageFeed() Models::MessageFeed::~MessageFeed()
{ {
for (Shared::Message* message : storage) { for (Shared::Message* message : storage) {
delete message; delete message;
} }
} }
void MessageFeed::addMessage(const Shared::Message& msg) void Models::MessageFeed::addMessage(const Shared::Message& msg)
{ {
QString id = msg.getId(); QString id = msg.getId();
StorageById::const_iterator itr = indexById.find(id); StorageById::const_iterator itr = indexById.find(id);
@ -67,15 +71,15 @@ void MessageFeed::addMessage(const Shared::Message& msg)
endInsertRows(); endInsertRows();
} }
void MessageFeed::changeMessage(const QString& id, const Shared::Message& msg) void Models::MessageFeed::changeMessage(const QString& id, const Shared::Message& msg)
{ {
} }
void MessageFeed::removeMessage(const QString& id) void Models::MessageFeed::removeMessage(const QString& id)
{ {
} }
QVariant MessageFeed::data(const QModelIndex& index, int role) const QVariant Models::MessageFeed::data(const QModelIndex& index, int role) const
{ {
int i = index.row(); int i = index.row();
QVariant answer; QVariant answer;
@ -90,7 +94,19 @@ QVariant MessageFeed::data(const QModelIndex& index, int role) const
answer = msg->getBody(); answer = msg->getBody();
break; break;
case Sender: case Sender:
answer = msg->getFrom(); if (rosterItem->isRoom()) {
if (sentByMe(*msg)) {
answer = rosterItem->getDisplayedName();
} else {
answer = msg->getFromResource();
}
} else {
if (sentByMe(*msg)) {
answer = rosterItem->getAccountName();
} else {
answer = rosterItem->getDisplayedName();
}
}
break; break;
case Date: case Date:
answer = msg->getTime(); answer = msg->getTime();
@ -102,51 +118,55 @@ QVariant MessageFeed::data(const QModelIndex& index, int role) const
answer = msg->getEdited(); answer = msg->getEdited();
break; break;
case SentByMe: case SentByMe:
answer = msg->getOutgoing(); answer = sentByMe(*msg);
break;
case Avatar: {
QString path;
if (sentByMe(*msg)) {
path = rosterItem->getAccountAvatarPath();
} else if (!rosterItem->isRoom()) {
if (rosterItem->getAvatarState() != Shared::Avatar::empty) {
path = rosterItem->getAvatarPath();
}
} else {
const Room* room = static_cast<const Room*>(rosterItem);
path = room->getParticipantIconPath(msg->getFromResource());
}
if (path.size() == 0) {
answer = Shared::iconPath("user", true);
} else {
answer = path;
}
}
break; break;
default: default:
break; break;
} }
} else {
switch (role) {
case Qt::DisplayRole:
case Text:
answer = "loading...";
break;
default:
answer = "";
break;
}
} }
return answer; return answer;
} }
int MessageFeed::rowCount(const QModelIndex& parent) const int Models::MessageFeed::rowCount(const QModelIndex& parent) const
{ {
int count = storage.size(); return storage.size();
if (syncState == syncing) {
count++;
}
return count;
} }
unsigned int MessageFeed::unreadMessagesCount() const unsigned int Models::MessageFeed::unreadMessagesCount() const
{ {
return storage.size(); //let's say they are all new for now =) return storage.size(); //let's say they are all new for now =)
} }
bool MessageFeed::canFetchMore(const QModelIndex& parent) const bool Models::MessageFeed::canFetchMore(const QModelIndex& parent) const
{ {
return syncState == incomplete; return syncState == incomplete;
} }
void MessageFeed::fetchMore(const QModelIndex& parent) void Models::MessageFeed::fetchMore(const QModelIndex& parent)
{ {
if (syncState == incomplete) { if (syncState == incomplete) {
beginInsertRows(QModelIndex(), storage.size(), storage.size()); emit requestStateChange(true);
syncState = syncing;
endInsertRows();
if (storage.size() == 0) { if (storage.size() == 0) {
emit requestArchive(""); emit requestArchive("");
@ -156,13 +176,11 @@ void MessageFeed::fetchMore(const QModelIndex& parent)
} }
} }
void MessageFeed::responseArchive(const std::list<Shared::Message> list) void Models::MessageFeed::responseArchive(const std::list<Shared::Message> list)
{ {
Storage::size_type size = storage.size(); Storage::size_type size = storage.size();
if (syncState == syncing) { if (syncState == syncing) {
beginRemoveRows(QModelIndex(), size, size); emit requestStateChange(false);
syncState = incomplete;
endRemoveRows();
} }
beginInsertRows(QModelIndex(), size, size + list.size() - 1); beginInsertRows(QModelIndex(), size, size + list.size() - 1);
@ -173,7 +191,17 @@ void MessageFeed::responseArchive(const std::list<Shared::Message> list)
endInsertRows(); endInsertRows();
} }
QHash<int, QByteArray> MessageFeed::roleNames() const QHash<int, QByteArray> Models::MessageFeed::roleNames() const
{ {
return roles; return roles;
} }
bool Models::MessageFeed::sentByMe(const Shared::Message& msg) const
{
if (rosterItem->isRoom()) {
const Room* room = static_cast<const Room*>(rosterItem);
return room->getNick().toLower() == msg.getFromResource().toLower();
} else {
return msg.getOutgoing();
}
}

View File

@ -29,13 +29,17 @@
#include <boost/multi_index/mem_fun.hpp> #include <boost/multi_index/mem_fun.hpp>
#include <shared/message.h> #include <shared/message.h>
#include <shared/icons.h>
namespace Models {
class Element;
class MessageFeed : public QAbstractListModel class MessageFeed : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
public: public:
MessageFeed(QObject *parent = nullptr); MessageFeed(const Element* rosterItem, QObject *parent = nullptr);
~MessageFeed(); ~MessageFeed();
void addMessage(const Shared::Message& msg); void addMessage(const Shared::Message& msg);
@ -55,6 +59,10 @@ public:
signals: signals:
void requestArchive(const QString& before); void requestArchive(const QString& before);
void requestStateChange(bool requesting);
protected:
bool sentByMe(const Shared::Message& msg) const;
public: public:
enum MessageRoles { enum MessageRoles {
@ -63,7 +71,8 @@ public:
Date, Date,
DeliveryState, DeliveryState,
Correction, Correction,
SentByMe SentByMe,
Avatar
}; };
private: private:
enum SyncState { enum SyncState {
@ -104,10 +113,11 @@ private:
StorageById& indexById; StorageById& indexById;
StorageByTime& indexByTime; StorageByTime& indexByTime;
const Element* rosterItem;
SyncState syncState; SyncState syncState;
static const QHash<int, QByteArray> roles; static const QHash<int, QByteArray> roles;
};
}; };
#endif // MESSAGEFEED_H #endif // MESSAGEFEED_H

View File

@ -310,7 +310,12 @@ QString Models::Room::getParticipantIconPath(const QString& name) const
{ {
std::map<QString, Models::Participant*>::const_iterator itr = participants.find(name); std::map<QString, Models::Participant*>::const_iterator itr = participants.find(name);
if (itr == participants.end()) { if (itr == participants.end()) {
return ""; std::map<QString, QString>::const_iterator eitr = exParticipantAvatars.find(name);
if (eitr != exParticipantAvatars.end()) {
return eitr->second;
} else {
return "";
}
} }
return itr->second->getAvatarPath(); return itr->second->getAvatarPath();

View File

@ -232,3 +232,8 @@ void FeedView::verticalScrollbarValueChanged(int value)
QAbstractItemView::verticalScrollbarValueChanged(vo); QAbstractItemView::verticalScrollbarValueChanged(vo);
} }
QFont FeedView::getFont() const
{
return viewOptions().font;
}

View File

@ -43,6 +43,8 @@ public:
void setSelection(const QRect & rect, QItemSelectionModel::SelectionFlags command) override; void setSelection(const QRect & rect, QItemSelectionModel::SelectionFlags command) override;
QRegion visualRegionForSelection(const QItemSelection & selection) const override; QRegion visualRegionForSelection(const QItemSelection & selection) const override;
QFont getFont() const;
protected slots: protected slots:
void rowsInserted(const QModelIndex & parent, int start, int end) override; void rowsInserted(const QModelIndex & parent, int start, int end) override;
void verticalScrollbarValueChanged(int value) override; void verticalScrollbarValueChanged(int value) override;

View File

@ -0,0 +1,130 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QPainter>
#include <QApplication>
#include "messagedelegate.h"
#include "ui/models/messagefeed.h"
constexpr int avatarHeight = 50;
constexpr int margin = 6;
MessageDelegate::MessageDelegate(QObject* parent):
QStyledItemDelegate(parent),
bodyFont(),
nickFont(),
dateFont(),
bodyMetrics(bodyFont),
nickMetrics(nickFont),
dateMetrics(dateFont)
{
}
MessageDelegate::~MessageDelegate()
{
}
void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
bool sentByMe = false;
QVariant sbm = index.data(Models::MessageFeed::SentByMe);
if (sbm.isValid()) {
sentByMe = sbm.toBool();
}
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
QIcon icon(index.data(Models::MessageFeed::Avatar).toString());
if (sentByMe) {
painter->drawPixmap(option.rect.width() - avatarHeight - margin, option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
} else {
painter->drawPixmap(margin, option.rect.y() + margin / 2, icon.pixmap(avatarHeight, avatarHeight));
}
QStyleOptionViewItem opt = option;
QRect messageRect = option.rect.adjusted(margin, margin / 2, -(avatarHeight + 2 * margin), -margin / 2);
if (!sentByMe) {
opt.displayAlignment = Qt::AlignLeft | Qt::AlignTop;
messageRect.adjust(avatarHeight + margin, 0, avatarHeight + margin, 0);
} else {
opt.displayAlignment = Qt::AlignRight | Qt::AlignTop;
}
opt.rect = messageRect;
QRect rect;
painter->setFont(nickFont);
painter->drawText(opt.rect, opt.displayAlignment, index.data(Models::MessageFeed::Sender).toString(), &rect);
opt.rect.adjust(0, rect.height(), 0, 0);
painter->setFont(bodyFont);
painter->drawText(opt.rect, opt.displayAlignment | Qt::TextWordWrap, index.data(Models::MessageFeed::Text).toString(), &rect);
opt.rect.adjust(0, rect.height(), 0, 0);
painter->setFont(dateFont);
QColor q = painter->pen().color();
q.setAlpha(180);
painter->setPen(q);
painter->drawText(opt.rect, opt.displayAlignment, index.data(Models::MessageFeed::Date).toDateTime().toLocalTime().toString(), &rect);
painter->restore();
}
QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
QRect messageRect = option.rect.adjusted(0, margin / 2, -(avatarHeight + 3 * margin), -margin);
QStyleOptionViewItem opt = option;
opt.rect = messageRect;
QSize messageSize = bodyMetrics.boundingRect(messageRect, Qt::TextWordWrap, index.data(Models::MessageFeed::Text).toString()).size();
messageSize.rheight() += nickMetrics.lineSpacing();
messageSize.rheight() += dateMetrics.height();
if (messageSize.height() < avatarHeight) {
messageSize.setHeight(avatarHeight);
}
messageSize.rheight() += margin;
return messageSize;
}
void MessageDelegate::initializeFonts(const QFont& font)
{
bodyFont = font;
nickFont = font;
dateFont = font;
nickFont.setBold(true);
dateFont.setItalic(true);
float dps = dateFont.pointSizeF();
if (dps != -1) {
dateFont.setPointSizeF(dps * 0.7);
} else {
dateFont.setPointSize(dateFont.pointSize() - 2);
}
bodyMetrics = QFontMetrics(bodyFont);
nickMetrics = QFontMetrics(nickFont);
dateMetrics = QFontMetrics(dateFont);
}
// void MessageDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
// {
//
// }

View File

@ -0,0 +1,50 @@
/*
* Squawk messenger.
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MESSAGEDELEGATE_H
#define MESSAGEDELEGATE_H
#include <QStyledItemDelegate>
#include <QFont>
#include <QFontMetrics>
#include "shared/icons.h"
class MessageDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
MessageDelegate(QObject *parent = nullptr);
~MessageDelegate();
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
//void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
void initializeFonts(const QFont& font);
private:
QFont bodyFont;
QFont nickFont;
QFont dateFont;
QFontMetrics bodyMetrics;
QFontMetrics nickMetrics;
QFontMetrics dateMetrics;
};
#endif // MESSAGEDELEGATE_H

View File

@ -46,6 +46,7 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
overlay(new QWidget()), overlay(new QWidget()),
filesToAttach(), filesToAttach(),
feed(new FeedView()), feed(new FeedView()),
delegate(new MessageDelegate()),
scroll(down), scroll(down),
manualSliderChange(false), manualSliderChange(false),
requestingHistory(false), requestingHistory(false),
@ -54,6 +55,8 @@ Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el,
{ {
m_ui->setupUi(this); m_ui->setupUi(this);
feed->setItemDelegate(delegate);
delegate->initializeFonts(feed->getFont());
feed->setModel(el->feed); feed->setModel(el->feed);
m_ui->widget->layout()->addWidget(feed); m_ui->widget->layout()->addWidget(feed);

View File

@ -34,6 +34,7 @@
#include "ui/utils/flowlayout.h" #include "ui/utils/flowlayout.h"
#include "ui/utils/badge.h" #include "ui/utils/badge.h"
#include "ui/utils/feedview.h" #include "ui/utils/feedview.h"
#include "ui/utils/messagedelegate.h"
#include "shared/icons.h" #include "shared/icons.h"
#include "shared/utils.h" #include "shared/utils.h"
@ -147,6 +148,7 @@ protected:
QWidget* overlay; QWidget* overlay;
W::Order<Badge*, Badge::Comparator> filesToAttach; W::Order<Badge*, Badge::Comparator> filesToAttach;
FeedView* feed; FeedView* feed;
MessageDelegate* delegate;
Scroll scroll; Scroll scroll;
bool manualSliderChange; bool manualSliderChange;
bool requestingHistory; bool requestingHistory;