mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
I BROKE EVERYTHING
refactored the rendering process
This commit is contained in:
parent
c240d6f7c2
commit
10850c0ec7
|
@ -58,11 +58,8 @@ SOURCES += \
|
|||
src/channel.cpp \
|
||||
src/channeldata.cpp \
|
||||
src/singletons/ircmanager.cpp \
|
||||
src/messages/lazyloadedimage.cpp \
|
||||
src/messages/link.cpp \
|
||||
src/messages/message.cpp \
|
||||
src/messages/word.cpp \
|
||||
src/messages/wordpart.cpp \
|
||||
src/widgets/notebook.cpp \
|
||||
src/widgets/helper/notebookbutton.cpp \
|
||||
src/widgets/helper/notebooktab.cpp \
|
||||
|
@ -71,7 +68,6 @@ SOURCES += \
|
|||
src/widgets/settingsdialog.cpp \
|
||||
src/widgets/helper/settingsdialogtab.cpp \
|
||||
src/widgets/textinputdialog.cpp \
|
||||
src/messages/messageref.cpp \
|
||||
src/logging/loggingmanager.cpp \
|
||||
src/logging/loggingchannel.cpp \
|
||||
src/singletons/windowmanager.cpp \
|
||||
|
@ -115,7 +111,12 @@ SOURCES += \
|
|||
src/singletons/resourcemanager.cpp \
|
||||
src/singletons/helper/ircmessagehandler.cpp \
|
||||
src/singletons/pathmanager.cpp \
|
||||
src/widgets/helper/searchpopup.cpp
|
||||
src/widgets/helper/searchpopup.cpp \
|
||||
src/messages/messageelement.cpp \
|
||||
src/messages/image.cpp \
|
||||
src/messages/layouts/messagelayout.cpp \
|
||||
src/messages/layouts/messagelayoutelement.cpp \
|
||||
src/messages/layouts/messagelayoutcontainer.cpp
|
||||
|
||||
HEADERS += \
|
||||
src/precompiled_headers.hpp \
|
||||
|
@ -124,11 +125,8 @@ HEADERS += \
|
|||
src/util/concurrentmap.hpp \
|
||||
src/emojis.hpp \
|
||||
src/singletons/ircmanager.hpp \
|
||||
src/messages/lazyloadedimage.hpp \
|
||||
src/messages/link.hpp \
|
||||
src/messages/message.hpp \
|
||||
src/messages/word.hpp \
|
||||
src/messages/wordpart.hpp \
|
||||
src/twitch/emotevalue.hpp \
|
||||
src/widgets/notebook.hpp \
|
||||
src/widgets/helper/notebookbutton.hpp \
|
||||
|
@ -142,7 +140,6 @@ HEADERS += \
|
|||
src/widgets/helper/resizingtextedit.hpp \
|
||||
src/messages/limitedqueue.hpp \
|
||||
src/messages/limitedqueuesnapshot.hpp \
|
||||
src/messages/messageref.hpp \
|
||||
src/logging/loggingmanager.hpp \
|
||||
src/logging/loggingchannel.hpp \
|
||||
src/singletons/channelmanager.hpp \
|
||||
|
@ -200,7 +197,13 @@ HEADERS += \
|
|||
src/messages/selection.hpp \
|
||||
src/singletons/pathmanager.hpp \
|
||||
src/widgets/helper/searchpopup.hpp \
|
||||
src/widgets/helper/shortcut.hpp
|
||||
src/widgets/helper/shortcut.hpp \
|
||||
src/messages/messageelement.hpp \
|
||||
src/messages/image.hpp \
|
||||
src/messages/layouts/messagelayout.hpp \
|
||||
src/messages/layouts/messagelayoutelement.hpp \
|
||||
src/messages/layouts/messagelayoutcontainer.hpp \
|
||||
src/util/property.hpp
|
||||
|
||||
|
||||
PRECOMPILED_HEADER =
|
||||
|
|
|
@ -29,14 +29,14 @@ bool Channel::isEmpty() const
|
|||
return false;
|
||||
}
|
||||
|
||||
messages::LimitedQueueSnapshot<messages::SharedMessage> Channel::getMessageSnapshot()
|
||||
messages::LimitedQueueSnapshot<messages::MessagePtr> Channel::getMessageSnapshot()
|
||||
{
|
||||
return this->messages.getSnapshot();
|
||||
}
|
||||
|
||||
void Channel::addMessage(std::shared_ptr<Message> message)
|
||||
void Channel::addMessage(MessagePtr message)
|
||||
{
|
||||
std::shared_ptr<Message> deleted;
|
||||
MessagePtr deleted;
|
||||
|
||||
const QString &username = message->loginName;
|
||||
|
||||
|
@ -56,16 +56,16 @@ void Channel::addMessage(std::shared_ptr<Message> message)
|
|||
this->messageAppended(message);
|
||||
}
|
||||
|
||||
void Channel::addMessagesAtStart(std::vector<messages::SharedMessage> &_messages)
|
||||
void Channel::addMessagesAtStart(std::vector<messages::MessagePtr> &_messages)
|
||||
{
|
||||
std::vector<messages::SharedMessage> addedMessages = this->messages.pushFront(_messages);
|
||||
std::vector<messages::MessagePtr> addedMessages = this->messages.pushFront(_messages);
|
||||
|
||||
if (addedMessages.size() != 0) {
|
||||
this->messagesAddedAtStart(addedMessages);
|
||||
}
|
||||
}
|
||||
|
||||
void Channel::replaceMessage(messages::SharedMessage message, messages::SharedMessage replacement)
|
||||
void Channel::replaceMessage(messages::MessagePtr message, messages::MessagePtr replacement)
|
||||
{
|
||||
int index = this->messages.replaceItem(message, replacement);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "logging/loggingchannel.hpp"
|
||||
#include "messages/lazyloadedimage.hpp"
|
||||
#include "messages/image.hpp"
|
||||
#include "messages/limitedqueue.hpp"
|
||||
#include "util/concurrentmap.hpp"
|
||||
|
||||
|
@ -24,17 +24,17 @@ class Channel : public std::enable_shared_from_this<Channel>
|
|||
public:
|
||||
explicit Channel(const QString &_name);
|
||||
|
||||
boost::signals2::signal<void(messages::SharedMessage &)> messageRemovedFromStart;
|
||||
boost::signals2::signal<void(messages::SharedMessage &)> messageAppended;
|
||||
boost::signals2::signal<void(std::vector<messages::SharedMessage> &)> messagesAddedAtStart;
|
||||
boost::signals2::signal<void(size_t index, messages::SharedMessage &)> messageReplaced;
|
||||
boost::signals2::signal<void(messages::MessagePtr &)> messageRemovedFromStart;
|
||||
boost::signals2::signal<void(messages::MessagePtr &)> messageAppended;
|
||||
boost::signals2::signal<void(std::vector<messages::MessagePtr> &)> messagesAddedAtStart;
|
||||
boost::signals2::signal<void(size_t index, messages::MessagePtr &)> messageReplaced;
|
||||
|
||||
virtual bool isEmpty() const;
|
||||
messages::LimitedQueueSnapshot<messages::SharedMessage> getMessageSnapshot();
|
||||
messages::LimitedQueueSnapshot<messages::MessagePtr> getMessageSnapshot();
|
||||
|
||||
void addMessage(messages::SharedMessage message);
|
||||
void addMessagesAtStart(std::vector<messages::SharedMessage> &messages);
|
||||
void replaceMessage(messages::SharedMessage message, messages::SharedMessage replacement);
|
||||
void addMessage(messages::MessagePtr message);
|
||||
void addMessagesAtStart(std::vector<messages::MessagePtr> &messages);
|
||||
void replaceMessage(messages::MessagePtr message, messages::MessagePtr replacement);
|
||||
void addRecentChatter(const std::shared_ptr<messages::Message> &message);
|
||||
|
||||
struct NameOptions {
|
||||
|
@ -55,9 +55,11 @@ public:
|
|||
virtual void sendMessage(const QString &message);
|
||||
|
||||
private:
|
||||
messages::LimitedQueue<messages::SharedMessage> messages;
|
||||
messages::LimitedQueue<messages::MessagePtr> messages;
|
||||
|
||||
// std::shared_ptr<logging::Channel> loggingChannel;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<Channel> SharedChannel;
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "lockedobject.hpp"
|
||||
#include "messages/lazyloadedimage.hpp"
|
||||
#include "messages/image.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "messages/lazyloadedimage.hpp"
|
||||
#include "messages/image.hpp"
|
||||
#include "util/concurrentmap.hpp"
|
||||
|
||||
#include <QObject>
|
||||
|
|
|
@ -39,7 +39,7 @@ void Channel::append(std::shared_ptr<messages::Message> message)
|
|||
str.append("] ");
|
||||
str.append(message->loginName);
|
||||
str.append(": ");
|
||||
str.append(message->getContent());
|
||||
str.append(message->getSearchText());
|
||||
str.append('\n');
|
||||
this->appendLine(str);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,9 @@
|
|||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication::setAttribute(Qt::AA_Use96Dpi, true);
|
||||
#ifdef Q_OS_WIN32
|
||||
QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
|
||||
#endif
|
||||
QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL, true);
|
||||
QApplication a(argc, argv);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "messages/lazyloadedimage.hpp"
|
||||
#include "messages/image.hpp"
|
||||
#include "asyncexec.hpp"
|
||||
#include "singletons/emotemanager.hpp"
|
||||
#include "singletons/ircmanager.hpp"
|
||||
|
@ -19,8 +19,8 @@
|
|||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
LazyLoadedImage::LazyLoadedImage(const QString &url, qreal scale, const QString &name,
|
||||
const QString &tooltip, const QMargins &margin, bool isHat)
|
||||
Image::Image(const QString &url, qreal scale, const QString &name, const QString &tooltip,
|
||||
const QMargins &margin, bool isHat)
|
||||
: currentPixmap(nullptr)
|
||||
, url(url)
|
||||
, name(name)
|
||||
|
@ -32,8 +32,8 @@ LazyLoadedImage::LazyLoadedImage(const QString &url, qreal scale, const QString
|
|||
{
|
||||
}
|
||||
|
||||
LazyLoadedImage::LazyLoadedImage(QPixmap *image, qreal scale, const QString &name,
|
||||
const QString &tooltip, const QMargins &margin, bool isHat)
|
||||
Image::Image(QPixmap *image, qreal scale, const QString &name, const QString &tooltip,
|
||||
const QMargins &margin, bool isHat)
|
||||
: currentPixmap(image)
|
||||
, name(name)
|
||||
, tooltip(tooltip)
|
||||
|
@ -44,7 +44,7 @@ LazyLoadedImage::LazyLoadedImage(QPixmap *image, qreal scale, const QString &nam
|
|||
{
|
||||
}
|
||||
|
||||
void LazyLoadedImage::loadImage()
|
||||
void Image::loadImage()
|
||||
{
|
||||
util::NetworkRequest req(this->getUrl());
|
||||
req.setCaller(this);
|
||||
|
@ -67,7 +67,7 @@ void LazyLoadedImage::loadImage()
|
|||
lli->currentPixmap = pixmap;
|
||||
}
|
||||
|
||||
chatterino::messages::LazyLoadedImage::FrameData data;
|
||||
chatterino::messages::Image::FrameData data;
|
||||
data.duration = std::max(20, reader.nextImageDelay());
|
||||
data.image = pixmap;
|
||||
|
||||
|
@ -90,7 +90,7 @@ void LazyLoadedImage::loadImage()
|
|||
// doesn't work, so this is the fix.
|
||||
}
|
||||
|
||||
void LazyLoadedImage::gifUpdateTimout()
|
||||
void Image::gifUpdateTimout()
|
||||
{
|
||||
if (animated) {
|
||||
this->currentFrameOffset += GIF_FRAME_LENGTH;
|
||||
|
@ -108,7 +108,7 @@ void LazyLoadedImage::gifUpdateTimout()
|
|||
}
|
||||
}
|
||||
|
||||
const QPixmap *LazyLoadedImage::getPixmap()
|
||||
const QPixmap *Image::getPixmap()
|
||||
{
|
||||
if (!this->isLoading) {
|
||||
this->isLoading = true;
|
||||
|
@ -118,42 +118,42 @@ const QPixmap *LazyLoadedImage::getPixmap()
|
|||
return this->currentPixmap;
|
||||
}
|
||||
|
||||
qreal LazyLoadedImage::getScale() const
|
||||
qreal Image::getScale() const
|
||||
{
|
||||
return this->scale;
|
||||
}
|
||||
|
||||
const QString &LazyLoadedImage::getUrl() const
|
||||
const QString &Image::getUrl() const
|
||||
{
|
||||
return this->url;
|
||||
}
|
||||
|
||||
const QString &LazyLoadedImage::getName() const
|
||||
const QString &Image::getName() const
|
||||
{
|
||||
return this->name;
|
||||
}
|
||||
|
||||
const QString &LazyLoadedImage::getTooltip() const
|
||||
const QString &Image::getTooltip() const
|
||||
{
|
||||
return this->tooltip;
|
||||
}
|
||||
|
||||
const QMargins &LazyLoadedImage::getMargin() const
|
||||
const QMargins &Image::getMargin() const
|
||||
{
|
||||
return this->margin;
|
||||
}
|
||||
|
||||
bool LazyLoadedImage::getAnimated() const
|
||||
bool Image::isAnimated() const
|
||||
{
|
||||
return this->animated;
|
||||
}
|
||||
|
||||
bool LazyLoadedImage::isHat() const
|
||||
bool Image::isHat() const
|
||||
{
|
||||
return this->ishat;
|
||||
}
|
||||
|
||||
int LazyLoadedImage::getWidth() const
|
||||
int Image::getWidth() const
|
||||
{
|
||||
if (this->currentPixmap == nullptr) {
|
||||
return 16;
|
||||
|
@ -161,12 +161,12 @@ int LazyLoadedImage::getWidth() const
|
|||
return this->currentPixmap->width();
|
||||
}
|
||||
|
||||
int LazyLoadedImage::getScaledWidth() const
|
||||
int Image::getScaledWidth() const
|
||||
{
|
||||
return static_cast<int>(getWidth() * this->scale);
|
||||
return static_cast<int>(this->getWidth() * this->scale);
|
||||
}
|
||||
|
||||
int LazyLoadedImage::getHeight() const
|
||||
int Image::getHeight() const
|
||||
{
|
||||
if (this->currentPixmap == nullptr) {
|
||||
return 16;
|
||||
|
@ -174,9 +174,9 @@ int LazyLoadedImage::getHeight() const
|
|||
return this->currentPixmap->height();
|
||||
}
|
||||
|
||||
int LazyLoadedImage::getScaledHeight() const
|
||||
int Image::getScaledHeight() const
|
||||
{
|
||||
return static_cast<int>(getHeight() * this->scale);
|
||||
return static_cast<int>(this->getHeight() * this->scale);
|
||||
}
|
||||
|
||||
} // namespace messages
|
|
@ -3,19 +3,21 @@
|
|||
#include <QPixmap>
|
||||
#include <QString>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
class LazyLoadedImage : public QObject
|
||||
class Image : public QObject, boost::noncopyable
|
||||
{
|
||||
public:
|
||||
LazyLoadedImage() = delete;
|
||||
Image() = delete;
|
||||
|
||||
explicit LazyLoadedImage(const QString &_url, qreal _scale = 1, const QString &_name = "",
|
||||
explicit Image(const QString &_url, qreal _scale = 1, const QString &_name = "",
|
||||
const QString &_tooltip = "", const QMargins &_margin = QMargins(),
|
||||
bool isHat = false);
|
||||
|
||||
explicit LazyLoadedImage(QPixmap *_currentPixmap, qreal _scale = 1, const QString &_name = "",
|
||||
explicit Image(QPixmap *_currentPixmap, qreal _scale = 1, const QString &_name = "",
|
||||
const QString &_tooltip = "", const QMargins &_margin = QMargins(),
|
||||
bool isHat = false);
|
||||
|
||||
|
@ -25,7 +27,7 @@ public:
|
|||
const QString &getName() const;
|
||||
const QString &getTooltip() const;
|
||||
const QMargins &getMargin() const;
|
||||
bool getAnimated() const;
|
||||
bool isAnimated() const;
|
||||
bool isHat() const;
|
||||
int getWidth() const;
|
||||
int getScaledWidth() const;
|
345
src/messages/layouts/messagelayout.cpp
Normal file
345
src/messages/layouts/messagelayout.cpp
Normal file
|
@ -0,0 +1,345 @@
|
|||
#include "messages/layouts/messagelayout.hpp"
|
||||
#include "singletons/emotemanager.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
#include <QThread>
|
||||
#include <QtGlobal>
|
||||
|
||||
#define MARGIN_LEFT (int)(8 * this->scale)
|
||||
#define MARGIN_RIGHT (int)(8 * this->scale)
|
||||
#define MARGIN_TOP (int)(4 * this->scale)
|
||||
#define MARGIN_BOTTOM (int)(4 * this->scale)
|
||||
#define COMPACT_EMOTES_OFFSET 6
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
namespace layouts {
|
||||
|
||||
MessageLayout::MessageLayout(MessagePtr _message)
|
||||
: message(_message)
|
||||
, buffer(nullptr)
|
||||
{
|
||||
if (_message->hasFlags(Message::Collapsed)) {
|
||||
this->addFlags(MessageLayout::Collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
Message *MessageLayout::getMessage()
|
||||
{
|
||||
return this->message.get();
|
||||
}
|
||||
|
||||
// Height
|
||||
int MessageLayout::getHeight() const
|
||||
{
|
||||
return container.getHeight();
|
||||
}
|
||||
|
||||
// Flags
|
||||
MessageLayout::Flags MessageLayout::getFlags() const
|
||||
{
|
||||
return this->flags;
|
||||
}
|
||||
|
||||
bool MessageLayout::hasFlags(Flags _flags) const
|
||||
{
|
||||
return this->flags & _flags;
|
||||
}
|
||||
|
||||
void MessageLayout::addFlags(Flags _flags)
|
||||
{
|
||||
this->flags = (Flags)((MessageLayoutFlagsType)this->flags | (MessageLayoutFlagsType)_flags);
|
||||
}
|
||||
|
||||
void MessageLayout::removeFlags(Flags _flags)
|
||||
{
|
||||
this->flags = (Flags)((MessageLayoutFlagsType)this->flags & ~((MessageLayoutFlagsType)_flags));
|
||||
}
|
||||
|
||||
// Layout
|
||||
// return true if redraw is required
|
||||
bool MessageLayout::layout(int width, float scale)
|
||||
{
|
||||
auto &emoteManager = singletons::EmoteManager::getInstance();
|
||||
|
||||
bool layoutRequired = false;
|
||||
|
||||
// check if width changed
|
||||
bool widthChanged = width != this->currentLayoutWidth;
|
||||
layoutRequired |= widthChanged;
|
||||
this->currentLayoutWidth = width;
|
||||
|
||||
// check if emotes changed
|
||||
bool imagesChanged = this->emoteGeneration != emoteManager.getGeneration();
|
||||
layoutRequired |= imagesChanged;
|
||||
this->emoteGeneration = emoteManager.getGeneration();
|
||||
|
||||
// check if text changed
|
||||
bool textChanged =
|
||||
this->fontGeneration != singletons::FontManager::getInstance().getGeneration();
|
||||
layoutRequired |= textChanged;
|
||||
this->fontGeneration = singletons::FontManager::getInstance().getGeneration();
|
||||
|
||||
// check if work mask changed
|
||||
bool wordMaskChanged =
|
||||
this->currentWordTypes != singletons::SettingManager::getInstance().getWordTypeMask();
|
||||
layoutRequired |= wordMaskChanged;
|
||||
this->currentWordTypes = singletons::SettingManager::getInstance().getWordTypeMask();
|
||||
|
||||
// check if dpi changed
|
||||
bool scaleChanged = this->scale != scale;
|
||||
layoutRequired |= scaleChanged;
|
||||
this->scale = scale;
|
||||
imagesChanged |= scaleChanged;
|
||||
textChanged |= scaleChanged;
|
||||
|
||||
// update word sizes if needed
|
||||
if (imagesChanged) {
|
||||
// fourtf: update images
|
||||
this->addFlags(MessageLayout::RequiresBufferUpdate);
|
||||
}
|
||||
if (textChanged) {
|
||||
// fourtf: update text
|
||||
this->addFlags(MessageLayout::RequiresBufferUpdate);
|
||||
}
|
||||
if (widthChanged || wordMaskChanged) {
|
||||
this->deleteBuffer();
|
||||
}
|
||||
|
||||
// return if no layout is required
|
||||
if (!layoutRequired) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->actuallyLayout(width);
|
||||
this->invalidateBuffer();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MessageLayout::actuallyLayout(int width)
|
||||
{
|
||||
this->container.clear();
|
||||
this->container.width = width;
|
||||
this->container.scale = this->scale;
|
||||
|
||||
for (const std::unique_ptr<MessageElement> &element : this->message->getElements()) {
|
||||
element->addToContainer(this->container, MessageElement::Default);
|
||||
}
|
||||
|
||||
if (this->height != this->container.getHeight()) {
|
||||
this->deleteBuffer();
|
||||
}
|
||||
|
||||
this->container.finish();
|
||||
this->height = this->container.getHeight();
|
||||
}
|
||||
|
||||
// Painting
|
||||
void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection &selection)
|
||||
{
|
||||
QPixmap *pixmap = this->buffer.get();
|
||||
|
||||
// create new buffer if required
|
||||
if (!pixmap) {
|
||||
#ifdef Q_OS_MACOS
|
||||
|
||||
pixmap =
|
||||
new QPixmap((int)(this->container.getWidth() * painter.device()->devicePixelRatioF()),
|
||||
(int)(this->container.getHeight() * painter.device()->devicePixelRatioF()));
|
||||
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
|
||||
#else
|
||||
pixmap = new QPixmap(this->container.width, std::max(16, this->container.getHeight()));
|
||||
#endif
|
||||
|
||||
this->buffer = std::shared_ptr<QPixmap>(pixmap);
|
||||
this->bufferValid = false;
|
||||
}
|
||||
|
||||
if (!this->bufferValid) {
|
||||
this->updateBuffer(pixmap, messageIndex, selection);
|
||||
}
|
||||
|
||||
// draw on buffer
|
||||
painter.drawPixmap(0, y, pixmap->width(), pixmap->height(), *pixmap);
|
||||
|
||||
this->bufferValid = true;
|
||||
}
|
||||
|
||||
void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &selection)
|
||||
{
|
||||
singletons::ThemeManager &themeManager = singletons::ThemeManager::getInstance();
|
||||
|
||||
QPainter painter(buffer);
|
||||
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
|
||||
// draw background
|
||||
painter.fillRect(buffer->rect(),
|
||||
this->message->hasFlags(Message::Highlighted)
|
||||
? themeManager.messages.backgrounds.highlighted
|
||||
: themeManager.messages.backgrounds.regular);
|
||||
|
||||
// draw selection
|
||||
if (!selection.isEmpty()) {
|
||||
this->container.paintSelection(painter, messageIndex, selection);
|
||||
}
|
||||
|
||||
// draw message
|
||||
this->container.paintElements(painter);
|
||||
|
||||
// debug
|
||||
painter.setPen(QColor(255, 0, 0));
|
||||
painter.drawRect(buffer->rect().x(), buffer->rect().y(), buffer->rect().width() - 1,
|
||||
buffer->rect().height() - 1);
|
||||
|
||||
QTextOption option;
|
||||
option.setAlignment(Qt::AlignRight | Qt::AlignTop);
|
||||
|
||||
painter.drawText(QRectF(1, 1, this->container.width - 3, 1000),
|
||||
QString::number(++this->bufferUpdatedCount), option);
|
||||
}
|
||||
|
||||
void MessageLayout::invalidateBuffer()
|
||||
{
|
||||
this->bufferValid = false;
|
||||
}
|
||||
|
||||
void MessageLayout::deleteBuffer()
|
||||
{
|
||||
this->buffer = nullptr;
|
||||
}
|
||||
|
||||
// Elements
|
||||
// assert(QThread::currentThread() == QApplication::instance()->thread());
|
||||
|
||||
// returns nullptr if none was found
|
||||
|
||||
// fourtf: this should return a MessageLayoutItem
|
||||
const MessageLayoutElement *MessageLayout::getElementAt(QPoint point)
|
||||
{
|
||||
// go through all words and return the first one that contains the point.
|
||||
return this->container.getElementAt(point);
|
||||
}
|
||||
|
||||
// XXX(pajlada): This is probably not the optimal way to calculate this
|
||||
int MessageLayout::getLastCharacterIndex() const
|
||||
{
|
||||
// fourtf: xD
|
||||
// // find out in which line the cursor is
|
||||
// int lineNumber = 0, lineStart = 0, lineEnd = 0;
|
||||
|
||||
// for (size_t i = 0; i < this->wordParts.size(); i++) {
|
||||
// const LayoutItem &part = this->wordParts[i];
|
||||
|
||||
// if (part.getLineNumber() != lineNumber) {
|
||||
// lineStart = i;
|
||||
// lineNumber = part.getLineNumber();
|
||||
// }
|
||||
|
||||
// lineEnd = i + 1;
|
||||
// }
|
||||
|
||||
// // count up to the cursor
|
||||
// int index = 0;
|
||||
|
||||
// for (int i = 0; i < lineStart; i++) {
|
||||
// const LayoutItem &part = this->wordParts[i];
|
||||
|
||||
// index += part.getWord().isImage() ? 2 : part.getText().length() + 1;
|
||||
// }
|
||||
|
||||
// for (int i = lineStart; i < lineEnd; i++) {
|
||||
// const LayoutItem &part = this->wordParts[i];
|
||||
|
||||
// index += part.getCharacterLength();
|
||||
// }
|
||||
|
||||
// return index;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MessageLayout::getSelectionIndex(QPoint position)
|
||||
{
|
||||
// if (this->wordParts.size() == 0) {
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
// // find out in which line the cursor is
|
||||
// int lineNumber = 0, lineStart = 0, lineEnd = 0;
|
||||
|
||||
// for (size_t i = 0; i < this->wordParts.size(); i++) {
|
||||
// LayoutItem &part = this->wordParts[i];
|
||||
|
||||
// if (part.getLineNumber() != 0 && position.y() < part.getY()) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
// if (part.getLineNumber() != lineNumber) {
|
||||
// lineStart = i;
|
||||
// lineNumber = part.getLineNumber();
|
||||
// }
|
||||
|
||||
// lineEnd = i + 1;
|
||||
// }
|
||||
|
||||
// // count up to the cursor
|
||||
// int index = 0;
|
||||
|
||||
// for (int i = 0; i < lineStart; i++) {
|
||||
// LayoutItem &part = this->wordParts[i];
|
||||
|
||||
// index += part.getWord().isImage() ? 2 : part.getText().length() + 1;
|
||||
// }
|
||||
|
||||
// for (int i = lineStart; i < lineEnd; i++) {
|
||||
// LayoutItem &part = this->wordParts[i];
|
||||
|
||||
// // curser is left of the word part
|
||||
// if (position.x() < part.getX()) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
// // cursor is right of the word part
|
||||
// if (position.x() > part.getX() + part.getWidth()) {
|
||||
// index += part.getCharacterLength();
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// // cursor is over the word part
|
||||
// if (part.getWord().isImage()) {
|
||||
// if (position.x() - part.getX() > part.getWidth() / 2) {
|
||||
// index++;
|
||||
// }
|
||||
// } else {
|
||||
// // TODO: use word.getCharacterWidthCache();
|
||||
|
||||
// auto text = part.getText();
|
||||
|
||||
// int x = part.getX();
|
||||
|
||||
// for (int j = 0; j < text.length(); j++) {
|
||||
// if (x > position.x()) {
|
||||
// break;
|
||||
// }
|
||||
|
||||
// index++;
|
||||
// x = part.getX() + part.getWord().getFontMetrics(this->scale).width(text, j +
|
||||
// 1);
|
||||
// }
|
||||
// }
|
||||
|
||||
// break;
|
||||
// }
|
||||
|
||||
// return index;
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace layouts
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
83
src/messages/layouts/messagelayout.hpp
Normal file
83
src/messages/layouts/messagelayout.hpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
|
||||
#include "messages/layouts/messagelayoutcontainer.hpp"
|
||||
#include "messages/layouts/messagelayoutelement.hpp"
|
||||
#include "messages/message.hpp"
|
||||
#include "messages/selection.hpp"
|
||||
|
||||
#include <QPixmap>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
namespace layouts {
|
||||
|
||||
class MessageLayout;
|
||||
typedef std::shared_ptr<MessageLayout> MessageLayoutPtr;
|
||||
typedef uint8_t MessageLayoutFlagsType;
|
||||
|
||||
class MessageLayout : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
enum Flags : MessageLayoutFlagsType { Collapsed, RequiresBufferUpdate, RequiresLayout };
|
||||
|
||||
MessageLayout(MessagePtr message);
|
||||
|
||||
Message *getMessage();
|
||||
|
||||
// Height
|
||||
int getHeight() const;
|
||||
|
||||
// Flags
|
||||
Flags getFlags() const;
|
||||
bool hasFlags(Flags flags) const;
|
||||
void addFlags(Flags flags);
|
||||
void removeFlags(Flags flags);
|
||||
|
||||
// Layout
|
||||
bool layout(int width, float scale);
|
||||
|
||||
// Painting
|
||||
void paint(QPainter &painter, int y, int messageIndex, Selection &selection);
|
||||
void invalidateBuffer();
|
||||
void deleteBuffer();
|
||||
|
||||
// Elements
|
||||
const MessageLayoutElement *getElementAt(QPoint point);
|
||||
int getLastCharacterIndex() const;
|
||||
int getSelectionIndex(QPoint position);
|
||||
|
||||
// Misc
|
||||
bool isDisabled() const;
|
||||
|
||||
private:
|
||||
// variables
|
||||
MessagePtr message;
|
||||
MessageLayoutContainer container;
|
||||
std::shared_ptr<QPixmap> buffer = nullptr;
|
||||
bool bufferValid = false;
|
||||
Flags flags;
|
||||
|
||||
int height = 0;
|
||||
|
||||
int currentLayoutWidth = -1;
|
||||
int fontGeneration = -1;
|
||||
int emoteGeneration = -1;
|
||||
float scale = -1;
|
||||
unsigned int bufferUpdatedCount = 0;
|
||||
|
||||
MessageElement::Flags currentWordTypes = MessageElement::None;
|
||||
|
||||
int collapsedHeight = 32;
|
||||
|
||||
// methods
|
||||
void actuallyLayout(int width);
|
||||
void updateBuffer(QPixmap *pixmap, int messageIndex, Selection &selection);
|
||||
};
|
||||
|
||||
} // namespace layouts
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
157
src/messages/layouts/messagelayoutcontainer.cpp
Normal file
157
src/messages/layouts/messagelayoutcontainer.cpp
Normal file
|
@ -0,0 +1,157 @@
|
|||
#include "messagelayoutcontainer.hpp"
|
||||
|
||||
#include "messagelayoutelement.hpp"
|
||||
#include "messages/selection.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#define COMPACT_EMOTES_OFFSET 6
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
namespace layouts {
|
||||
MessageLayoutContainer::MessageLayoutContainer()
|
||||
: scale(1)
|
||||
, margin(4, 8, 4, 8)
|
||||
, centered(false)
|
||||
{
|
||||
this->clear();
|
||||
}
|
||||
|
||||
int MessageLayoutContainer::getHeight() const
|
||||
{
|
||||
return this->height;
|
||||
}
|
||||
|
||||
// methods
|
||||
void MessageLayoutContainer::clear()
|
||||
{
|
||||
this->elements.clear();
|
||||
|
||||
this->height = 0;
|
||||
this->line = 0;
|
||||
this->currentX = 0;
|
||||
this->currentY = 0;
|
||||
this->lineStart = 0;
|
||||
this->lineHeight = 0;
|
||||
}
|
||||
|
||||
void MessageLayoutContainer::addElement(MessageLayoutElement *element)
|
||||
{
|
||||
if (!this->fitsInLine(element->getRect().width())) {
|
||||
this->breakLine();
|
||||
}
|
||||
|
||||
this->_addElement(element);
|
||||
}
|
||||
|
||||
void MessageLayoutContainer::addElementNoLineBreak(MessageLayoutElement *element)
|
||||
{
|
||||
this->_addElement(element);
|
||||
}
|
||||
|
||||
void MessageLayoutContainer::_addElement(MessageLayoutElement *element)
|
||||
{
|
||||
if (this->elements.size() == 0) {
|
||||
this->currentY = this->margin.top * this->scale;
|
||||
}
|
||||
|
||||
int newLineHeight = element->getRect().height();
|
||||
|
||||
// fourtf: xD
|
||||
// bool compactEmotes = true;
|
||||
// if (compactEmotes && element->word.isImage() && word.getFlags() &
|
||||
// MessageElement::EmoteImages) {
|
||||
// newLineHeight -= COMPACT_EMOTES_OFFSET * this->scale;
|
||||
// }
|
||||
|
||||
this->lineHeight = std::max(this->lineHeight, newLineHeight);
|
||||
|
||||
element->setPosition(QPoint(this->currentX, this->currentY - element->getRect().height()));
|
||||
this->elements.push_back(std::unique_ptr<MessageLayoutElement>(element));
|
||||
|
||||
this->currentX += element->getRect().width();
|
||||
|
||||
if (element->hasTrailingSpace()) {
|
||||
this->currentX += this->spaceWidth;
|
||||
}
|
||||
}
|
||||
|
||||
void MessageLayoutContainer::breakLine()
|
||||
{
|
||||
int xOffset = 0;
|
||||
|
||||
if (this->centered && this->elements.size() > 0) {
|
||||
xOffset = (width - this->elements.at(this->elements.size() - 1)->getRect().right()) / 2;
|
||||
}
|
||||
|
||||
for (size_t i = lineStart; i < this->elements.size(); i++) {
|
||||
MessageLayoutElement *element = this->elements.at(i).get();
|
||||
|
||||
bool isCompactEmote = false;
|
||||
|
||||
// fourtf: xD
|
||||
// this->enableCompactEmotes && element->getWord().isImage() &&
|
||||
// element->getWord().getFlags() &
|
||||
// MessageElement::EmoteImages;
|
||||
|
||||
int yExtra = 0;
|
||||
if (isCompactEmote) {
|
||||
yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale;
|
||||
}
|
||||
|
||||
element->setPosition(QPoint(element->getRect().x() + xOffset + this->margin.left,
|
||||
element->getRect().y() + this->lineHeight + yExtra));
|
||||
}
|
||||
|
||||
this->lineStart = this->elements.size();
|
||||
this->currentX = 0;
|
||||
this->currentY += this->lineHeight;
|
||||
this->height = this->currentY + (this->margin.bottom * this->scale);
|
||||
this->lineHeight = 0;
|
||||
}
|
||||
|
||||
bool MessageLayoutContainer::atStartOfLine()
|
||||
{
|
||||
return this->lineStart == this->elements.size();
|
||||
}
|
||||
|
||||
bool MessageLayoutContainer::fitsInLine(int _width)
|
||||
{
|
||||
return this->currentX + _width <= this->width - this->margin.left - this->margin.right;
|
||||
}
|
||||
|
||||
void MessageLayoutContainer::finish()
|
||||
{
|
||||
if (!this->atStartOfLine()) {
|
||||
this->breakLine();
|
||||
}
|
||||
}
|
||||
|
||||
MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point)
|
||||
{
|
||||
for (std::unique_ptr<MessageLayoutElement> &element : this->elements) {
|
||||
if (element->getRect().contains(point)) {
|
||||
return element.get();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// painting
|
||||
void MessageLayoutContainer::paintElements(QPainter &painter)
|
||||
{
|
||||
for (const std::unique_ptr<MessageLayoutElement> &element : this->elements) {
|
||||
element->paint(painter);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageLayoutContainer::paintSelection(QPainter &painter, int messageIndex,
|
||||
Selection &selection)
|
||||
{
|
||||
}
|
||||
} // namespace layouts
|
||||
}
|
||||
}
|
84
src/messages/layouts/messagelayoutcontainer.hpp
Normal file
84
src/messages/layouts/messagelayoutcontainer.hpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QPoint>
|
||||
|
||||
class QPainter;
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
class Selection;
|
||||
|
||||
namespace layouts {
|
||||
class MessageLayoutElement;
|
||||
|
||||
struct Margin {
|
||||
int top;
|
||||
int right;
|
||||
int bottom;
|
||||
int left;
|
||||
|
||||
Margin()
|
||||
: Margin(0)
|
||||
{
|
||||
}
|
||||
|
||||
Margin(int value)
|
||||
: Margin(value, value, value, value)
|
||||
{
|
||||
}
|
||||
|
||||
Margin(int _top, int _right, int _bottom, int _left)
|
||||
: top(_top)
|
||||
, right(_right)
|
||||
, bottom(_bottom)
|
||||
, left(_left)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class MessageLayoutContainer
|
||||
{
|
||||
public:
|
||||
MessageLayoutContainer();
|
||||
|
||||
float scale;
|
||||
Margin margin;
|
||||
bool centered;
|
||||
bool enableCompactEmotes;
|
||||
int width;
|
||||
|
||||
int getHeight() const;
|
||||
|
||||
// methods
|
||||
void clear();
|
||||
void addElement(MessageLayoutElement *element);
|
||||
void addElementNoLineBreak(MessageLayoutElement *element);
|
||||
void breakLine();
|
||||
bool atStartOfLine();
|
||||
bool fitsInLine(int width);
|
||||
void finish();
|
||||
MessageLayoutElement *getElementAt(QPoint point);
|
||||
|
||||
// painting
|
||||
void paintElements(QPainter &painter);
|
||||
void paintSelection(QPainter &painter, int messageIndex, Selection &selection);
|
||||
|
||||
private:
|
||||
// helpers
|
||||
void _addElement(MessageLayoutElement *element);
|
||||
|
||||
// variables
|
||||
int line;
|
||||
int height;
|
||||
int currentX, currentY;
|
||||
size_t lineStart = 0;
|
||||
int lineHeight = 0;
|
||||
int spaceWidth = 4;
|
||||
std::vector<std::unique_ptr<MessageLayoutElement>> elements;
|
||||
};
|
||||
} // namespace layouts
|
||||
}
|
||||
}
|
132
src/messages/layouts/messagelayoutelement.cpp
Normal file
132
src/messages/layouts/messagelayoutelement.cpp
Normal file
|
@ -0,0 +1,132 @@
|
|||
#include "messages/layouts/messagelayoutelement.hpp"
|
||||
#include "messages/messageelement.hpp"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
namespace layouts {
|
||||
|
||||
const QRect &MessageLayoutElement::getRect() const
|
||||
{
|
||||
return this->rect;
|
||||
}
|
||||
|
||||
MessageLayoutElement::MessageLayoutElement(MessageElement &_creator, const QSize &size)
|
||||
: creator(_creator)
|
||||
{
|
||||
this->rect.setSize(size);
|
||||
}
|
||||
|
||||
MessageElement &MessageLayoutElement::getCreator() const
|
||||
{
|
||||
return this->creator;
|
||||
}
|
||||
|
||||
void MessageLayoutElement::setPosition(QPoint point)
|
||||
{
|
||||
this->rect.moveTopLeft(point);
|
||||
}
|
||||
|
||||
bool MessageLayoutElement::hasTrailingSpace() const
|
||||
{
|
||||
return this->trailingSpace;
|
||||
}
|
||||
|
||||
MessageLayoutElement *MessageLayoutElement::setTrailingSpace(bool value)
|
||||
{
|
||||
this->trailingSpace = value;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
//
|
||||
// IMAGE
|
||||
//
|
||||
|
||||
ImageLayoutElement::ImageLayoutElement(MessageElement &_creator, Image &_image, QSize _size)
|
||||
: MessageLayoutElement(_creator, _size)
|
||||
, image(_image)
|
||||
{
|
||||
this->trailingSpace = _creator.hasTrailingSpace();
|
||||
}
|
||||
|
||||
void ImageLayoutElement::addCopyTextToString(QString &str, int from, int to) const
|
||||
{
|
||||
str += "<image>";
|
||||
}
|
||||
|
||||
int ImageLayoutElement::getSelectionIndexCount()
|
||||
{
|
||||
return this->trailingSpace ? 2 : 1;
|
||||
}
|
||||
|
||||
void ImageLayoutElement::paint(QPainter &painter)
|
||||
{
|
||||
const QPixmap *pixmap = this->image.getPixmap();
|
||||
|
||||
if (pixmap != nullptr && !this->image.isAnimated()) {
|
||||
// fourtf: make it use qreal values
|
||||
painter.drawPixmap(QRectF(this->getRect()), *pixmap, QRectF());
|
||||
}
|
||||
}
|
||||
|
||||
void ImageLayoutElement::paintAnimated(QPainter &painter)
|
||||
{
|
||||
if (this->image.isAnimated()) {
|
||||
if (this->image.getPixmap() != nullptr) {
|
||||
// fourtf: make it use qreal values
|
||||
painter.drawPixmap(QRectF(this->getRect()), *this->image.getPixmap(), QRectF());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ImageLayoutElement::getMouseOverIndex(const QPoint &abs)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// TEXT
|
||||
//
|
||||
|
||||
TextLayoutElement::TextLayoutElement(MessageElement &_creator, QString &_text, QSize _size,
|
||||
QColor _color, FontStyle _style, float _scale)
|
||||
: MessageLayoutElement(_creator, _size)
|
||||
, text(_text)
|
||||
, color(_color)
|
||||
, style(_style)
|
||||
, scale(_scale)
|
||||
{
|
||||
}
|
||||
|
||||
void TextLayoutElement::addCopyTextToString(QString &str, int from, int to) const
|
||||
{
|
||||
}
|
||||
|
||||
int TextLayoutElement::getSelectionIndexCount()
|
||||
{
|
||||
return this->text.length() + (this->trailingSpace ? 1 : 0);
|
||||
}
|
||||
|
||||
void TextLayoutElement::paint(QPainter &painter)
|
||||
{
|
||||
painter.setPen(this->color);
|
||||
|
||||
painter.setFont(singletons::FontManager::getInstance().getFont(this->style, this->scale));
|
||||
|
||||
painter.drawText(QRectF(this->getRect().x(), this->getRect().y(), 10000, 10000), this->text,
|
||||
QTextOption(Qt::AlignLeft | Qt::AlignTop));
|
||||
}
|
||||
|
||||
void TextLayoutElement::paintAnimated(QPainter &painter)
|
||||
{
|
||||
}
|
||||
|
||||
int TextLayoutElement::getMouseOverIndex(const QPoint &abs)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
} // namespace layouts
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
88
src/messages/layouts/messagelayoutelement.hpp
Normal file
88
src/messages/layouts/messagelayoutelement.hpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
#pragma once
|
||||
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <climits>
|
||||
|
||||
#include "messages/messagecolor.hpp"
|
||||
#include "singletons/fontmanager.hpp"
|
||||
|
||||
class QPainter;
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
class MessageElement;
|
||||
class Image;
|
||||
|
||||
namespace layouts {
|
||||
|
||||
class MessageLayoutElement : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
MessageLayoutElement(MessageElement &creator, const QSize &size);
|
||||
|
||||
const QRect &getRect() const;
|
||||
MessageElement &getCreator() const;
|
||||
void setPosition(QPoint point);
|
||||
bool hasTrailingSpace() const;
|
||||
|
||||
MessageLayoutElement *setTrailingSpace(bool value);
|
||||
|
||||
virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const = 0;
|
||||
virtual int getSelectionIndexCount() = 0;
|
||||
virtual void paint(QPainter &painter) = 0;
|
||||
virtual void paintAnimated(QPainter &painter) = 0;
|
||||
virtual int getMouseOverIndex(const QPoint &abs) = 0;
|
||||
|
||||
protected:
|
||||
bool trailingSpace = true;
|
||||
|
||||
private:
|
||||
QRect rect;
|
||||
// bool isInNewLine;
|
||||
MessageElement &creator;
|
||||
};
|
||||
|
||||
// IMAGE
|
||||
class ImageLayoutElement : public MessageLayoutElement
|
||||
{
|
||||
public:
|
||||
ImageLayoutElement(MessageElement &creator, Image &image, QSize size);
|
||||
|
||||
protected:
|
||||
virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const override;
|
||||
virtual int getSelectionIndexCount() override;
|
||||
virtual void paint(QPainter &painter) override;
|
||||
virtual void paintAnimated(QPainter &painter) override;
|
||||
virtual int getMouseOverIndex(const QPoint &abs) override;
|
||||
|
||||
private:
|
||||
Image ℑ
|
||||
};
|
||||
|
||||
// TEXT
|
||||
class TextLayoutElement : public MessageLayoutElement
|
||||
{
|
||||
public:
|
||||
TextLayoutElement(MessageElement &creator, QString &text, QSize size, QColor color,
|
||||
FontStyle style, float scale);
|
||||
|
||||
protected:
|
||||
virtual void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const override;
|
||||
virtual int getSelectionIndexCount() override;
|
||||
virtual void paint(QPainter &painter) override;
|
||||
virtual void paintAnimated(QPainter &painter) override;
|
||||
virtual int getMouseOverIndex(const QPoint &abs) override;
|
||||
|
||||
private:
|
||||
QString text;
|
||||
QColor color;
|
||||
FontStyle style;
|
||||
float scale;
|
||||
};
|
||||
} // namespace layouts
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
|
@ -171,7 +171,7 @@ public:
|
|||
{
|
||||
std::lock_guard<std::mutex> lock(this->mutex);
|
||||
|
||||
int x = 0;
|
||||
size_t x = 0;
|
||||
|
||||
for (size_t i = 0; i < this->chunks->size(); i++) {
|
||||
Chunk &chunk = this->chunks->at(i);
|
||||
|
@ -191,11 +191,12 @@ public:
|
|||
newChunk->at(j) = replacement;
|
||||
this->chunks->at(i) = newChunk;
|
||||
|
||||
return x;
|
||||
return true;
|
||||
}
|
||||
x++;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// void insertAfter(const std::vector<T> &items, const T &index)
|
||||
|
|
|
@ -1,67 +1,34 @@
|
|||
#include "messages/message.hpp"
|
||||
#include "channel.hpp"
|
||||
#include "emojis.hpp"
|
||||
#include "messages/link.hpp"
|
||||
#include "singletons/emotemanager.hpp"
|
||||
#include "singletons/fontmanager.hpp"
|
||||
#include "singletons/ircmanager.hpp"
|
||||
#include "singletons/resourcemanager.hpp"
|
||||
#include "singletons/thememanager.hpp"
|
||||
#include "messageelement.hpp"
|
||||
#include "util/irchelpers.hpp"
|
||||
|
||||
#include <ctime>
|
||||
#include <list>
|
||||
#include <tuple>
|
||||
|
||||
typedef chatterino::widgets::ScrollbarHighlight SBHighlight;
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
bool Message::containsHighlightedPhrase() const
|
||||
// elements
|
||||
void Message::addElement(MessageElement *element)
|
||||
{
|
||||
return this->highlightTab;
|
||||
this->elements.push_back(std::unique_ptr<MessageElement>(element));
|
||||
}
|
||||
|
||||
void Message::setHighlight(bool value)
|
||||
const std::vector<std::unique_ptr<MessageElement>> &Message::getElements() const
|
||||
{
|
||||
this->highlightTab = value;
|
||||
}
|
||||
|
||||
const QString &Message::getTimeoutUser() const
|
||||
{
|
||||
return this->timeoutUser;
|
||||
}
|
||||
|
||||
int Message::getTimeoutCount() const
|
||||
{
|
||||
return this->timeoutCount;
|
||||
}
|
||||
|
||||
const QString &Message::getContent() const
|
||||
{
|
||||
if (this->content.isNull()) {
|
||||
this->updateContent();
|
||||
}
|
||||
|
||||
return this->content;
|
||||
}
|
||||
|
||||
const std::chrono::time_point<std::chrono::system_clock> &Message::getParseTime() const
|
||||
{
|
||||
return this->parseTime;
|
||||
}
|
||||
|
||||
std::vector<Word> &Message::getWords()
|
||||
{
|
||||
return this->words;
|
||||
return this->elements;
|
||||
}
|
||||
|
||||
// Flags
|
||||
Message::MessageFlags Message::getFlags() const
|
||||
{
|
||||
return this->flags;
|
||||
}
|
||||
|
||||
bool Message::hasFlags(MessageFlags _flags) const
|
||||
{
|
||||
return this->flags & _flags;
|
||||
}
|
||||
|
||||
void Message::setFlags(MessageFlags _flags)
|
||||
{
|
||||
this->flags = flags;
|
||||
|
@ -77,112 +44,73 @@ void Message::removeFlags(MessageFlags _flags)
|
|||
this->flags = (MessageFlags)((MessageFlagsType)this->flags & ~((MessageFlagsType)_flags));
|
||||
}
|
||||
|
||||
bool Message::isDisabled() const
|
||||
// Parse Time
|
||||
const QTime &Message::getParseTime() const
|
||||
{
|
||||
return this->disabled;
|
||||
}
|
||||
|
||||
void Message::setDisabled(bool value)
|
||||
{
|
||||
this->disabled = value;
|
||||
return this->parseTime;
|
||||
}
|
||||
|
||||
// Id
|
||||
const QString &Message::getId() const
|
||||
{
|
||||
return this->id;
|
||||
}
|
||||
|
||||
bool Message::getCollapsedDefault() const
|
||||
void Message::setId(const QString &_id)
|
||||
{
|
||||
return this->collapsedDefault;
|
||||
this->id = _id;
|
||||
}
|
||||
|
||||
void Message::setCollapsedDefault(bool value)
|
||||
// Search
|
||||
const QString &Message::getSearchText() const
|
||||
{
|
||||
this->collapsedDefault = value;
|
||||
}
|
||||
|
||||
bool Message::getDisableCompactEmotes() const
|
||||
{
|
||||
return this->disableCompactEmotes;
|
||||
}
|
||||
|
||||
void Message::setDisableCompactEmotes(bool value)
|
||||
{
|
||||
this->disableCompactEmotes = value;
|
||||
}
|
||||
|
||||
void Message::updateContent() const
|
||||
{
|
||||
QString _content("");
|
||||
|
||||
bool first;
|
||||
|
||||
for (const Word &word : this->words) {
|
||||
if (!first) {
|
||||
_content += "";
|
||||
}
|
||||
|
||||
_content += word.getCopyText();
|
||||
first = false;
|
||||
}
|
||||
|
||||
this->content = _content;
|
||||
// fourtf: asdf
|
||||
// if (this->searchText.isNull()) {
|
||||
// QString _content("");
|
||||
|
||||
// bool first;
|
||||
|
||||
// for (const MessageElement &word : this->words) {
|
||||
// if (!first) {
|
||||
// _content += "";
|
||||
// }
|
||||
|
||||
// _content += word.getCopyText();
|
||||
// first = false;
|
||||
// }
|
||||
|
||||
// this->searchText = _content;
|
||||
// }
|
||||
|
||||
// return this->searchText;
|
||||
|
||||
static QString xd;
|
||||
|
||||
return xd;
|
||||
}
|
||||
|
||||
// Highlight
|
||||
SBHighlight Message::getScrollBarHighlight() const
|
||||
{
|
||||
if (this->getFlags() & Message::Highlighted) {
|
||||
if (this->hasFlags(Message::Highlighted)) {
|
||||
return SBHighlight(SBHighlight::Highlight);
|
||||
}
|
||||
return SBHighlight();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void AddCurrentTimestamp(Message *message)
|
||||
// Static
|
||||
MessagePtr Message::createSystemMessage(const QString &text)
|
||||
{
|
||||
std::time_t t;
|
||||
time(&t);
|
||||
char timeStampBuffer[69];
|
||||
MessagePtr message(new Message);
|
||||
|
||||
// Add word for timestamp with no seconds
|
||||
strftime(timeStampBuffer, 69, "%H:%M", localtime(&t));
|
||||
QString timestampNoSeconds(timeStampBuffer);
|
||||
message->getWords().push_back(Word(timestampNoSeconds, Word::TimestampNoSeconds,
|
||||
MessageColor(MessageColor::System),
|
||||
singletons::FontManager::Medium, QString(), QString()));
|
||||
|
||||
// Add word for timestamp with seconds
|
||||
strftime(timeStampBuffer, 69, "%H:%M:%S", localtime(&t));
|
||||
QString timestampWithSeconds(timeStampBuffer);
|
||||
message->getWords().push_back(Word(timestampWithSeconds, Word::TimestampWithSeconds,
|
||||
MessageColor(MessageColor::System),
|
||||
singletons::FontManager::Medium, QString(), QString()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// Static
|
||||
Message *Message::createSystemMessage(const QString &text)
|
||||
{
|
||||
Message *message = new Message;
|
||||
|
||||
AddCurrentTimestamp(message);
|
||||
|
||||
QStringList words = text.split(' ');
|
||||
|
||||
for (QString word : words) {
|
||||
message->getWords().push_back(Word(word, Word::Flags::Default,
|
||||
MessageColor(MessageColor::Type::System),
|
||||
singletons::FontManager::Medium, word, QString()));
|
||||
}
|
||||
message->addElement(new TimestampElement(QTime::currentTime()));
|
||||
message->addElement(new TextElement(text, MessageElement::Text, MessageColor::System));
|
||||
message->addFlags(Message::System);
|
||||
|
||||
return message;
|
||||
return MessagePtr(message);
|
||||
}
|
||||
|
||||
Message *Message::createTimeoutMessage(const QString &username, const QString &durationInSeconds,
|
||||
MessagePtr Message::createTimeoutMessage(const QString &username, const QString &durationInSeconds,
|
||||
const QString &reason, bool multipleTimes)
|
||||
{
|
||||
QString text;
|
||||
|
@ -207,7 +135,7 @@ Message *Message::createTimeoutMessage(const QString &username, const QString &d
|
|||
|
||||
if (reason.length() > 0) {
|
||||
text.append(": \"");
|
||||
text.append(ParseTagString(reason));
|
||||
text.append(util::ParseTagString(reason));
|
||||
text.append("\"");
|
||||
}
|
||||
text.append(".");
|
||||
|
@ -216,7 +144,7 @@ Message *Message::createTimeoutMessage(const QString &username, const QString &d
|
|||
text.append(" (multiple times)");
|
||||
}
|
||||
|
||||
Message *message = Message::createSystemMessage(text);
|
||||
MessagePtr message = Message::createSystemMessage(text);
|
||||
message->addFlags(Message::Timeout);
|
||||
message->timeoutUser = username;
|
||||
return message;
|
||||
|
|
|
@ -1,94 +1,88 @@
|
|||
#pragma once
|
||||
|
||||
#include "messages/word.hpp"
|
||||
#include "messages/messageelement.hpp"
|
||||
#include "widgets/helper/scrollbarhighlight.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QTime>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Channel;
|
||||
|
||||
namespace messages {
|
||||
class Message;
|
||||
|
||||
typedef std::shared_ptr<Message> SharedMessage;
|
||||
typedef char MessageFlagsType;
|
||||
typedef std::shared_ptr<Message> MessagePtr;
|
||||
typedef uint8_t MessageFlagsType;
|
||||
|
||||
class Message
|
||||
{
|
||||
public:
|
||||
enum MessageFlags : MessageFlagsType {
|
||||
None = 0,
|
||||
System = (1 << 1),
|
||||
Timeout = (1 << 2),
|
||||
Highlighted = (1 << 3),
|
||||
System = (1 << 0),
|
||||
Timeout = (1 << 1),
|
||||
Highlighted = (1 << 2),
|
||||
DoNotTriggerNotification = (1 << 3), // disable notification sound
|
||||
Centered = (1 << 4),
|
||||
Disabled = (1 << 5),
|
||||
DisableCompactEmotes = (1 << 6),
|
||||
Collapsed = (1 << 7),
|
||||
};
|
||||
|
||||
bool containsHighlightedPhrase() const;
|
||||
void setHighlight(bool value);
|
||||
const QString &getTimeoutUser() const;
|
||||
int getTimeoutCount() const;
|
||||
const QString &getContent() const;
|
||||
const std::chrono::time_point<std::chrono::system_clock> &getParseTime() const;
|
||||
std::vector<Word> &getWords();
|
||||
// Elements
|
||||
// Messages should not be added after the message is done initializing.
|
||||
void addElement(MessageElement *element);
|
||||
const std::vector<std::unique_ptr<MessageElement>> &getElements() const;
|
||||
|
||||
// Message flags
|
||||
MessageFlags getFlags() const;
|
||||
bool hasFlags(MessageFlags flags) const;
|
||||
void setFlags(MessageFlags flags);
|
||||
void addFlags(MessageFlags flags);
|
||||
void removeFlags(MessageFlags flags);
|
||||
bool isDisabled() const;
|
||||
void setDisabled(bool value);
|
||||
|
||||
// Parse Time
|
||||
const QTime &getParseTime() const;
|
||||
|
||||
// Id
|
||||
const QString &getId() const;
|
||||
bool getCollapsedDefault() const;
|
||||
void setCollapsedDefault(bool value);
|
||||
bool getDisableCompactEmotes() const;
|
||||
void setDisableCompactEmotes(bool value);
|
||||
void updateContent() const;
|
||||
void setId(const QString &id);
|
||||
|
||||
// Searching
|
||||
const QString &getSearchText() const;
|
||||
|
||||
// Scrollbar
|
||||
widgets::ScrollbarHighlight getScrollBarHighlight() const;
|
||||
|
||||
// Usernames
|
||||
QString loginName;
|
||||
QString displayName;
|
||||
QString localizedName;
|
||||
QString timeoutUser;
|
||||
|
||||
const QString text;
|
||||
bool centered = false;
|
||||
// Timeouts
|
||||
const QString &getTimeoutUser(const QString &value) const;
|
||||
void setTimeoutUser();
|
||||
|
||||
static Message *createSystemMessage(const QString &text);
|
||||
// Static
|
||||
static MessagePtr createSystemMessage(const QString &text);
|
||||
|
||||
static Message *createTimeoutMessage(const QString &username, const QString &durationInSeconds,
|
||||
const QString &reason, bool multipleTimes);
|
||||
static MessagePtr createTimeoutMessage(const QString &username,
|
||||
const QString &durationInSeconds, const QString &reason,
|
||||
bool multipleTimes);
|
||||
|
||||
private:
|
||||
static LazyLoadedImage *badgeStaff;
|
||||
static LazyLoadedImage *badgeAdmin;
|
||||
static LazyLoadedImage *badgeGlobalmod;
|
||||
static LazyLoadedImage *badgeModerator;
|
||||
static LazyLoadedImage *badgeTurbo;
|
||||
static LazyLoadedImage *badgeBroadcaster;
|
||||
static LazyLoadedImage *badgePremium;
|
||||
|
||||
static QRegularExpression *cheerRegex;
|
||||
|
||||
MessageFlags flags = MessageFlags::None;
|
||||
|
||||
// what is highlightTab?
|
||||
bool highlightTab = false;
|
||||
|
||||
int timeoutCount = 0;
|
||||
bool disabled = false;
|
||||
|
||||
QString timeoutUser;
|
||||
bool collapsedDefault = false;
|
||||
bool disableCompactEmotes = false;
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock> parseTime;
|
||||
|
||||
mutable QString content;
|
||||
QTime parseTime;
|
||||
mutable QString searchText;
|
||||
QString id = "";
|
||||
|
||||
std::vector<Word> words;
|
||||
std::vector<std::unique_ptr<MessageElement>> elements;
|
||||
};
|
||||
|
||||
} // namespace messages
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include "messagebuilder.hpp"
|
||||
#include "singletons/thememanager.hpp"
|
||||
#include "singletons/emotemanager.hpp"
|
||||
#include "singletons/resourcemanager.hpp"
|
||||
#include "singletons/thememanager.hpp"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
|
@ -11,43 +11,35 @@ namespace messages {
|
|||
MessageBuilder::MessageBuilder()
|
||||
: message(new Message)
|
||||
{
|
||||
_parseTime = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
SharedMessage MessageBuilder::getMessage()
|
||||
MessagePtr MessageBuilder::getMessage()
|
||||
{
|
||||
return this->message;
|
||||
}
|
||||
|
||||
void MessageBuilder::appendWord(const Word &&word)
|
||||
void MessageBuilder::appendElement(MessageElement *element)
|
||||
{
|
||||
this->message->getWords().push_back(word);
|
||||
this->message->addElement(element);
|
||||
}
|
||||
|
||||
void MessageBuilder::appendTimestamp()
|
||||
{
|
||||
QDateTime t = QDateTime::currentDateTime();
|
||||
this->appendTimestamp(t);
|
||||
this->appendTimestamp(QTime::currentTime());
|
||||
}
|
||||
|
||||
void MessageBuilder::setHighlight(bool value)
|
||||
{
|
||||
this->message->setHighlight(value);
|
||||
if (value) {
|
||||
this->message->addFlags(Message::Highlighted);
|
||||
} else {
|
||||
this->message->removeFlags(Message::Highlighted);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageBuilder::appendTimestamp(QDateTime &time)
|
||||
void MessageBuilder::appendTimestamp(const QTime &time)
|
||||
{
|
||||
// Add word for timestamp with no seconds
|
||||
QString timestampNoSeconds(time.toString("hh:mm"));
|
||||
this->appendWord(Word(timestampNoSeconds, Word::TimestampNoSeconds,
|
||||
MessageColor(MessageColor::System), singletons::FontManager::Medium, QString(),
|
||||
QString()));
|
||||
|
||||
// Add word for timestamp with seconds
|
||||
QString timestampWithSeconds(time.toString("hh:mm:ss"));
|
||||
this->appendWord(Word(timestampWithSeconds, Word::TimestampWithSeconds,
|
||||
MessageColor(MessageColor::System), singletons::FontManager::Medium, QString(),
|
||||
QString()));
|
||||
this->appendElement(new TimestampElement(time));
|
||||
}
|
||||
|
||||
QString MessageBuilder::matchLink(const QString &string)
|
||||
|
|
|
@ -14,25 +14,32 @@ class MessageBuilder
|
|||
public:
|
||||
MessageBuilder();
|
||||
|
||||
SharedMessage getMessage();
|
||||
MessagePtr getMessage();
|
||||
|
||||
void appendWord(const Word &&word);
|
||||
void appendTimestamp();
|
||||
void appendTimestamp(QDateTime &time);
|
||||
void setHighlight(bool value);
|
||||
void appendElement(MessageElement *element);
|
||||
|
||||
// typename std::enable_if<std::is_base_of<MessageElement, T>::value, T>::type
|
||||
|
||||
template <class T, class... Args>
|
||||
T *append(Args &&... args)
|
||||
{
|
||||
static_assert(std::is_base_of<MessageElement, T>::value, "T must extend MessageElement");
|
||||
|
||||
T *element = new T(std::forward<Args>(args)...);
|
||||
this->appendElement(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
void appendTimestamp();
|
||||
void appendTimestamp(const QTime &time);
|
||||
|
||||
QString matchLink(const QString &string);
|
||||
QRegularExpression linkRegex;
|
||||
|
||||
QString originalMessage;
|
||||
|
||||
protected:
|
||||
std::shared_ptr<messages::Message> message;
|
||||
|
||||
private:
|
||||
std::vector<Word> _words;
|
||||
bool highlight = false;
|
||||
std::chrono::time_point<std::chrono::system_clock> _parseTime;
|
||||
MessagePtr message;
|
||||
};
|
||||
|
||||
} // namespace messages
|
||||
|
|
|
@ -12,8 +12,8 @@ class MessageColor
|
|||
public:
|
||||
enum Type { Custom, Text, Link, System };
|
||||
|
||||
explicit MessageColor(const QColor &color);
|
||||
explicit MessageColor(Type type = Text);
|
||||
MessageColor(const QColor &color);
|
||||
MessageColor(Type type = Text);
|
||||
|
||||
Type getType() const;
|
||||
const QColor &getColor(singletons::ThemeManager &themeManager) const;
|
||||
|
|
249
src/messages/messageelement.cpp
Normal file
249
src/messages/messageelement.cpp
Normal file
|
@ -0,0 +1,249 @@
|
|||
#include "messages/messageelement.hpp"
|
||||
#include "messages/layouts/messagelayoutcontainer.hpp"
|
||||
#include "messages/layouts/messagelayoutelement.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
#include "util/benchmark.hpp"
|
||||
#include "util/emotemap.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
MessageElement::MessageElement(Flags _flags)
|
||||
: flags(_flags)
|
||||
{
|
||||
}
|
||||
|
||||
MessageElement *MessageElement::setLink(const Link &_link)
|
||||
{
|
||||
this->link = _link;
|
||||
return this;
|
||||
}
|
||||
|
||||
MessageElement *MessageElement::setTooltip(const QString &_tooltip)
|
||||
{
|
||||
this->tooltip = _tooltip;
|
||||
return this;
|
||||
}
|
||||
|
||||
MessageElement *MessageElement::setTrailingSpace(bool value)
|
||||
{
|
||||
this->trailingSpace = value;
|
||||
}
|
||||
|
||||
const QString &MessageElement::getTooltip() const
|
||||
{
|
||||
return this->tooltip;
|
||||
}
|
||||
|
||||
const Link &MessageElement::getLink() const
|
||||
{
|
||||
return this->link;
|
||||
}
|
||||
|
||||
bool MessageElement::hasTrailingSpace() const
|
||||
{
|
||||
return this->trailingSpace;
|
||||
}
|
||||
|
||||
MessageElement::Flags MessageElement::getFlags() const
|
||||
{
|
||||
return this->flags;
|
||||
}
|
||||
|
||||
// IMAGE
|
||||
ImageElement::ImageElement(Image &_image, MessageElement::Flags flags)
|
||||
: MessageElement(flags)
|
||||
, image(_image)
|
||||
{
|
||||
this->setTooltip(_image.getTooltip());
|
||||
}
|
||||
|
||||
void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
|
||||
{
|
||||
QSize size(this->image.getWidth() * this->image.getScale() * container.scale,
|
||||
this->image.getHeight() * this->image.getScale() * container.scale);
|
||||
|
||||
container.addElement(new ImageLayoutElement(*this, this->image, size));
|
||||
}
|
||||
|
||||
void ImageElement::update(UpdateFlags _flags)
|
||||
{
|
||||
}
|
||||
|
||||
// EMOTE
|
||||
EmoteElement::EmoteElement(const util::EmoteData &_data, MessageElement::Flags flags)
|
||||
: MessageElement(flags)
|
||||
, data(_data)
|
||||
{
|
||||
if (_data.isValid()) {
|
||||
this->setTooltip(data.image1x->getTooltip());
|
||||
}
|
||||
}
|
||||
|
||||
void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
|
||||
{
|
||||
if (!this->data.isValid()) {
|
||||
qDebug() << "EmoteElement::data is invalid xD";
|
||||
return;
|
||||
}
|
||||
|
||||
int quality = singletons::SettingManager::getInstance().preferredEmoteQuality;
|
||||
|
||||
Image *_image;
|
||||
if (quality == 3 && this->data.image3x != nullptr) {
|
||||
_image = this->data.image3x;
|
||||
} else if (quality >= 2 && this->data.image2x != nullptr) {
|
||||
_image = this->data.image2x;
|
||||
} else {
|
||||
_image = this->data.image1x;
|
||||
}
|
||||
|
||||
QSize size((int)(container.scale * _image->getScaledWidth()),
|
||||
(int)(container.scale * _image->getScaledHeight()));
|
||||
|
||||
container.addElement(new ImageLayoutElement(*this, *_image, size));
|
||||
}
|
||||
|
||||
void EmoteElement::update(UpdateFlags _flags)
|
||||
{
|
||||
}
|
||||
|
||||
// TEXT
|
||||
TextElement::TextElement(const QString &text, MessageElement::Flags flags,
|
||||
const MessageColor &_color, FontStyle _style)
|
||||
: MessageElement(flags)
|
||||
, color(_color)
|
||||
, style(_style)
|
||||
{
|
||||
for (QString word : text.split(' ')) {
|
||||
this->words.push_back({word, -1});
|
||||
// fourtf: add logic to store mutliple spaces after message
|
||||
}
|
||||
}
|
||||
|
||||
void TextElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
|
||||
{
|
||||
QFontMetrics &metrics =
|
||||
singletons::FontManager::getInstance().getFontMetrics(this->style, container.scale);
|
||||
singletons::ThemeManager &themeManager = singletons::ThemeManager::ThemeManager::getInstance();
|
||||
|
||||
for (Word &word : this->words) {
|
||||
auto getTextLayoutElement = [&](QString text, int width) {
|
||||
return new TextLayoutElement(*this, text, QSize(width, metrics.height()),
|
||||
this->color.getColor(themeManager), this->style,
|
||||
container.scale);
|
||||
};
|
||||
|
||||
if (word.width == -1) {
|
||||
word.width = metrics.width(word.text);
|
||||
}
|
||||
|
||||
// see if the text fits in the current line
|
||||
if (container.fitsInLine(word.width)) {
|
||||
container.addElementNoLineBreak(getTextLayoutElement(word.text, word.width));
|
||||
continue;
|
||||
}
|
||||
|
||||
// see if the text fits in the next line
|
||||
if (!container.atStartOfLine()) {
|
||||
container.breakLine();
|
||||
|
||||
if (container.fitsInLine(word.width)) {
|
||||
container.addElementNoLineBreak(getTextLayoutElement(word.text, word.width));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// we done goofed, we need to wrap the text
|
||||
QString text = word.text;
|
||||
int textLength = text.length();
|
||||
int wordStart = 0;
|
||||
int width = metrics.width(text[0]);
|
||||
int lastWidth = 0;
|
||||
|
||||
for (int i = 1; i < textLength; i++) {
|
||||
int chatWidth = metrics.width(text[i]);
|
||||
|
||||
if (!container.fitsInLine(width + chatWidth)) {
|
||||
container.addElementNoLineBreak(
|
||||
getTextLayoutElement(text.mid(wordStart, i - wordStart), width - lastWidth));
|
||||
container.breakLine();
|
||||
|
||||
i += 2;
|
||||
wordStart = i;
|
||||
lastWidth = width;
|
||||
width += chatWidth;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
container.addElement(getTextLayoutElement(text.mid(wordStart), word.width - lastWidth));
|
||||
}
|
||||
}
|
||||
|
||||
void TextElement::update(UpdateFlags _flags)
|
||||
{
|
||||
if (_flags & UpdateFlags::Update_Text) {
|
||||
for (Word &word : this->words) {
|
||||
word.width = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TIMESTAMP
|
||||
TimestampElement::TimestampElement()
|
||||
: TimestampElement(QTime::currentTime())
|
||||
{
|
||||
}
|
||||
|
||||
TimestampElement::TimestampElement(QTime _time)
|
||||
: MessageElement(MessageElement::Timestamp)
|
||||
, time(_time)
|
||||
, element(formatTime(_time))
|
||||
{
|
||||
assert(this->element != nullptr);
|
||||
}
|
||||
|
||||
TimestampElement::~TimestampElement()
|
||||
{
|
||||
delete this->element;
|
||||
}
|
||||
|
||||
void TimestampElement::addToContainer(MessageLayoutContainer &container,
|
||||
MessageElement::Flags _flags)
|
||||
{
|
||||
this->element->addToContainer(container, _flags);
|
||||
}
|
||||
|
||||
void TimestampElement::update(UpdateFlags _flags)
|
||||
{
|
||||
if (_flags == UpdateFlags::Update_All) {
|
||||
this->element = TimestampElement::formatTime(this->time);
|
||||
} else {
|
||||
this->element->update(_flags);
|
||||
}
|
||||
}
|
||||
|
||||
TextElement *TimestampElement::formatTime(const QTime &time)
|
||||
{
|
||||
QString text = time.toString(singletons::SettingManager::getInstance().timestampFormat);
|
||||
|
||||
return new TextElement(text, Flags::Timestamp, MessageColor::System, FontStyle::Medium);
|
||||
}
|
||||
|
||||
// TWITCH MODERATION
|
||||
TwitchModerationElement::TwitchModerationElement()
|
||||
: MessageElement(MessageElement::ModeratorTools)
|
||||
{
|
||||
}
|
||||
|
||||
void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
|
||||
MessageElement::Flags _flags)
|
||||
{
|
||||
}
|
||||
|
||||
void TwitchModerationElement::update(UpdateFlags _flags)
|
||||
{
|
||||
}
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
224
src/messages/messageelement.hpp
Normal file
224
src/messages/messageelement.hpp
Normal file
|
@ -0,0 +1,224 @@
|
|||
#pragma once
|
||||
|
||||
#include "messages/image.hpp"
|
||||
#include "messages/link.hpp"
|
||||
#include "messages/messagecolor.hpp"
|
||||
#include "singletons/fontmanager.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
#include <QTime>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <util/emotemap.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
class Channel;
|
||||
namespace util {
|
||||
struct EmoteData;
|
||||
}
|
||||
namespace messages {
|
||||
namespace layouts {
|
||||
class MessageLayoutContainer;
|
||||
}
|
||||
|
||||
using namespace chatterino::messages::layouts;
|
||||
|
||||
class MessageElement : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
enum Flags : uint32_t {
|
||||
None = 0,
|
||||
Misc = (1 << 0),
|
||||
Text = (1 << 1),
|
||||
|
||||
Username = (1 << 2),
|
||||
Timestamp = (1 << 3),
|
||||
|
||||
TwitchEmoteImage = (1 << 4),
|
||||
TwitchEmoteText = (1 << 5),
|
||||
TwitchEmote = TwitchEmoteImage | TwitchEmoteText,
|
||||
BttvEmoteImage = (1 << 6),
|
||||
BttvEmoteText = (1 << 7),
|
||||
BttvEmote = BttvEmoteImage | BttvEmoteText,
|
||||
FfzEmoteImage = (1 << 10),
|
||||
FfzEmoteText = (1 << 11),
|
||||
FfzEmote = FfzEmoteImage | FfzEmoteText,
|
||||
EmoteImages = TwitchEmoteImage | BttvEmoteImage | FfzEmoteImage,
|
||||
|
||||
BitsStatic = (1 << 12),
|
||||
BitsAnimated = (1 << 13),
|
||||
|
||||
// Slot 1: Twitch
|
||||
// - Staff badge
|
||||
// - Admin badge
|
||||
// - Global Moderator badge
|
||||
BadgeGlobalAuthority = (1 << 14),
|
||||
|
||||
// Slot 2: Twitch
|
||||
// - Moderator badge
|
||||
// - Broadcaster badge
|
||||
BadgeChannelAuthority = (1 << 15),
|
||||
|
||||
// Slot 3: Twitch
|
||||
// - Subscription badges
|
||||
BadgeSubscription = (1 << 16),
|
||||
|
||||
// Slot 4: Twitch
|
||||
// - Turbo badge
|
||||
// - Prime badge
|
||||
// - Bit badges
|
||||
// - Game badges
|
||||
BadgeVanity = (1 << 17),
|
||||
|
||||
// Slot 5: Chatterino
|
||||
// - Chatterino developer badge
|
||||
// - Chatterino donator badge
|
||||
// - Chatterino top donator badge
|
||||
BadgeChatterino = (1 << 18),
|
||||
|
||||
// Rest of slots: ffz custom badge? bttv custom badge? mywaifu (puke) custom badge?
|
||||
|
||||
Badges = BadgeGlobalAuthority | BadgeChannelAuthority | BadgeSubscription | BadgeVanity |
|
||||
BadgeChatterino,
|
||||
|
||||
ChannelName = (1 << 19),
|
||||
|
||||
BitsAmount = (1 << 20),
|
||||
|
||||
ModeratorTools = (1 << 21),
|
||||
|
||||
EmojiImage = (1 << 23),
|
||||
EmojiText = (1 << 24),
|
||||
EmojiAll = EmojiImage | EmojiText,
|
||||
|
||||
AlwaysShow = (1 << 25),
|
||||
|
||||
// used in the ChannelView class to make the collapse buttons visible if needed
|
||||
Collapsed = (1 << 26),
|
||||
|
||||
Default = Timestamp | Badges | Username | BitsStatic | FfzEmoteImage | BttvEmoteImage |
|
||||
TwitchEmoteImage | BitsAmount | Text | AlwaysShow,
|
||||
};
|
||||
|
||||
enum UpdateFlags : char {
|
||||
Update_Text,
|
||||
Update_Emotes,
|
||||
Update_Images,
|
||||
Update_All = Update_Text | Update_Emotes | Update_Images
|
||||
};
|
||||
|
||||
virtual ~MessageElement()
|
||||
{
|
||||
}
|
||||
|
||||
MessageElement *setLink(const Link &link);
|
||||
MessageElement *setTooltip(const QString &tooltip);
|
||||
MessageElement *setTrailingSpace(bool value);
|
||||
const QString &getTooltip() const;
|
||||
const Link &getLink() const;
|
||||
bool hasTrailingSpace() const;
|
||||
Flags getFlags() const;
|
||||
|
||||
virtual void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) = 0;
|
||||
virtual void update(UpdateFlags flags) = 0;
|
||||
|
||||
protected:
|
||||
MessageElement(Flags flags);
|
||||
bool trailingSpace = true;
|
||||
|
||||
private:
|
||||
Link link;
|
||||
QString tooltip;
|
||||
Flags flags;
|
||||
};
|
||||
|
||||
// contains a simple image
|
||||
class ImageElement : public MessageElement
|
||||
{
|
||||
Image ℑ
|
||||
|
||||
public:
|
||||
ImageElement(Image &image, MessageElement::Flags flags);
|
||||
|
||||
virtual void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElement::Flags flags) override;
|
||||
virtual void update(UpdateFlags flags) override;
|
||||
};
|
||||
|
||||
// contains emote data and will pick the emote based on :
|
||||
// a) are images for the emote type enabled
|
||||
// b) which size it wants
|
||||
class EmoteElement : public MessageElement
|
||||
{
|
||||
const util::EmoteData data;
|
||||
|
||||
public:
|
||||
EmoteElement(const util::EmoteData &data, MessageElement::Flags flags);
|
||||
|
||||
virtual void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElement::Flags flags) override;
|
||||
virtual void update(UpdateFlags flags) override;
|
||||
};
|
||||
|
||||
// contains a text, it will split it into words
|
||||
class TextElement : public MessageElement
|
||||
{
|
||||
MessageColor color;
|
||||
FontStyle style;
|
||||
|
||||
struct Word {
|
||||
QString text;
|
||||
int width = -1;
|
||||
};
|
||||
std::vector<Word> words;
|
||||
|
||||
public:
|
||||
TextElement(const QString &text, MessageElement::Flags flags,
|
||||
const MessageColor &color = MessageColor::Text,
|
||||
FontStyle style = FontStyle::Medium);
|
||||
|
||||
virtual void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElement::Flags flags) override;
|
||||
virtual void update(UpdateFlags flags);
|
||||
};
|
||||
|
||||
// contains a text, formated depending on the preferences
|
||||
class TimestampElement : public MessageElement
|
||||
{
|
||||
QTime time;
|
||||
TextElement *element;
|
||||
|
||||
public:
|
||||
TimestampElement();
|
||||
TimestampElement(QTime time);
|
||||
virtual ~TimestampElement();
|
||||
|
||||
virtual void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElement::Flags flags) override;
|
||||
virtual void update(UpdateFlags flags);
|
||||
|
||||
TextElement *formatTime(const QTime &time);
|
||||
};
|
||||
|
||||
// adds all the custom moderation buttons, adds a variable amount of items depending on settings
|
||||
// fourtf: implement
|
||||
class TwitchModerationElement : public MessageElement
|
||||
{
|
||||
public:
|
||||
TwitchModerationElement();
|
||||
|
||||
virtual void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElement::Flags flags) override;
|
||||
virtual void update(UpdateFlags flags);
|
||||
};
|
||||
|
||||
// adds bits as text, static image or animated image
|
||||
// class BitsElement : public MessageElement
|
||||
//{
|
||||
// public:
|
||||
// virtual void addToContainer(LayoutContainer &container) override;
|
||||
//};
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
|
@ -1,450 +0,0 @@
|
|||
#include "messages/messageref.hpp"
|
||||
#include "singletons/emotemanager.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#define MARGIN_LEFT (int)(8 * this->scale)
|
||||
#define MARGIN_RIGHT (int)(8 * this->scale)
|
||||
#define MARGIN_TOP (int)(4 * this->scale)
|
||||
#define MARGIN_BOTTOM (int)(4 * this->scale)
|
||||
#define COMPACT_EMOTES_OFFSET 6
|
||||
|
||||
using namespace chatterino::messages;
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
MessageRef::MessageRef(SharedMessage _message)
|
||||
: message(_message)
|
||||
, wordParts()
|
||||
, collapsed(_message->getCollapsedDefault())
|
||||
{
|
||||
}
|
||||
|
||||
Message *MessageRef::getMessage()
|
||||
{
|
||||
return this->message.get();
|
||||
}
|
||||
|
||||
int MessageRef::getHeight() const
|
||||
{
|
||||
return this->height;
|
||||
}
|
||||
|
||||
// return true if redraw is required
|
||||
bool MessageRef::layout(int width, float scale)
|
||||
{
|
||||
auto &emoteManager = singletons::EmoteManager::getInstance();
|
||||
|
||||
bool rebuildRequired = false, layoutRequired = false;
|
||||
|
||||
// check if width changed
|
||||
bool widthChanged = width != this->currentLayoutWidth;
|
||||
layoutRequired |= widthChanged;
|
||||
this->currentLayoutWidth = width;
|
||||
|
||||
// check if emotes changed
|
||||
bool imagesChanged = this->emoteGeneration != emoteManager.getGeneration();
|
||||
layoutRequired |= imagesChanged;
|
||||
this->emoteGeneration = emoteManager.getGeneration();
|
||||
|
||||
// check if text changed
|
||||
bool textChanged =
|
||||
this->fontGeneration != singletons::FontManager::getInstance().getGeneration();
|
||||
layoutRequired |= textChanged;
|
||||
this->fontGeneration = singletons::FontManager::getInstance().getGeneration();
|
||||
|
||||
// check if work mask changed
|
||||
bool wordMaskChanged =
|
||||
this->currentWordTypes != singletons::SettingManager::getInstance().getWordTypeMask();
|
||||
layoutRequired |= wordMaskChanged;
|
||||
this->currentWordTypes = singletons::SettingManager::getInstance().getWordTypeMask();
|
||||
|
||||
// check if dpi changed
|
||||
bool scaleChanged = this->scale != scale;
|
||||
layoutRequired |= scaleChanged;
|
||||
this->scale = scale;
|
||||
imagesChanged |= scaleChanged;
|
||||
textChanged |= scaleChanged;
|
||||
|
||||
// update word sizes if needed
|
||||
if (imagesChanged) {
|
||||
this->updateImageSizes();
|
||||
this->buffer = nullptr;
|
||||
}
|
||||
if (textChanged) {
|
||||
this->updateTextSizes();
|
||||
this->buffer = nullptr;
|
||||
}
|
||||
if (widthChanged || wordMaskChanged) {
|
||||
this->buffer = nullptr;
|
||||
}
|
||||
|
||||
// return if no layout is required
|
||||
if (!layoutRequired) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->actuallyLayout(width);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MessageRef::actuallyLayout(int width)
|
||||
{
|
||||
auto &settings = singletons::SettingManager::getInstance();
|
||||
|
||||
const int spaceWidth = 4;
|
||||
const int right = width - MARGIN_RIGHT;
|
||||
|
||||
bool compactEmotes = !this->getMessage()->getDisableCompactEmotes();
|
||||
|
||||
// clear word parts
|
||||
this->wordParts.clear();
|
||||
|
||||
// layout
|
||||
int x = MARGIN_LEFT;
|
||||
int y = MARGIN_TOP;
|
||||
|
||||
int lineNumber = 0;
|
||||
int lineStart = 0;
|
||||
int lineHeight = 0;
|
||||
int firstLineHeight = -1;
|
||||
bool first = true;
|
||||
|
||||
uint32_t flags = settings.getWordTypeMask();
|
||||
if (this->collapsed) {
|
||||
flags |= Word::Collapsed;
|
||||
}
|
||||
|
||||
// loop throught all the words and add them when a line is full
|
||||
for (auto it = this->message->getWords().begin(); it != this->message->getWords().end(); ++it) {
|
||||
Word &word = *it;
|
||||
|
||||
// Check if given word is supposed to be rendered by comparing it to the current setting
|
||||
if ((word.getFlags() & flags) == Word::None) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int xOffset = 0, yOffset = 0;
|
||||
|
||||
/// if (enableEmoteMargins) {
|
||||
/// if (word.isImage() && word.getImage().isHat()) {
|
||||
/// xOffset = -word.getWidth() + 2;
|
||||
/// } else {
|
||||
xOffset = word.getXOffset();
|
||||
yOffset = word.getYOffset();
|
||||
/// }
|
||||
/// }
|
||||
|
||||
// word wrapping
|
||||
if (word.isText() && word.getWidth(this->scale) + MARGIN_LEFT > right) {
|
||||
// align and end the current line
|
||||
this->_alignWordParts(lineStart, lineHeight, width, firstLineHeight);
|
||||
y += lineHeight;
|
||||
|
||||
int currentPartStart = 0;
|
||||
int currentLineWidth = 0;
|
||||
|
||||
// go through the text, break text when it doesn't fit in the line anymore
|
||||
for (int i = 1; i <= word.getText().length(); i++) {
|
||||
currentLineWidth += word.getCharWidth(i - 1, this->scale);
|
||||
|
||||
if (currentLineWidth + MARGIN_LEFT > right) {
|
||||
// add the current line
|
||||
QString mid = word.getText().mid(currentPartStart, i - currentPartStart - 1);
|
||||
|
||||
this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y, currentLineWidth,
|
||||
word.getHeight(this->scale), lineNumber, mid,
|
||||
mid, false, currentPartStart));
|
||||
|
||||
y += word.getFontMetrics(this->scale).height();
|
||||
|
||||
currentPartStart = i - 1;
|
||||
|
||||
currentLineWidth = 0;
|
||||
lineNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
QString mid(word.getText().mid(currentPartStart));
|
||||
currentLineWidth = word.getFontMetrics(this->scale).width(mid);
|
||||
|
||||
this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y - word.getHeight(this->scale),
|
||||
currentLineWidth, word.getHeight(this->scale),
|
||||
lineNumber, mid, mid, true, currentPartStart));
|
||||
|
||||
x = currentLineWidth + MARGIN_LEFT + spaceWidth;
|
||||
lineHeight = this->_updateLineHeight(0, word, compactEmotes);
|
||||
lineStart = this->wordParts.size() - 1;
|
||||
}
|
||||
// fits in the current line
|
||||
else if (first || x + word.getWidth(this->scale) + xOffset <= right) {
|
||||
this->wordParts.push_back(WordPart(word, x, y - word.getHeight(this->scale), scale,
|
||||
lineNumber, word.getCopyText()));
|
||||
|
||||
x += word.getWidth(this->scale) + xOffset;
|
||||
x += spaceWidth;
|
||||
|
||||
lineHeight = this->_updateLineHeight(lineHeight, word, compactEmotes);
|
||||
}
|
||||
// doesn't fit in the line
|
||||
else {
|
||||
// align and end the current line
|
||||
this->_alignWordParts(lineStart, lineHeight, width, firstLineHeight);
|
||||
|
||||
y += lineHeight;
|
||||
|
||||
lineNumber++;
|
||||
|
||||
this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y - word.getHeight(this->scale),
|
||||
this->scale, lineNumber, word.getCopyText()));
|
||||
|
||||
lineStart = this->wordParts.size() - 1;
|
||||
|
||||
lineHeight = this->_updateLineHeight(0, word, compactEmotes);
|
||||
|
||||
x = word.getWidth(this->scale) + MARGIN_LEFT;
|
||||
x += spaceWidth;
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
// align and end the current line
|
||||
this->_alignWordParts(lineStart, lineHeight, width, firstLineHeight);
|
||||
|
||||
this->collapsedHeight = firstLineHeight == -1 ? (int)(24 * this->scale)
|
||||
: firstLineHeight + MARGIN_TOP + MARGIN_BOTTOM;
|
||||
|
||||
// update height
|
||||
int oldHeight = this->height;
|
||||
|
||||
if (this->isCollapsed()) {
|
||||
this->height = this->collapsedHeight;
|
||||
} else {
|
||||
this->height = y + lineHeight + MARGIN_BOTTOM;
|
||||
}
|
||||
|
||||
// invalidate buffer if height changed
|
||||
if (oldHeight != this->height) {
|
||||
this->buffer = nullptr;
|
||||
}
|
||||
|
||||
updateBuffer = true;
|
||||
}
|
||||
|
||||
void MessageRef::updateTextSizes()
|
||||
{
|
||||
for (auto &word : this->message->getWords()) {
|
||||
if (!word.isText()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
word.updateSize();
|
||||
}
|
||||
}
|
||||
|
||||
void MessageRef::updateImageSizes()
|
||||
{
|
||||
for (auto &word : this->message->getWords()) {
|
||||
if (!word.isImage())
|
||||
continue;
|
||||
|
||||
word.updateSize();
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<WordPart> &MessageRef::getWordParts() const
|
||||
{
|
||||
return this->wordParts;
|
||||
}
|
||||
|
||||
void MessageRef::_alignWordParts(int lineStart, int lineHeight, int width, int &firstLineHeight)
|
||||
{
|
||||
bool compactEmotes = true;
|
||||
|
||||
if (firstLineHeight == -1) {
|
||||
firstLineHeight = lineHeight;
|
||||
}
|
||||
|
||||
int xOffset = 0;
|
||||
|
||||
if (this->message->centered && this->wordParts.size() > 0) {
|
||||
xOffset = (width - this->wordParts.at(this->wordParts.size() - 1).getRight()) / 2;
|
||||
}
|
||||
|
||||
for (size_t i = lineStart; i < this->wordParts.size(); i++) {
|
||||
WordPart &wordPart = this->wordParts.at(i);
|
||||
|
||||
const bool isCompactEmote = compactEmotes && wordPart.getWord().isImage() &&
|
||||
wordPart.getWord().getFlags() & Word::EmoteImages;
|
||||
|
||||
int yExtra = 0;
|
||||
if (isCompactEmote) {
|
||||
yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale;
|
||||
}
|
||||
|
||||
wordPart.setPosition(wordPart.getX() + xOffset, wordPart.getY() + lineHeight + yExtra);
|
||||
}
|
||||
}
|
||||
|
||||
int MessageRef::_updateLineHeight(int currentLineHeight, Word &word, bool compactEmotes)
|
||||
{
|
||||
int newLineHeight = word.getHeight(this->scale);
|
||||
|
||||
// fourtf: doesn't care about the height of a normal line
|
||||
if (compactEmotes && word.isImage() && word.getFlags() & Word::EmoteImages) {
|
||||
newLineHeight -= COMPACT_EMOTES_OFFSET * this->scale;
|
||||
}
|
||||
|
||||
return std::max(currentLineHeight, newLineHeight);
|
||||
}
|
||||
|
||||
const Word *MessageRef::tryGetWordPart(QPoint point)
|
||||
{
|
||||
// go through all words and return the first one that contains the point.
|
||||
for (WordPart &wordPart : this->wordParts) {
|
||||
if (wordPart.getRect().contains(point)) {
|
||||
return &wordPart.getWord();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// XXX(pajlada): This is probably not the optimal way to calculate this
|
||||
int MessageRef::getLastCharacterIndex() const
|
||||
{
|
||||
// find out in which line the cursor is
|
||||
int lineNumber = 0, lineStart = 0, lineEnd = 0;
|
||||
|
||||
for (size_t i = 0; i < this->wordParts.size(); i++) {
|
||||
const WordPart &part = this->wordParts[i];
|
||||
|
||||
if (part.getLineNumber() != lineNumber) {
|
||||
lineStart = i;
|
||||
lineNumber = part.getLineNumber();
|
||||
}
|
||||
|
||||
lineEnd = i + 1;
|
||||
}
|
||||
|
||||
// count up to the cursor
|
||||
int index = 0;
|
||||
|
||||
for (int i = 0; i < lineStart; i++) {
|
||||
const WordPart &part = this->wordParts[i];
|
||||
|
||||
index += part.getWord().isImage() ? 2 : part.getText().length() + 1;
|
||||
}
|
||||
|
||||
for (int i = lineStart; i < lineEnd; i++) {
|
||||
const WordPart &part = this->wordParts[i];
|
||||
|
||||
index += part.getCharacterLength();
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
int MessageRef::getSelectionIndex(QPoint position)
|
||||
{
|
||||
if (this->wordParts.size() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// find out in which line the cursor is
|
||||
int lineNumber = 0, lineStart = 0, lineEnd = 0;
|
||||
|
||||
for (size_t i = 0; i < this->wordParts.size(); i++) {
|
||||
WordPart &part = this->wordParts[i];
|
||||
|
||||
if (part.getLineNumber() != 0 && position.y() < part.getY()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (part.getLineNumber() != lineNumber) {
|
||||
lineStart = i;
|
||||
lineNumber = part.getLineNumber();
|
||||
}
|
||||
|
||||
lineEnd = i + 1;
|
||||
}
|
||||
|
||||
// count up to the cursor
|
||||
int index = 0;
|
||||
|
||||
for (int i = 0; i < lineStart; i++) {
|
||||
WordPart &part = this->wordParts[i];
|
||||
|
||||
index += part.getWord().isImage() ? 2 : part.getText().length() + 1;
|
||||
}
|
||||
|
||||
for (int i = lineStart; i < lineEnd; i++) {
|
||||
WordPart &part = this->wordParts[i];
|
||||
|
||||
// curser is left of the word part
|
||||
if (position.x() < part.getX()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// cursor is right of the word part
|
||||
if (position.x() > part.getX() + part.getWidth()) {
|
||||
index += part.getCharacterLength();
|
||||
continue;
|
||||
}
|
||||
|
||||
// cursor is over the word part
|
||||
if (part.getWord().isImage()) {
|
||||
if (position.x() - part.getX() > part.getWidth() / 2) {
|
||||
index++;
|
||||
}
|
||||
} else {
|
||||
// TODO: use word.getCharacterWidthCache();
|
||||
|
||||
auto text = part.getText();
|
||||
|
||||
int x = part.getX();
|
||||
|
||||
for (int j = 0; j < text.length(); j++) {
|
||||
if (x > position.x()) {
|
||||
break;
|
||||
}
|
||||
|
||||
index++;
|
||||
x = part.getX() + part.getWord().getFontMetrics(this->scale).width(text, j + 1);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
bool MessageRef::isCollapsed() const
|
||||
{
|
||||
return this->collapsed;
|
||||
}
|
||||
|
||||
void MessageRef::setCollapsed(bool value)
|
||||
{
|
||||
if (this->collapsed != value) {
|
||||
this->currentLayoutWidth = 0;
|
||||
this->collapsed = value;
|
||||
}
|
||||
}
|
||||
|
||||
int MessageRef::getCollapsedHeight() const
|
||||
{
|
||||
return this->collapsedHeight;
|
||||
}
|
||||
|
||||
bool MessageRef::isDisabled() const
|
||||
{
|
||||
return this->message->isDisabled();
|
||||
}
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
|
@ -1,70 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "messages/message.hpp"
|
||||
#include "messages/wordpart.hpp"
|
||||
|
||||
#include <QPixmap>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
class MessageRef;
|
||||
|
||||
typedef std::shared_ptr<MessageRef> SharedMessageRef;
|
||||
|
||||
class MessageRef
|
||||
{
|
||||
public:
|
||||
MessageRef(SharedMessage message);
|
||||
|
||||
Message *getMessage();
|
||||
int getHeight() const;
|
||||
|
||||
bool layout(int width, float scale);
|
||||
|
||||
const std::vector<WordPart> &getWordParts() const;
|
||||
|
||||
std::shared_ptr<QPixmap> buffer = nullptr;
|
||||
bool updateBuffer = false;
|
||||
|
||||
const Word *tryGetWordPart(QPoint point);
|
||||
int getLastCharacterIndex() const;
|
||||
int getSelectionIndex(QPoint position);
|
||||
|
||||
bool isCollapsed() const;
|
||||
void setCollapsed(bool value);
|
||||
int getCollapsedHeight() const;
|
||||
int getCollapsedLineCount() const;
|
||||
|
||||
bool isDisabled() const;
|
||||
|
||||
private:
|
||||
// variables
|
||||
SharedMessage message;
|
||||
std::vector<WordPart> wordParts;
|
||||
|
||||
int height = 0;
|
||||
|
||||
int currentLayoutWidth = -1;
|
||||
int fontGeneration = -1;
|
||||
int emoteGeneration = -1;
|
||||
float scale = -1;
|
||||
|
||||
Word::Flags currentWordTypes = Word::None;
|
||||
|
||||
bool collapsed;
|
||||
int collapsedHeight = 32;
|
||||
|
||||
// methods
|
||||
void rebuild();
|
||||
void actuallyLayout(int width);
|
||||
void _alignWordParts(int lineStart, int lineHeight, int width, int &firstLineHeight);
|
||||
int _updateLineHeight(int currentLineHeight, Word &word, bool overlapEmotes);
|
||||
void updateTextSizes();
|
||||
void updateImageSizes();
|
||||
};
|
||||
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
|
@ -1,207 +0,0 @@
|
|||
#include "messages/word.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
#include "util/benchmark.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
// Image word
|
||||
Word::Word(LazyLoadedImage *image, Flags type, const QString ©text, const QString &tooltip,
|
||||
const Link &link)
|
||||
: image(image)
|
||||
, _isImage(true)
|
||||
, type(type)
|
||||
, copyText(copytext)
|
||||
, tooltip(tooltip)
|
||||
, link(link)
|
||||
{
|
||||
}
|
||||
|
||||
// Text word
|
||||
Word::Word(const QString &text, Flags type, const MessageColor &color,
|
||||
singletons::FontManager::Type font, const QString ©text, const QString &tooltip,
|
||||
const Link &link)
|
||||
: image(nullptr)
|
||||
, text(text)
|
||||
, color(color)
|
||||
, _isImage(false)
|
||||
, type(type)
|
||||
, copyText(copytext)
|
||||
, tooltip(tooltip)
|
||||
, font(font)
|
||||
, link(link)
|
||||
{
|
||||
}
|
||||
|
||||
LazyLoadedImage &Word::getImage() const
|
||||
{
|
||||
return *this->image;
|
||||
}
|
||||
|
||||
const QString &Word::getText() const
|
||||
{
|
||||
return this->text;
|
||||
}
|
||||
|
||||
int Word::getWidth(float scale) const
|
||||
{
|
||||
return this->getSize(scale).width();
|
||||
}
|
||||
|
||||
int Word::getHeight(float scale) const
|
||||
{
|
||||
return this->getSize(scale).height();
|
||||
}
|
||||
|
||||
QSize Word::getSize(float scale) const
|
||||
{
|
||||
auto &data = this->getDataByScale(scale);
|
||||
|
||||
if (data.size.isEmpty()) {
|
||||
// no size found
|
||||
if (this->isText()) {
|
||||
QFontMetrics &metrics = this->getFontMetrics(scale);
|
||||
data.size.setWidth((int)(metrics.width(this->getText())));
|
||||
data.size.setHeight((int)(metrics.height()));
|
||||
} else {
|
||||
const int mediumTextLineHeight =
|
||||
singletons::FontManager::getInstance().getFontMetrics(this->font, scale).height();
|
||||
const qreal emoteScale =
|
||||
singletons::SettingManager::getInstance().emoteScale.getValue() * scale;
|
||||
const bool scaleEmotesByLineHeight =
|
||||
singletons::SettingManager::getInstance().scaleEmotesByLineHeight;
|
||||
|
||||
auto &image = this->getImage();
|
||||
|
||||
qreal w = image.getWidth();
|
||||
qreal h = image.getHeight();
|
||||
|
||||
if (scaleEmotesByLineHeight) {
|
||||
data.size.setWidth(w * mediumTextLineHeight / h * emoteScale);
|
||||
data.size.setHeight(mediumTextLineHeight * emoteScale);
|
||||
} else {
|
||||
data.size.setWidth(w * image.getScale() * emoteScale);
|
||||
data.size.setHeight(h * image.getScale() * emoteScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data.size;
|
||||
}
|
||||
|
||||
void Word::updateSize()
|
||||
{
|
||||
this->dataByScale.clear();
|
||||
}
|
||||
|
||||
bool Word::isImage() const
|
||||
{
|
||||
return this->_isImage;
|
||||
}
|
||||
|
||||
bool Word::isText() const
|
||||
{
|
||||
return !this->_isImage;
|
||||
}
|
||||
|
||||
const QString &Word::getCopyText() const
|
||||
{
|
||||
return this->copyText;
|
||||
}
|
||||
|
||||
bool Word::hasTrailingSpace() const
|
||||
{
|
||||
return this->_hasTrailingSpace;
|
||||
}
|
||||
|
||||
QFont &Word::getFont(float scale) const
|
||||
{
|
||||
return singletons::FontManager::getInstance().getFont(this->font, scale);
|
||||
}
|
||||
|
||||
QFontMetrics &Word::getFontMetrics(float scale) const
|
||||
{
|
||||
return singletons::FontManager::getInstance().getFontMetrics(this->font, scale);
|
||||
}
|
||||
|
||||
Word::Flags Word::getFlags() const
|
||||
{
|
||||
return this->type;
|
||||
}
|
||||
|
||||
const QString &Word::getTooltip() const
|
||||
{
|
||||
return this->tooltip;
|
||||
}
|
||||
|
||||
const MessageColor &Word::getTextColor() const
|
||||
{
|
||||
return this->color;
|
||||
}
|
||||
|
||||
const Link &Word::getLink() const
|
||||
{
|
||||
return this->link;
|
||||
}
|
||||
|
||||
int Word::getXOffset() const
|
||||
{
|
||||
return this->xOffset;
|
||||
}
|
||||
|
||||
int Word::getYOffset() const
|
||||
{
|
||||
return this->yOffset;
|
||||
}
|
||||
|
||||
void Word::setOffset(int xOffset, int yOffset)
|
||||
{
|
||||
this->xOffset = std::max(0, xOffset);
|
||||
this->yOffset = std::max(0, yOffset);
|
||||
}
|
||||
|
||||
int Word::getCharacterLength() const
|
||||
{
|
||||
return this->isImage() ? 2 : this->getText().length() + 1;
|
||||
}
|
||||
|
||||
short Word::getCharWidth(int index, float scale) const
|
||||
{
|
||||
return this->getCharacterWidthCache(scale).at(index);
|
||||
}
|
||||
|
||||
std::vector<short> &Word::getCharacterWidthCache(float scale) const
|
||||
{
|
||||
auto &data = this->getDataByScale(scale);
|
||||
|
||||
// lock not required because there is only one gui thread
|
||||
// std::lock_guard<std::mutex> lock(this->charWidthCacheMutex);
|
||||
|
||||
if (data.charWidthCache.size() == 0 && this->isText()) {
|
||||
for (int i = 0; i < this->getText().length(); i++) {
|
||||
data.charWidthCache.push_back(
|
||||
this->getFontMetrics(scale).charWidth(this->getText(), i));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: on font change
|
||||
return data.charWidthCache;
|
||||
}
|
||||
|
||||
Word::ScaleDependantData &Word::getDataByScale(float scale) const
|
||||
{
|
||||
// try to find and return data for scale
|
||||
for (auto it = this->dataByScale.begin(); it != this->dataByScale.end(); it++) {
|
||||
if (it->scale == scale) {
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
|
||||
// create new data element and return that
|
||||
this->dataByScale.emplace_back(scale);
|
||||
|
||||
return this->dataByScale.back();
|
||||
}
|
||||
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
|
@ -1,163 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "messages/lazyloadedimage.hpp"
|
||||
#include "messages/link.hpp"
|
||||
#include "messages/messagecolor.hpp"
|
||||
#include "singletons/fontmanager.hpp"
|
||||
//#include "wordflags.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
class Word
|
||||
{
|
||||
public:
|
||||
enum Flags : uint32_t {
|
||||
None = 0,
|
||||
Misc = (1 << 0),
|
||||
Text = (1 << 1),
|
||||
|
||||
TimestampNoSeconds = (1 << 2),
|
||||
TimestampWithSeconds = (1 << 3),
|
||||
|
||||
TwitchEmoteImage = (1 << 4),
|
||||
TwitchEmoteText = (1 << 5),
|
||||
BttvEmoteImage = (1 << 6),
|
||||
BttvEmoteText = (1 << 7),
|
||||
BttvGifEmoteImage = (1 << 8),
|
||||
BttvGifEmoteText = (1 << 9),
|
||||
FfzEmoteImage = (1 << 10),
|
||||
FfzEmoteText = (1 << 11),
|
||||
EmoteImages = TwitchEmoteImage | BttvEmoteImage | BttvGifEmoteImage | FfzEmoteImage,
|
||||
|
||||
BitsStatic = (1 << 12),
|
||||
BitsAnimated = (1 << 13),
|
||||
|
||||
// Slot 1: Twitch
|
||||
// - Staff badge
|
||||
// - Admin badge
|
||||
// - Global Moderator badge
|
||||
BadgeGlobalAuthority = (1 << 14),
|
||||
|
||||
// Slot 2: Twitch
|
||||
// - Moderator badge
|
||||
// - Broadcaster badge
|
||||
BadgeChannelAuthority = (1 << 15),
|
||||
|
||||
// Slot 3: Twitch
|
||||
// - Subscription badges
|
||||
BadgeSubscription = (1 << 16),
|
||||
|
||||
// Slot 4: Twitch
|
||||
// - Turbo badge
|
||||
// - Prime badge
|
||||
// - Bit badges
|
||||
// - Game badges
|
||||
BadgeVanity = (1 << 17),
|
||||
|
||||
// Slot 5: Chatterino
|
||||
// - Chatterino developer badge
|
||||
// - Chatterino donator badge
|
||||
// - Chatterino top donator badge
|
||||
BadgeChatterino = (1 << 18),
|
||||
|
||||
// Rest of slots: ffz custom badge? bttv custom badge? mywaifu (puke) custom badge?
|
||||
|
||||
Badges = BadgeGlobalAuthority | BadgeChannelAuthority | BadgeSubscription | BadgeVanity |
|
||||
BadgeChatterino,
|
||||
|
||||
Username = (1 << 19),
|
||||
BitsAmount = (1 << 20),
|
||||
|
||||
ButtonBan = (1 << 21),
|
||||
ButtonTimeout = (1 << 22),
|
||||
|
||||
EmojiImage = (1 << 23),
|
||||
EmojiText = (1 << 24),
|
||||
|
||||
AlwaysShow = (1 << 25),
|
||||
|
||||
// used in the ChannelView class to make the collapse buttons visible if needed
|
||||
Collapsed = (1 << 26),
|
||||
|
||||
Default = TimestampNoSeconds | Badges | Username | BitsStatic | FfzEmoteImage |
|
||||
BttvEmoteImage | BttvGifEmoteImage | TwitchEmoteImage | BitsAmount | Text |
|
||||
ButtonBan | ButtonTimeout | AlwaysShow
|
||||
};
|
||||
|
||||
Word()
|
||||
{
|
||||
}
|
||||
|
||||
explicit Word(LazyLoadedImage *_image, Flags getFlags, const QString ©text,
|
||||
const QString &tooltip, const Link &getLink = Link());
|
||||
explicit Word(const QString &_text, Flags getFlags, const MessageColor &textColor,
|
||||
singletons::FontManager::Type font, const QString ©text,
|
||||
const QString &tooltip, const Link &getLink = Link());
|
||||
|
||||
bool isImage() const;
|
||||
bool isText() const;
|
||||
|
||||
LazyLoadedImage &getImage() const;
|
||||
const QString &getText() const;
|
||||
const QString &getCopyText() const;
|
||||
bool hasTrailingSpace() const;
|
||||
Flags getFlags() const;
|
||||
const QString &getTooltip() const;
|
||||
const MessageColor &getTextColor() const;
|
||||
const Link &getLink() const;
|
||||
int getXOffset() const;
|
||||
int getYOffset() const;
|
||||
void setOffset(int _xOffset, int _yOffset);
|
||||
int getCharacterLength() const;
|
||||
|
||||
void updateSize();
|
||||
|
||||
QFont &getFont(float scale) const;
|
||||
QFontMetrics &getFontMetrics(float scale) const;
|
||||
int getWidth(float scale) const;
|
||||
int getHeight(float scale) const;
|
||||
QSize getSize(float scale) const;
|
||||
short getCharWidth(int index, float scale) const;
|
||||
|
||||
private:
|
||||
LazyLoadedImage *image;
|
||||
QString text;
|
||||
MessageColor color;
|
||||
bool _isImage;
|
||||
|
||||
Flags type;
|
||||
QString copyText;
|
||||
QString tooltip;
|
||||
|
||||
int xOffset = 0;
|
||||
int yOffset = 0;
|
||||
|
||||
bool _hasTrailingSpace = true;
|
||||
singletons::FontManager::Type font = singletons::FontManager::Medium;
|
||||
Link link;
|
||||
|
||||
struct ScaleDependantData {
|
||||
float scale;
|
||||
QSize size;
|
||||
mutable std::vector<short> charWidthCache;
|
||||
|
||||
ScaleDependantData(float _scale)
|
||||
: scale(_scale)
|
||||
, size()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
mutable std::list<ScaleDependantData> dataByScale;
|
||||
|
||||
inline ScaleDependantData &getDataByScale(float scale) const;
|
||||
std::vector<short> &getCharacterWidthCache(float scale) const;
|
||||
};
|
||||
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
|
@ -1,123 +0,0 @@
|
|||
#include "messages/wordpart.hpp"
|
||||
#include "messages/word.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
WordPart::WordPart(Word &_word, int _x, int _y, float scale, int _lineNumber,
|
||||
const QString &_copyText, bool _allowTrailingSpace)
|
||||
: word(_word)
|
||||
, copyText(_copyText)
|
||||
, text(_word.isText() ? _word.getText() : QString())
|
||||
, x(_x)
|
||||
, y(_y)
|
||||
, width(_word.getWidth(scale))
|
||||
, height(_word.getHeight(scale))
|
||||
, lineNumber(_lineNumber)
|
||||
, _trailingSpace(!_word.getCopyText().isEmpty() &&
|
||||
_word.hasTrailingSpace() & _allowTrailingSpace)
|
||||
, wordCharOffset(0)
|
||||
{
|
||||
}
|
||||
|
||||
WordPart::WordPart(Word &_word, int _x, int _y, int _width, int _height, int _lineNumber,
|
||||
const QString &_copyText, const QString &_customText, bool _allowTrailingSpace,
|
||||
int _wordCharOffset)
|
||||
: word(_word)
|
||||
, copyText(_copyText)
|
||||
, text(_customText)
|
||||
, x(_x)
|
||||
, y(_y)
|
||||
, width(_width)
|
||||
, height(_height)
|
||||
, lineNumber(_lineNumber)
|
||||
, _trailingSpace(!_word.getCopyText().isEmpty() &&
|
||||
_word.hasTrailingSpace() & _allowTrailingSpace)
|
||||
, wordCharOffset(_wordCharOffset)
|
||||
{
|
||||
}
|
||||
|
||||
const Word &WordPart::getWord() const
|
||||
{
|
||||
return this->word;
|
||||
}
|
||||
|
||||
int WordPart::getWidth() const
|
||||
{
|
||||
return this->width;
|
||||
}
|
||||
|
||||
int WordPart::getHeight() const
|
||||
{
|
||||
return this->height;
|
||||
}
|
||||
|
||||
int WordPart::getX() const
|
||||
{
|
||||
return this->x;
|
||||
}
|
||||
|
||||
int WordPart::getY() const
|
||||
{
|
||||
return this->y;
|
||||
}
|
||||
|
||||
void WordPart::setPosition(int x, int y)
|
||||
{
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
}
|
||||
|
||||
void WordPart::setY(int y)
|
||||
{
|
||||
this->y = y;
|
||||
}
|
||||
|
||||
int WordPart::getRight() const
|
||||
{
|
||||
return this->x + this->width;
|
||||
}
|
||||
|
||||
int WordPart::getBottom() const
|
||||
{
|
||||
return this->y + this->height;
|
||||
}
|
||||
|
||||
QRect WordPart::getRect() const
|
||||
{
|
||||
return QRect(this->x, this->y, this->width, this->height - 1);
|
||||
}
|
||||
|
||||
const QString WordPart::getCopyText() const
|
||||
{
|
||||
return this->copyText;
|
||||
}
|
||||
|
||||
int WordPart::hasTrailingSpace() const
|
||||
{
|
||||
return this->_trailingSpace;
|
||||
}
|
||||
|
||||
const QString &WordPart::getText() const
|
||||
{
|
||||
return this->text;
|
||||
}
|
||||
|
||||
int WordPart::getLineNumber() const
|
||||
{
|
||||
return this->lineNumber;
|
||||
}
|
||||
|
||||
int WordPart::getCharacterLength() const
|
||||
{
|
||||
// return (this->getWord().isImage() ? 1 : this->getText().length()) + (_trailingSpace ? 1 :
|
||||
// 0);
|
||||
return this->getWord().isImage() ? 2 : this->getText().length() + 1;
|
||||
}
|
||||
|
||||
short WordPart::getCharWidth(int index, float scale) const
|
||||
{
|
||||
return this->getWord().getCharWidth(index + this->wordCharOffset, scale);
|
||||
}
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
|
@ -1,56 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
class Word;
|
||||
|
||||
class WordPart
|
||||
{
|
||||
public:
|
||||
WordPart(Word &getWord, int getX, int getY, float scale, int _lineNumber,
|
||||
const QString &getCopyText, bool allowTrailingSpace = true);
|
||||
|
||||
WordPart(Word &getWord, int getX, int getY, int getWidth, int getHeight, int _lineNumber,
|
||||
const QString &getCopyText, const QString &customText, bool allowTrailingSpace = true,
|
||||
int wordCharOffset = 0);
|
||||
|
||||
const Word &getWord() const;
|
||||
int getWidth() const;
|
||||
int getHeight() const;
|
||||
int getX() const;
|
||||
int getY() const;
|
||||
void setPosition(int _x, int _y);
|
||||
void setY(int _y);
|
||||
int getRight() const;
|
||||
int getBottom() const;
|
||||
QRect getRect() const;
|
||||
const QString getCopyText() const;
|
||||
int hasTrailingSpace() const;
|
||||
const QString &getText() const;
|
||||
int getLineNumber() const;
|
||||
int getCharacterLength() const;
|
||||
short getCharWidth(int index, float scale) const;
|
||||
|
||||
private:
|
||||
Word &word;
|
||||
|
||||
QString copyText;
|
||||
QString text;
|
||||
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
int lineNumber;
|
||||
|
||||
bool _trailingSpace;
|
||||
int wordCharOffset;
|
||||
};
|
||||
|
||||
} // namespace messages
|
||||
} // namespace chatterino
|
|
@ -19,11 +19,11 @@ ChannelManager::ChannelManager()
|
|||
{
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<Channel>> ChannelManager::getItems()
|
||||
const std::vector<SharedChannel> ChannelManager::getItems()
|
||||
{
|
||||
QMutexLocker locker(&this->channelsMutex);
|
||||
|
||||
std::vector<std::shared_ptr<Channel>> items;
|
||||
std::vector<SharedChannel> items;
|
||||
|
||||
for (auto &item : this->twitchChannels.values()) {
|
||||
items.push_back(std::get<0>(item));
|
||||
|
@ -32,7 +32,7 @@ const std::vector<std::shared_ptr<Channel>> ChannelManager::getItems()
|
|||
return items;
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> ChannelManager::addTwitchChannel(const QString &rawChannelName)
|
||||
SharedChannel ChannelManager::addTwitchChannel(const QString &rawChannelName)
|
||||
{
|
||||
QString channelName = rawChannelName.toLower();
|
||||
|
||||
|
@ -63,7 +63,7 @@ std::shared_ptr<Channel> ChannelManager::addTwitchChannel(const QString &rawChan
|
|||
return std::get<0>(it.value());
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> ChannelManager::getTwitchChannel(const QString &channel)
|
||||
SharedChannel ChannelManager::getTwitchChannel(const QString &channel)
|
||||
{
|
||||
QMutexLocker locker(&this->channelsMutex);
|
||||
|
||||
|
@ -128,7 +128,7 @@ const std::string &ChannelManager::getUserID(const std::string &username)
|
|||
return temporary;
|
||||
}
|
||||
|
||||
void ChannelManager::doOnAll(std::function<void(std::shared_ptr<Channel>)> func)
|
||||
void ChannelManager::doOnAll(std::function<void(SharedChannel)> func)
|
||||
{
|
||||
for (const auto &channel : this->twitchChannels) {
|
||||
func(std::get<0>(channel));
|
||||
|
|
|
@ -17,20 +17,20 @@ class ChannelManager
|
|||
public:
|
||||
static ChannelManager &getInstance();
|
||||
|
||||
const std::vector<std::shared_ptr<Channel>> getItems();
|
||||
const std::vector<SharedChannel> getItems();
|
||||
|
||||
std::shared_ptr<Channel> addTwitchChannel(const QString &channel);
|
||||
std::shared_ptr<Channel> getTwitchChannel(const QString &channel);
|
||||
SharedChannel addTwitchChannel(const QString &channel);
|
||||
SharedChannel getTwitchChannel(const QString &channel);
|
||||
void removeTwitchChannel(const QString &channel);
|
||||
|
||||
const std::string &getUserID(const std::string &username);
|
||||
|
||||
void doOnAll(std::function<void(std::shared_ptr<Channel>)> func);
|
||||
void doOnAll(std::function<void(SharedChannel)> func);
|
||||
|
||||
// Special channels
|
||||
const std::shared_ptr<Channel> whispersChannel;
|
||||
const std::shared_ptr<Channel> mentionsChannel;
|
||||
const std::shared_ptr<Channel> emptyChannel;
|
||||
const SharedChannel whispersChannel;
|
||||
const SharedChannel mentionsChannel;
|
||||
const SharedChannel emptyChannel;
|
||||
|
||||
private:
|
||||
std::map<std::string, std::string> usernameToID;
|
||||
|
|
|
@ -88,7 +88,7 @@ QStringList CommandManager::getCommands()
|
|||
return this->commandsStringList;
|
||||
}
|
||||
|
||||
QString CommandManager::execCommand(const QString &text, std::shared_ptr<Channel> channel,
|
||||
QString CommandManager::execCommand(const QString &text, SharedChannel channel,
|
||||
bool dryRun)
|
||||
{
|
||||
QStringList words = text.split(' ', QString::SkipEmptyParts);
|
||||
|
@ -110,9 +110,8 @@ QString CommandManager::execCommand(const QString &text, std::shared_ptr<Channel
|
|||
if (commandName == "/uptime") {
|
||||
QString messageText =
|
||||
twitchChannel->isLive ? twitchChannel->streamUptime : "Channel is not live.";
|
||||
messages::SharedMessage message(
|
||||
messages::Message::createSystemMessage(messageText));
|
||||
channel->addMessage(message);
|
||||
|
||||
channel->addMessage(messages::Message::createSystemMessage(messageText));
|
||||
|
||||
return "";
|
||||
} else if (commandName == "/ignore" && words.size() >= 2) {
|
||||
|
@ -122,9 +121,7 @@ QString CommandManager::execCommand(const QString &text, std::shared_ptr<Channel
|
|||
messageText = "Ignored user \"" + words.at(1) + "\".";
|
||||
}
|
||||
|
||||
messages::SharedMessage message(
|
||||
messages::Message::createSystemMessage(messageText));
|
||||
channel->addMessage(message);
|
||||
channel->addMessage(messages::Message::createSystemMessage(messageText));
|
||||
return "";
|
||||
} else if (commandName == "/unignore") {
|
||||
QString messageText;
|
||||
|
@ -133,9 +130,7 @@ QString CommandManager::execCommand(const QString &text, std::shared_ptr<Channel
|
|||
messageText = "Ignored user \"" + words.at(1) + "\".";
|
||||
}
|
||||
|
||||
messages::SharedMessage message(
|
||||
messages::Message::createSystemMessage(messageText));
|
||||
channel->addMessage(message);
|
||||
channel->addMessage(messages::Message::createSystemMessage(messageText));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,14 +62,14 @@ static void FillInFFZEmoteData(const QJsonObject &urls, const QString &code,
|
|||
|
||||
assert(!url1x.isEmpty());
|
||||
|
||||
emoteData.image1x = new LazyLoadedImage(url1x, 1, code, code + "<br />Global FFZ Emote");
|
||||
emoteData.image1x = new Image(url1x, 1, code, code + "<br />Global FFZ Emote");
|
||||
|
||||
if (!url2x.isEmpty()) {
|
||||
emoteData.image2x = new LazyLoadedImage(url2x, 0.5, code, code + "<br />Global FFZ Emote");
|
||||
emoteData.image2x = new Image(url2x, 0.5, code, code + "<br />Global FFZ Emote");
|
||||
}
|
||||
|
||||
if (!url3x.isEmpty()) {
|
||||
emoteData.image3x = new LazyLoadedImage(url3x, 0.25, code, code + "<br />Global FFZ Emote");
|
||||
emoteData.image3x = new Image(url3x, 0.25, code, code + "<br />Global FFZ Emote");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,7 +143,7 @@ void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName,
|
|||
|
||||
auto emote = this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [this, &code, &link] {
|
||||
return util::EmoteData(
|
||||
new LazyLoadedImage(link, 1, code, code + "<br/>Channel BTTV Emote"));
|
||||
new Image(link, 1, code, code + "<br/>Channel BTTV Emote"));
|
||||
});
|
||||
|
||||
this->bttvChannelEmotes.insert(code, emote);
|
||||
|
@ -294,7 +294,7 @@ void EmoteManager::loadEmojis()
|
|||
code + ".png";
|
||||
|
||||
this->emojis.insert(code,
|
||||
util::EmoteData(new LazyLoadedImage(url, 0.35, ":" + shortCode + ":",
|
||||
util::EmoteData(new Image(url, 0.35, ":" + shortCode + ":",
|
||||
":" + shortCode + ":<br/>Emoji")));
|
||||
|
||||
// TODO(pajlada): The vectors in emojiFirstByte need to be sorted by
|
||||
|
@ -375,7 +375,7 @@ void EmoteManager::parseEmojis(std::vector<std::tuple<util::EmoteData, QString>>
|
|||
// Create or fetch cached emoji image
|
||||
auto emojiImage = this->emojis.getOrAdd(matchedEmoji.code, [this, &url] {
|
||||
return util::EmoteData(
|
||||
new LazyLoadedImage(url, 0.35, "?????????", "???????????????")); //
|
||||
new Image(url, 0.35, "?????????", "???????????????")); //
|
||||
});
|
||||
|
||||
// Push the emoji as a word to parsedWords
|
||||
|
@ -489,11 +489,11 @@ void EmoteManager::loadBTTVEmotes()
|
|||
QString code = emote.toObject().value("code").toString();
|
||||
|
||||
util::EmoteData emoteData;
|
||||
emoteData.image1x = new LazyLoadedImage(GetBTTVEmoteLink(urlTemplate, id, "1x"), 1,
|
||||
emoteData.image1x = new Image(GetBTTVEmoteLink(urlTemplate, id, "1x"), 1,
|
||||
code, code + "<br />Global BTTV Emote");
|
||||
emoteData.image2x = new LazyLoadedImage(GetBTTVEmoteLink(urlTemplate, id, "2x"), 0.5,
|
||||
emoteData.image2x = new Image(GetBTTVEmoteLink(urlTemplate, id, "2x"), 0.5,
|
||||
code, code + "<br />Global BTTV Emote");
|
||||
emoteData.image3x = new LazyLoadedImage(GetBTTVEmoteLink(urlTemplate, id, "3x"), 0.25,
|
||||
emoteData.image3x = new Image(GetBTTVEmoteLink(urlTemplate, id, "3x"), 0.25,
|
||||
code, code + "<br />Global BTTV Emote");
|
||||
|
||||
this->bttvGlobalEmotes.insert(code, emoteData);
|
||||
|
@ -547,11 +547,11 @@ util::EmoteData EmoteManager::getTwitchEmoteById(long id, const QString &emoteNa
|
|||
|
||||
return _twitchEmoteFromCache.getOrAdd(id, [this, &emoteName, &_emoteName, &id] {
|
||||
util::EmoteData newEmoteData;
|
||||
newEmoteData.image1x = new LazyLoadedImage(GetTwitchEmoteLink(id, "1.0"), 1, emoteName,
|
||||
newEmoteData.image1x = new Image(GetTwitchEmoteLink(id, "1.0"), 1, emoteName,
|
||||
_emoteName + "<br/>Twitch Emote 1x");
|
||||
newEmoteData.image2x = new LazyLoadedImage(GetTwitchEmoteLink(id, "2.0"), .5, emoteName,
|
||||
newEmoteData.image2x = new Image(GetTwitchEmoteLink(id, "2.0"), .5, emoteName,
|
||||
_emoteName + "<br/>Twitch Emote 2x");
|
||||
newEmoteData.image3x = new LazyLoadedImage(GetTwitchEmoteLink(id, "3.0"), .25, emoteName,
|
||||
newEmoteData.image3x = new Image(GetTwitchEmoteLink(id, "3.0"), .25, emoteName,
|
||||
_emoteName + "<br/>Twitch Emote 3x");
|
||||
|
||||
return newEmoteData;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#define GIF_FRAME_LENGTH 33
|
||||
|
||||
#include "emojis.hpp"
|
||||
#include "messages/lazyloadedimage.hpp"
|
||||
#include "messages/image.hpp"
|
||||
#include "signalvector.hpp"
|
||||
#include "twitch/emotevalue.hpp"
|
||||
#include "twitch/twitchuser.hpp"
|
||||
|
@ -63,7 +63,7 @@ public:
|
|||
boost::signals2::signal<void()> &getGifUpdateSignal();
|
||||
|
||||
// Bit badge/emotes?
|
||||
util::ConcurrentMap<QString, messages::LazyLoadedImage *> miscImageCache;
|
||||
util::ConcurrentMap<QString, messages::Image *> miscImageCache;
|
||||
|
||||
private:
|
||||
SettingManager &settingsManager;
|
||||
|
|
|
@ -132,6 +132,7 @@ private:
|
|||
|
||||
int generation = 0;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
}
|
||||
|
||||
typedef singletons::FontManager::Type FontStyle;
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -71,9 +71,7 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
|
|||
|
||||
// check if the chat has been cleared by a moderator
|
||||
if (message->parameters().length() == 1) {
|
||||
SharedMessage msg(Message::createSystemMessage("Chat has been cleared by a moderator."));
|
||||
|
||||
c->addMessage(msg);
|
||||
c->addMessage(Message::createSystemMessage("Chat has been cleared by a moderator."));
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -94,12 +92,13 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
|
|||
}
|
||||
|
||||
// add the notice that the user has been timed out
|
||||
LimitedQueueSnapshot<SharedMessage> snapshot = c->getMessageSnapshot();
|
||||
LimitedQueueSnapshot<MessagePtr> snapshot = c->getMessageSnapshot();
|
||||
bool addMessage = true;
|
||||
int snapshotLength = snapshot.getLength();
|
||||
|
||||
for (int i = std::max(0, (int)snapshot.getLength() - 20); i < snapshot.getLength(); i++) {
|
||||
if (snapshot[i]->getFlags() & Message::Timeout && snapshot[i]->timeoutUser == username) {
|
||||
SharedMessage replacement(
|
||||
for (int i = std::max(0, snapshotLength - 20); i < snapshotLength; i++) {
|
||||
if (snapshot[i]->hasFlags(Message::Timeout) && snapshot[i]->loginName == username) {
|
||||
MessagePtr replacement(
|
||||
Message::createTimeoutMessage(username, durationInSeconds, reason, true));
|
||||
c->replaceMessage(snapshot[i], replacement);
|
||||
addMessage = false;
|
||||
|
@ -108,16 +107,13 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
|
|||
}
|
||||
|
||||
if (addMessage) {
|
||||
SharedMessage msg(
|
||||
Message::createTimeoutMessage(username, durationInSeconds, reason, false));
|
||||
c->addMessage(msg);
|
||||
c->addMessage(Message::createTimeoutMessage(username, durationInSeconds, reason, false));
|
||||
}
|
||||
|
||||
// disable the messages from the user
|
||||
for (int i = 0; i < snapshot.getLength(); i++) {
|
||||
if (!(snapshot[i]->getFlags() & Message::Timeout) &&
|
||||
snapshot[i]->getTimeoutUser() == username) {
|
||||
snapshot[i]->setDisabled(true);
|
||||
for (int i = 0; i < snapshotLength; i++) {
|
||||
if (!snapshot[i]->hasFlags(Message::Timeout) && snapshot[i]->loginName == username) {
|
||||
snapshot[i]->setFlags(Message::Disabled);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,7 +151,7 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
|
|||
auto rawChannelName = message->target();
|
||||
|
||||
bool broadcast = rawChannelName.length() < 2;
|
||||
std::shared_ptr<Message> msg(Message::createSystemMessage(message->content()));
|
||||
MessagePtr msg = Message::createSystemMessage(message->content());
|
||||
|
||||
if (broadcast) {
|
||||
this->channelManager.doOnAll([msg](const auto &c) {
|
||||
|
|
|
@ -381,9 +381,9 @@ void IrcManager::removeIgnoredUser(QString const &username)
|
|||
|
||||
void IrcManager::onConnected()
|
||||
{
|
||||
std::shared_ptr<Message> msg(Message::createSystemMessage("connected to chat"));
|
||||
MessagePtr msg = Message::createSystemMessage("connected to chat");
|
||||
|
||||
this->channelManager.doOnAll([msg](std::shared_ptr<Channel> channel) {
|
||||
this->channelManager.doOnAll([msg](SharedChannel channel) {
|
||||
assert(channel);
|
||||
channel->addMessage(msg);
|
||||
});
|
||||
|
@ -391,9 +391,9 @@ void IrcManager::onConnected()
|
|||
|
||||
void IrcManager::onDisconnected()
|
||||
{
|
||||
std::shared_ptr<Message> msg(Message::createSystemMessage("disconnected from chat"));
|
||||
MessagePtr msg = Message::createSystemMessage("disconnected from chat");
|
||||
|
||||
this->channelManager.doOnAll([msg](std::shared_ptr<Channel> channel) {
|
||||
this->channelManager.doOnAll([msg](SharedChannel channel) {
|
||||
assert(channel);
|
||||
channel->addMessage(msg);
|
||||
});
|
||||
|
|
|
@ -10,9 +10,9 @@ namespace singletons {
|
|||
|
||||
namespace {
|
||||
|
||||
inline messages::LazyLoadedImage *lli(const char *pixmapPath, qreal scale = 1)
|
||||
inline messages::Image *lli(const char *pixmapPath, qreal scale = 1)
|
||||
{
|
||||
return new messages::LazyLoadedImage(new QPixmap(pixmapPath), scale);
|
||||
return new messages::Image(new QPixmap(pixmapPath), scale);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -49,9 +49,9 @@ ResourceManager &ResourceManager::getInstance()
|
|||
}
|
||||
|
||||
ResourceManager::BadgeVersion::BadgeVersion(QJsonObject &&root)
|
||||
: badgeImage1x(new messages::LazyLoadedImage(root.value("image_url_1x").toString()))
|
||||
, badgeImage2x(new messages::LazyLoadedImage(root.value("image_url_2x").toString()))
|
||||
, badgeImage4x(new messages::LazyLoadedImage(root.value("image_url_4x").toString()))
|
||||
: badgeImage1x(new messages::Image(root.value("image_url_1x").toString()))
|
||||
, badgeImage2x(new messages::Image(root.value("image_url_2x").toString()))
|
||||
, badgeImage4x(new messages::Image(root.value("image_url_4x").toString()))
|
||||
, description(root.value("description").toString().toStdString())
|
||||
, title(root.value("title").toString().toStdString())
|
||||
, clickAction(root.value("clickAction").toString().toStdString())
|
||||
|
@ -139,7 +139,7 @@ void ResourceManager::loadChatterinoBadges()
|
|||
const QString &badgeVariantImageURL = badgeVariant.value("image").toString();
|
||||
|
||||
auto badgeVariantPtr = std::make_shared<ChatterinoBadge>(
|
||||
badgeVariantTooltip, new messages::LazyLoadedImage(badgeVariantImageURL));
|
||||
badgeVariantTooltip, new messages::Image(badgeVariantImageURL));
|
||||
|
||||
QJsonArray badgeVariantUsers = badgeVariant.value("users").toArray();
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "messages/lazyloadedimage.hpp"
|
||||
#include "messages/image.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
@ -16,34 +16,34 @@ class ResourceManager
|
|||
public:
|
||||
static ResourceManager &getInstance();
|
||||
|
||||
messages::LazyLoadedImage *badgeStaff;
|
||||
messages::LazyLoadedImage *badgeAdmin;
|
||||
messages::LazyLoadedImage *badgeGlobalModerator;
|
||||
messages::LazyLoadedImage *badgeModerator;
|
||||
messages::LazyLoadedImage *badgeTurbo;
|
||||
messages::LazyLoadedImage *badgeBroadcaster;
|
||||
messages::LazyLoadedImage *badgePremium;
|
||||
messages::LazyLoadedImage *badgeVerified;
|
||||
messages::LazyLoadedImage *badgeSubscriber;
|
||||
messages::LazyLoadedImage *badgeCollapsed;
|
||||
messages::Image *badgeStaff;
|
||||
messages::Image *badgeAdmin;
|
||||
messages::Image *badgeGlobalModerator;
|
||||
messages::Image *badgeModerator;
|
||||
messages::Image *badgeTurbo;
|
||||
messages::Image *badgeBroadcaster;
|
||||
messages::Image *badgePremium;
|
||||
messages::Image *badgeVerified;
|
||||
messages::Image *badgeSubscriber;
|
||||
messages::Image *badgeCollapsed;
|
||||
|
||||
messages::LazyLoadedImage *cheerBadge100000;
|
||||
messages::LazyLoadedImage *cheerBadge10000;
|
||||
messages::LazyLoadedImage *cheerBadge5000;
|
||||
messages::LazyLoadedImage *cheerBadge1000;
|
||||
messages::LazyLoadedImage *cheerBadge100;
|
||||
messages::LazyLoadedImage *cheerBadge1;
|
||||
messages::Image *cheerBadge100000;
|
||||
messages::Image *cheerBadge10000;
|
||||
messages::Image *cheerBadge5000;
|
||||
messages::Image *cheerBadge1000;
|
||||
messages::Image *cheerBadge100;
|
||||
messages::Image *cheerBadge1;
|
||||
|
||||
std::map<std::string, messages::LazyLoadedImage *> cheerBadges;
|
||||
std::map<std::string, messages::Image *> cheerBadges;
|
||||
|
||||
struct BadgeVersion {
|
||||
BadgeVersion() = delete;
|
||||
|
||||
explicit BadgeVersion(QJsonObject &&root);
|
||||
|
||||
messages::LazyLoadedImage *badgeImage1x;
|
||||
messages::LazyLoadedImage *badgeImage2x;
|
||||
messages::LazyLoadedImage *badgeImage4x;
|
||||
messages::Image *badgeImage1x;
|
||||
messages::Image *badgeImage2x;
|
||||
messages::Image *badgeImage4x;
|
||||
std::string description;
|
||||
std::string title;
|
||||
std::string clickAction;
|
||||
|
@ -58,8 +58,8 @@ public:
|
|||
|
||||
bool dynamicBadgesLoaded = false;
|
||||
|
||||
messages::LazyLoadedImage *buttonBan;
|
||||
messages::LazyLoadedImage *buttonTimeout;
|
||||
messages::Image *buttonBan;
|
||||
messages::Image *buttonTimeout;
|
||||
|
||||
struct Channel {
|
||||
std::map<std::string, BadgeSet> badgeSets;
|
||||
|
@ -72,14 +72,14 @@ public:
|
|||
|
||||
// Chatterino badges
|
||||
struct ChatterinoBadge {
|
||||
ChatterinoBadge(const std::string &_tooltip, messages::LazyLoadedImage *_image)
|
||||
ChatterinoBadge(const std::string &_tooltip, messages::Image *_image)
|
||||
: tooltip(_tooltip)
|
||||
, image(_image)
|
||||
{
|
||||
}
|
||||
|
||||
std::string tooltip;
|
||||
messages::LazyLoadedImage *image;
|
||||
messages::Image *image;
|
||||
};
|
||||
|
||||
// username
|
||||
|
|
|
@ -18,7 +18,6 @@ SettingManager::SettingManager()
|
|||
: snapshot(nullptr)
|
||||
{
|
||||
this->wordMaskListener.addSetting(this->showTimestamps);
|
||||
this->wordMaskListener.addSetting(this->showTimestampSeconds);
|
||||
this->wordMaskListener.addSetting(this->showBadges);
|
||||
this->wordMaskListener.addSetting(this->enableBttvEmotes);
|
||||
this->wordMaskListener.addSetting(this->enableEmojis);
|
||||
|
@ -29,7 +28,7 @@ SettingManager::SettingManager()
|
|||
};
|
||||
}
|
||||
|
||||
Word::Flags SettingManager::getWordTypeMask()
|
||||
MessageElement::Flags SettingManager::getWordTypeMask()
|
||||
{
|
||||
return this->wordTypeMask;
|
||||
}
|
||||
|
@ -48,35 +47,31 @@ void SettingManager::init()
|
|||
|
||||
void SettingManager::updateWordTypeMask()
|
||||
{
|
||||
uint32_t newMaskUint = Word::Text;
|
||||
uint32_t newMaskUint = MessageElement::Text;
|
||||
|
||||
if (this->showTimestamps) {
|
||||
if (this->showTimestampSeconds) {
|
||||
newMaskUint |= Word::TimestampWithSeconds;
|
||||
} else {
|
||||
newMaskUint |= Word::TimestampNoSeconds;
|
||||
}
|
||||
newMaskUint |= MessageElement::Timestamp;
|
||||
}
|
||||
|
||||
newMaskUint |= enableTwitchEmotes ? Word::TwitchEmoteImage : Word::TwitchEmoteText;
|
||||
newMaskUint |= enableFfzEmotes ? Word::FfzEmoteImage : Word::FfzEmoteText;
|
||||
newMaskUint |= enableBttvEmotes ? Word::BttvEmoteImage : Word::BttvEmoteText;
|
||||
newMaskUint |=
|
||||
(enableBttvEmotes && enableGifAnimations) ? Word::BttvEmoteImage : Word::BttvEmoteText;
|
||||
newMaskUint |= enableEmojis ? Word::EmojiImage : Word::EmojiText;
|
||||
enableTwitchEmotes ? MessageElement::TwitchEmoteImage : MessageElement::TwitchEmoteText;
|
||||
newMaskUint |= enableFfzEmotes ? MessageElement::FfzEmoteImage : MessageElement::FfzEmoteText;
|
||||
newMaskUint |=
|
||||
enableBttvEmotes ? MessageElement::BttvEmoteImage : MessageElement::BttvEmoteText;
|
||||
newMaskUint |= enableEmojis ? MessageElement::EmojiImage : MessageElement::EmojiText;
|
||||
|
||||
newMaskUint |= Word::BitsAmount;
|
||||
newMaskUint |= enableGifAnimations ? Word::BitsAnimated : Word::BitsStatic;
|
||||
newMaskUint |= MessageElement::BitsAmount;
|
||||
newMaskUint |= enableGifAnimations ? MessageElement::BitsAnimated : MessageElement::BitsStatic;
|
||||
|
||||
if (this->showBadges) {
|
||||
newMaskUint |= Word::Badges;
|
||||
newMaskUint |= MessageElement::Badges;
|
||||
}
|
||||
|
||||
newMaskUint |= Word::Username;
|
||||
newMaskUint |= MessageElement::Username;
|
||||
|
||||
newMaskUint |= Word::AlwaysShow;
|
||||
newMaskUint |= MessageElement::AlwaysShow;
|
||||
|
||||
Word::Flags newMask = static_cast<Word::Flags>(newMaskUint);
|
||||
MessageElement::Flags newMask = static_cast<MessageElement::Flags>(newMaskUint);
|
||||
|
||||
if (newMask != this->wordTypeMask) {
|
||||
this->wordTypeMask = newMask;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "messages/highlightphrase.hpp"
|
||||
#include "messages/word.hpp"
|
||||
#include "messages/messageelement.hpp"
|
||||
#include "singletons/helper/chatterinosetting.hpp"
|
||||
|
||||
#include <pajlada/settings/setting.hpp>
|
||||
|
@ -23,14 +23,14 @@ class SettingManager : public QObject
|
|||
using QStringSetting = ChatterinoSetting<QString>;
|
||||
|
||||
public:
|
||||
messages::Word::Flags getWordTypeMask();
|
||||
messages::MessageElement::Flags getWordTypeMask();
|
||||
bool isIgnoredEmote(const QString &emote);
|
||||
|
||||
void init();
|
||||
|
||||
/// Appearance
|
||||
BoolSetting showTimestamps = {"/appearance/messages/showTimestamps", true};
|
||||
BoolSetting showTimestampSeconds = {"/appearance/messages/showTimestampSeconds", true};
|
||||
QStringSetting timestampFormat = {"/appearance/messages/timestampFormat", "h:mm"};
|
||||
BoolSetting showBadges = {"/appearance/messages/showBadges", true};
|
||||
BoolSetting showLastMessageIndicator = {"/appearance/messages/showLastMessageIndicator", false};
|
||||
BoolSetting hideEmptyInput = {"/appearance/hideEmptyInputBox", false};
|
||||
|
@ -110,7 +110,7 @@ private:
|
|||
|
||||
SettingManager();
|
||||
|
||||
messages::Word::Flags wordTypeMask = messages::Word::Default;
|
||||
messages::MessageElement::Flags wordTypeMask = messages::MessageElement::Default;
|
||||
|
||||
pajlada::Settings::SettingListener wordMaskListener;
|
||||
};
|
||||
|
|
|
@ -107,7 +107,7 @@ void TwitchChannel::refreshLiveStatus()
|
|||
std::weak_ptr<Channel> weak = this->shared_from_this();
|
||||
|
||||
util::twitch::get2(url, QThread::currentThread(), [weak](rapidjson::Document &d) {
|
||||
std::shared_ptr<Channel> shared = weak.lock();
|
||||
SharedChannel shared = weak.lock();
|
||||
|
||||
if (!shared) {
|
||||
return;
|
||||
|
@ -168,7 +168,7 @@ void TwitchChannel::fetchRecentMessages()
|
|||
std::weak_ptr<Channel> weak = this->shared_from_this();
|
||||
|
||||
util::twitch::get(genericURL.arg(roomID), QThread::currentThread(), [weak](QJsonObject obj) {
|
||||
std::shared_ptr<Channel> shared = weak.lock();
|
||||
SharedChannel shared = weak.lock();
|
||||
|
||||
if (!shared) {
|
||||
return;
|
||||
|
@ -179,7 +179,7 @@ void TwitchChannel::fetchRecentMessages()
|
|||
|
||||
auto msgArray = obj.value("messages").toArray();
|
||||
if (msgArray.size() > 0) {
|
||||
std::vector<messages::SharedMessage> messages;
|
||||
std::vector<messages::MessagePtr> messages;
|
||||
messages.resize(msgArray.size());
|
||||
|
||||
for (int i = 0; i < msgArray.size(); i++) {
|
||||
|
|
|
@ -29,15 +29,14 @@ TwitchMessageBuilder::TwitchMessageBuilder(Channel *_channel,
|
|||
{
|
||||
}
|
||||
|
||||
SharedMessage TwitchMessageBuilder::parse()
|
||||
MessagePtr TwitchMessageBuilder::parse()
|
||||
{
|
||||
singletons::SettingManager &settings = singletons::SettingManager::getInstance();
|
||||
singletons::EmoteManager &emoteManager = singletons::EmoteManager::getInstance();
|
||||
|
||||
auto preferredEmoteQuality = settings.preferredEmoteQuality.getValue();
|
||||
|
||||
this->originalMessage = this->ircMessage->content();
|
||||
|
||||
// PARSING
|
||||
this->parseUsername();
|
||||
|
||||
// this->message->setCollapsedDefault(true);
|
||||
|
@ -48,25 +47,27 @@ SharedMessage TwitchMessageBuilder::parse()
|
|||
// Whether or not will be rendered is decided/checked later
|
||||
|
||||
// Appends the correct timestamp if the message is a past message
|
||||
|
||||
bool isPastMsg = this->tags.contains("historical");
|
||||
if (isPastMsg) {
|
||||
// This may be architecture dependent(datatype)
|
||||
qint64 ts = this->tags.value("tmi-sent-ts").toLongLong();
|
||||
QDateTime time = QDateTime::fromMSecsSinceEpoch(ts);
|
||||
this->appendTimestamp(time);
|
||||
QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(ts);
|
||||
this->append<TimestampElement>(dateTime.time());
|
||||
} else {
|
||||
this->appendTimestamp();
|
||||
this->append<TimestampElement>();
|
||||
}
|
||||
|
||||
this->parseMessageID();
|
||||
|
||||
this->parseRoomID();
|
||||
|
||||
this->appendModerationButtons();
|
||||
// TIMESTAMP
|
||||
this->append<TwitchModerationElement>();
|
||||
|
||||
this->parseTwitchBadges();
|
||||
|
||||
this->parseChatterinoBadges();
|
||||
this->addChatterinoBadges();
|
||||
|
||||
if (this->args.includeChannelName) {
|
||||
this->parseChannelName();
|
||||
|
@ -123,9 +124,8 @@ SharedMessage TwitchMessageBuilder::parse()
|
|||
|
||||
// twitch emote
|
||||
if (currentTwitchEmote != twitchEmotes.end() && currentTwitchEmote->first == i) {
|
||||
auto emoteImage = currentTwitchEmote->second.getImageForSize(preferredEmoteQuality);
|
||||
this->appendWord(Word(emoteImage, Word::TwitchEmoteImage, emoteImage->getName(),
|
||||
emoteImage->getTooltip(), Link(Link::Url, emoteImage->getUrl())));
|
||||
auto emoteImage = currentTwitchEmote->second;
|
||||
this->append<EmoteElement>(emoteImage, MessageElement::TwitchEmote);
|
||||
|
||||
i += split.length() + 1;
|
||||
currentTwitchEmote = std::next(currentTwitchEmote);
|
||||
|
@ -177,31 +177,13 @@ SharedMessage TwitchMessageBuilder::parse()
|
|||
QString bitsLink =
|
||||
QString("http://static-cdn.jtvnw.net/bits/dark/static/" + color + "/1");
|
||||
|
||||
LazyLoadedImage *imageAnimated = emoteManager.miscImageCache.getOrAdd(
|
||||
bitsLinkAnimated, [this, &bitsLinkAnimated] {
|
||||
return new LazyLoadedImage(bitsLinkAnimated);
|
||||
});
|
||||
LazyLoadedImage *image = emoteManager.miscImageCache.getOrAdd(
|
||||
bitsLink, [this, &bitsLink] { return new LazyLoadedImage(bitsLink); });
|
||||
Image *imageAnimated = emoteManager.miscImageCache.getOrAdd(
|
||||
bitsLinkAnimated,
|
||||
[this, &bitsLinkAnimated] { return new Image(bitsLinkAnimated); });
|
||||
Image *image = emoteManager.miscImageCache.getOrAdd(
|
||||
bitsLink, [this, &bitsLink] { return new Image(bitsLink); });
|
||||
|
||||
this->appendWord(Word(imageAnimated, Word::BitsAnimated, QString("cheer"),
|
||||
QString("Twitch Cheer"),
|
||||
Link(Link::Url, QString("https://blog.twitch.tv/"
|
||||
"introducing-cheering-celebrate-"
|
||||
"together-da62af41fac6"))));
|
||||
this->appendWord(Word(
|
||||
image, Word::BitsStatic, QString("cheer"), QString("Twitch Cheer"),
|
||||
Link(Link::Url,
|
||||
QString("https://blog.twitch.tv/"
|
||||
"introducing-cheering-celebrate-together-da62af41fac6"))));
|
||||
|
||||
this->appendWord(Word(
|
||||
QString("x" + string.mid(5)), Word::BitsAmount, MessageColor(bitsColor),
|
||||
singletons::FontManager::Medium, QString(string.mid(5)),
|
||||
QString("Twitch Cheer"),
|
||||
Link(Link::Url,
|
||||
QString("https://blog.twitch.tv/"
|
||||
"introducing-cheering-celebrate-together-da62af41fac6"))));
|
||||
// append bits
|
||||
|
||||
continue;
|
||||
}
|
||||
|
@ -228,15 +210,10 @@ SharedMessage TwitchMessageBuilder::parse()
|
|||
textColor = MessageColor(MessageColor::Link);
|
||||
}
|
||||
|
||||
this->appendWord(Word(string, Word::Text, textColor,
|
||||
singletons::FontManager::Medium, string, QString(), link));
|
||||
this->append<TextElement>(string, EmoteElement::Text) //
|
||||
->setLink(link);
|
||||
} else { // is emoji
|
||||
auto emoteImage = emoteData.getImageForSize(preferredEmoteQuality);
|
||||
this->appendWord(Word(emoteImage, Word::EmojiImage, emoteImage->getName(),
|
||||
emoteImage->getTooltip()));
|
||||
Word(emoteImage->getName(), Word::EmojiText, textColor,
|
||||
singletons::FontManager::Medium, emoteImage->getName(),
|
||||
emoteImage->getTooltip());
|
||||
this->append<EmoteElement>(emoteData, EmoteElement::EmojiAll);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -283,9 +260,10 @@ void TwitchMessageBuilder::parseRoomID()
|
|||
void TwitchMessageBuilder::parseChannelName()
|
||||
{
|
||||
QString channelName("#" + this->channel->name);
|
||||
this->appendWord(Word(channelName, Word::Misc, MessageColor(MessageColor::System),
|
||||
singletons::FontManager::Medium, QString(channelName), QString(),
|
||||
Link(Link::Url, this->channel->name + "\n" + this->messageID)));
|
||||
Link link(Link::Url, this->channel->name + "\n" + this->messageID);
|
||||
|
||||
this->append<TextElement>(channelName, MessageElement::ChannelName, MessageColor::System) //
|
||||
->setLink(link);
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::parseUsername()
|
||||
|
@ -303,7 +281,6 @@ void TwitchMessageBuilder::parseUsername()
|
|||
}
|
||||
|
||||
this->message->loginName = this->userName;
|
||||
this->message->timeoutUser = this->userName;
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::appendUsername()
|
||||
|
@ -330,30 +307,30 @@ void TwitchMessageBuilder::appendUsername()
|
|||
bool hasLocalizedName = !localizedName.isEmpty();
|
||||
|
||||
// The full string that will be rendered in the chat widget
|
||||
QString usernameString;
|
||||
QString usernameText;
|
||||
|
||||
pajlada::Settings::Setting<int> usernameDisplayMode(
|
||||
"/appearance/messages/usernameDisplayMode", UsernameDisplayMode::UsernameAndLocalizedName);
|
||||
|
||||
switch (usernameDisplayMode.getValue()) {
|
||||
case UsernameDisplayMode::Username: {
|
||||
usernameString = username;
|
||||
usernameText = username;
|
||||
} break;
|
||||
|
||||
case UsernameDisplayMode::LocalizedName: {
|
||||
if (hasLocalizedName) {
|
||||
usernameString = localizedName;
|
||||
usernameText = localizedName;
|
||||
} else {
|
||||
usernameString = username;
|
||||
usernameText = username;
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
case UsernameDisplayMode::UsernameAndLocalizedName: {
|
||||
if (hasLocalizedName) {
|
||||
usernameString = username + "(" + localizedName + ")";
|
||||
usernameText = username + "(" + localizedName + ")";
|
||||
} else {
|
||||
usernameString = username;
|
||||
usernameText = username;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
@ -369,12 +346,12 @@ void TwitchMessageBuilder::appendUsername()
|
|||
}
|
||||
|
||||
if (!ircMessage->isAction()) {
|
||||
usernameString += ":";
|
||||
usernameText += ":";
|
||||
}
|
||||
|
||||
this->appendWord(Word(usernameString, Word::Username, MessageColor(this->usernameColor),
|
||||
singletons::FontManager::MediumBold, usernameString, QString(),
|
||||
Link(Link::UserInfo, this->userName)));
|
||||
this->append<TextElement>(usernameText, MessageElement::Text, this->usernameColor,
|
||||
FontStyle::MediumBold)
|
||||
->setLink({Link::UserInfo, this->userName});
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::parseHighlights()
|
||||
|
@ -465,19 +442,6 @@ void TwitchMessageBuilder::parseHighlights()
|
|||
}
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::appendModerationButtons()
|
||||
{
|
||||
// mod buttons
|
||||
static QString buttonBanTooltip("Ban user");
|
||||
static QString buttonTimeoutTooltip("Timeout user");
|
||||
|
||||
this->appendWord(Word(singletons::ResourceManager::getInstance().buttonBan, Word::ButtonBan,
|
||||
QString(), buttonBanTooltip, Link(Link::UserBan, ircMessage->account())));
|
||||
this->appendWord(Word(singletons::ResourceManager::getInstance().buttonTimeout,
|
||||
Word::ButtonTimeout, QString(), buttonTimeoutTooltip,
|
||||
Link(Link::UserTimeout, ircMessage->account())));
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::appendTwitchEmote(const Communi::IrcPrivateMessage *ircMessage,
|
||||
const QString &emote,
|
||||
std::vector<std::pair<long int, util::EmoteData>> &vec)
|
||||
|
@ -547,20 +511,18 @@ bool TwitchMessageBuilder::tryAppendEmote(QString &emoteString)
|
|||
|
||||
bool TwitchMessageBuilder::appendEmote(const util::EmoteData &emoteData)
|
||||
{
|
||||
auto emoteImage = emoteData.getImageForSize(
|
||||
singletons::SettingManager::getInstance().preferredEmoteQuality.getValue());
|
||||
|
||||
this->appendWord(Word(emoteImage, Word::BttvEmoteImage, emoteImage->getName(),
|
||||
emoteImage->getTooltip(), Link(Link::Url, emoteImage->getUrl())));
|
||||
this->append<EmoteElement>(emoteData, MessageElement::BttvEmote);
|
||||
|
||||
// Perhaps check for ignored emotes here?
|
||||
return true;
|
||||
}
|
||||
|
||||
// fourtf: this is ugly
|
||||
// maybe put the individual badges into a map instead of this mess
|
||||
void TwitchMessageBuilder::parseTwitchBadges()
|
||||
{
|
||||
const auto &channelResources =
|
||||
singletons::ResourceManager::getInstance().channels[this->roomID];
|
||||
singletons::ResourceManager &resourceManager = singletons::ResourceManager::getInstance();
|
||||
const auto &channelResources = resourceManager.channels[this->roomID];
|
||||
|
||||
auto iterator = this->tags.find("badges");
|
||||
|
||||
|
@ -586,14 +548,14 @@ void TwitchMessageBuilder::parseTwitchBadges()
|
|||
std::string versionKey = cheerAmountQS.toStdString();
|
||||
|
||||
try {
|
||||
auto &badgeSet = singletons::ResourceManager::getInstance().badgeSets.at("bits");
|
||||
auto &badgeSet = resourceManager.badgeSets.at("bits");
|
||||
|
||||
try {
|
||||
auto &badgeVersion = badgeSet.versions.at(versionKey);
|
||||
|
||||
appendWord(
|
||||
Word(badgeVersion.badgeImage1x, Word::BadgeVanity, QString(),
|
||||
QString("Twitch " + QString::fromStdString(badgeVersion.title))));
|
||||
this->append<ImageElement>(*badgeVersion.badgeImage1x,
|
||||
MessageElement::BadgeVanity)
|
||||
->setTooltip("Twitch " + QString::fromStdString(badgeVersion.title));
|
||||
} catch (const std::exception &e) {
|
||||
debug::Log("Exception caught: {} when trying to fetch badge version {} ",
|
||||
e.what(), versionKey);
|
||||
|
@ -602,36 +564,40 @@ void TwitchMessageBuilder::parseTwitchBadges()
|
|||
debug::Log("No badge set with key bits. Exception: {}", e.what());
|
||||
}
|
||||
} else if (badge == "staff/1") {
|
||||
appendWord(Word(singletons::ResourceManager::getInstance().badgeStaff,
|
||||
Word::BadgeGlobalAuthority, QString(), QString("Twitch Staff")));
|
||||
this->append<ImageElement>(*resourceManager.badgeStaff,
|
||||
MessageElement::BadgeGlobalAuthority)
|
||||
->setTooltip("Twitch Staff");
|
||||
} else if (badge == "admin/1") {
|
||||
appendWord(Word(singletons::ResourceManager::getInstance().badgeAdmin,
|
||||
Word::BadgeGlobalAuthority, QString(), QString("Twitch Admin")));
|
||||
this->append<ImageElement>(*resourceManager.badgeAdmin,
|
||||
MessageElement::BadgeGlobalAuthority)
|
||||
->setTooltip("Twitch Admin");
|
||||
} else if (badge == "global_mod/1") {
|
||||
appendWord(Word(singletons::ResourceManager::getInstance().badgeGlobalModerator,
|
||||
Word::BadgeGlobalAuthority, QString(), QString("Global Moderator")));
|
||||
this->append<ImageElement>(*resourceManager.badgeGlobalModerator,
|
||||
MessageElement::BadgeGlobalAuthority)
|
||||
->setTooltip("Twitch Global Moderator");
|
||||
} else if (badge == "moderator/1") {
|
||||
// TODO: Implement custom FFZ moderator badge
|
||||
appendWord(Word(singletons::ResourceManager::getInstance().badgeModerator,
|
||||
Word::BadgeChannelAuthority, QString(),
|
||||
QString("Channel Moderator"))); // custom badge
|
||||
this->append<ImageElement>(*resourceManager.badgeModerator,
|
||||
MessageElement::BadgeChannelAuthority)
|
||||
->setTooltip("Twitch Channel Moderator");
|
||||
} else if (badge == "turbo/1") {
|
||||
appendWord(Word(singletons::ResourceManager::getInstance().badgeTurbo,
|
||||
Word::BadgeVanity, QString(), QString("Turbo Subscriber")));
|
||||
this->append<ImageElement>(*resourceManager.badgeTurbo,
|
||||
MessageElement::BadgeGlobalAuthority)
|
||||
->setTooltip("Twitch Turbo Subscriber");
|
||||
} else if (badge == "broadcaster/1") {
|
||||
appendWord(Word(singletons::ResourceManager::getInstance().badgeBroadcaster,
|
||||
Word::BadgeChannelAuthority, QString(),
|
||||
QString("Channel Broadcaster")));
|
||||
this->append<ImageElement>(*resourceManager.badgeBroadcaster,
|
||||
MessageElement::BadgeChannelAuthority)
|
||||
->setTooltip("Twitch Broadcaster");
|
||||
} else if (badge == "premium/1") {
|
||||
appendWord(Word(singletons::ResourceManager::getInstance().badgePremium,
|
||||
Word::BadgeVanity, QString(), QString("Twitch Prime")));
|
||||
|
||||
this->append<ImageElement>(*resourceManager.badgePremium, MessageElement::BadgeVanity)
|
||||
->setTooltip("Twitch Prime Subscriber");
|
||||
} else if (badge.startsWith("partner/")) {
|
||||
int index = badge.midRef(8).toInt();
|
||||
switch (index) {
|
||||
case 1: {
|
||||
appendWord(Word(singletons::ResourceManager::getInstance().badgeVerified,
|
||||
Word::BadgeVanity, QString(), "Twitch Verified"));
|
||||
this->append<ImageElement>(*resourceManager.badgeVerified,
|
||||
MessageElement::BadgeVanity)
|
||||
->setTooltip("Twitch Verified");
|
||||
} break;
|
||||
default: {
|
||||
printf("[TwitchMessageBuilder] Unhandled partner badge index: %d\n", index);
|
||||
|
@ -646,9 +612,9 @@ void TwitchMessageBuilder::parseTwitchBadges()
|
|||
auto badgeSetIt = channelResources.badgeSets.find("subscriber");
|
||||
if (badgeSetIt == channelResources.badgeSets.end()) {
|
||||
// Fall back to default badge
|
||||
appendWord(Word(singletons::ResourceManager::getInstance().badgeSubscriber,
|
||||
Word::Flags::BadgeSubscription, QString(),
|
||||
QString("Twitch Subscriber")));
|
||||
this->append<ImageElement>(*resourceManager.badgeSubscriber,
|
||||
MessageElement::BadgeSubscription)
|
||||
->setTooltip("Twitch Subscriber");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -660,18 +626,19 @@ void TwitchMessageBuilder::parseTwitchBadges()
|
|||
|
||||
if (badgeVersionIt == badgeSet.versions.end()) {
|
||||
// Fall back to default badge
|
||||
appendWord(Word(singletons::ResourceManager::getInstance().badgeSubscriber,
|
||||
Word::Flags::BadgeSubscription, QString(),
|
||||
QString("Twitch Subscriber")));
|
||||
this->append<ImageElement>(*resourceManager.badgeSubscriber,
|
||||
MessageElement::BadgeSubscription)
|
||||
->setTooltip("Twitch Subscriber");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &badgeVersion = badgeVersionIt->second;
|
||||
|
||||
appendWord(Word(badgeVersion.badgeImage1x, Word::Flags::BadgeSubscription, QString(),
|
||||
QString("Twitch " + QString::fromStdString(badgeVersion.title))));
|
||||
this->append<ImageElement>(*badgeVersion.badgeImage1x,
|
||||
MessageElement::BadgeSubscription)
|
||||
->setTooltip("Twitch " + QString::fromStdString(badgeVersion.title));
|
||||
} else {
|
||||
if (!singletons::ResourceManager::getInstance().dynamicBadgesLoaded) {
|
||||
if (!resourceManager.dynamicBadgesLoaded) {
|
||||
// Do nothing
|
||||
continue;
|
||||
}
|
||||
|
@ -683,21 +650,19 @@ void TwitchMessageBuilder::parseTwitchBadges()
|
|||
continue;
|
||||
}
|
||||
|
||||
Word::Flags badgeType = Word::Flags::BadgeVanity;
|
||||
MessageElement::Flags badgeType = MessageElement::Flags::BadgeVanity;
|
||||
|
||||
std::string badgeSetKey = parts[0].toStdString();
|
||||
std::string versionKey = parts[1].toStdString();
|
||||
|
||||
try {
|
||||
auto &badgeSet =
|
||||
singletons::ResourceManager::getInstance().badgeSets.at(badgeSetKey);
|
||||
auto &badgeSet = resourceManager.badgeSets.at(badgeSetKey);
|
||||
|
||||
try {
|
||||
auto &badgeVersion = badgeSet.versions.at(versionKey);
|
||||
|
||||
appendWord(
|
||||
Word(badgeVersion.badgeImage1x, badgeType, QString(),
|
||||
QString("Twitch " + QString::fromStdString(badgeVersion.title))));
|
||||
this->append<ImageElement>(*badgeVersion.badgeImage1x, badgeType)
|
||||
->setTooltip("Twitch " + QString::fromStdString(badgeVersion.title));
|
||||
} catch (const std::exception &e) {
|
||||
qDebug() << "Exception caught:" << e.what()
|
||||
<< "when trying to fetch badge version " << versionKey.c_str();
|
||||
|
@ -710,7 +675,7 @@ void TwitchMessageBuilder::parseTwitchBadges()
|
|||
}
|
||||
}
|
||||
|
||||
void TwitchMessageBuilder::parseChatterinoBadges()
|
||||
void TwitchMessageBuilder::addChatterinoBadges()
|
||||
{
|
||||
auto &badges = singletons::ResourceManager::getInstance().chatterinoBadges;
|
||||
auto it = badges.find(this->userName.toStdString());
|
||||
|
@ -721,12 +686,13 @@ void TwitchMessageBuilder::parseChatterinoBadges()
|
|||
|
||||
const auto badge = it->second;
|
||||
|
||||
this->appendWord(Word(badge->image, Word::BadgeChatterino, QString(), badge->tooltip.c_str()));
|
||||
this->append<ImageElement>(*badge->image, MessageElement::BadgeChatterino)
|
||||
->setTooltip(QString::fromStdString(badge->tooltip));
|
||||
}
|
||||
|
||||
// bool
|
||||
// sortTwitchEmotes(const std::pair<long int, LazyLoadedImage *> &a,
|
||||
// const std::pair<long int, LazyLoadedImage *> &b)
|
||||
// sortTwitchEmotes(const std::pair<long int, Image *> &a,
|
||||
// const std::pair<long int, Image *> &b)
|
||||
//{
|
||||
// return a.first < b.first;
|
||||
//}
|
||||
|
|
|
@ -38,11 +38,11 @@ public:
|
|||
QString messageID;
|
||||
QString userName;
|
||||
|
||||
messages::SharedMessage parse();
|
||||
messages::MessagePtr parse();
|
||||
|
||||
// static bool sortTwitchEmotes(
|
||||
// const std::pair<long int, messages::LazyLoadedImage *> &a,
|
||||
// const std::pair<long int, messages::LazyLoadedImage *> &b);
|
||||
// const std::pair<long int, messages::Image *> &a,
|
||||
// const std::pair<long int, messages::Image *> &b);
|
||||
|
||||
private:
|
||||
QString roomID;
|
||||
|
@ -56,14 +56,13 @@ private:
|
|||
void appendUsername();
|
||||
void parseHighlights();
|
||||
|
||||
void appendModerationButtons();
|
||||
void appendTwitchEmote(const Communi::IrcPrivateMessage *ircMessage, const QString &emote,
|
||||
std::vector<std::pair<long, util::EmoteData>> &vec);
|
||||
bool tryAppendEmote(QString &emoteString);
|
||||
bool appendEmote(const util::EmoteData &emoteData);
|
||||
|
||||
void parseTwitchBadges();
|
||||
void parseChatterinoBadges();
|
||||
void addChatterinoBadges();
|
||||
};
|
||||
|
||||
} // namespace twitch
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "messages/lazyloadedimage.hpp"
|
||||
#include "messages/image.hpp"
|
||||
#include "util/concurrentmap.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
@ -13,14 +13,14 @@ struct EmoteData {
|
|||
{
|
||||
}
|
||||
|
||||
EmoteData(messages::LazyLoadedImage *_image)
|
||||
EmoteData(messages::Image *_image)
|
||||
: image1x(_image)
|
||||
{
|
||||
}
|
||||
|
||||
messages::LazyLoadedImage *getImageForSize(unsigned emoteSize) const
|
||||
messages::Image *getImageForSize(unsigned emoteSize) const
|
||||
{
|
||||
messages::LazyLoadedImage *ret = nullptr;
|
||||
messages::Image *ret = nullptr;
|
||||
|
||||
switch (emoteSize) {
|
||||
case 0:
|
||||
|
@ -53,9 +53,9 @@ struct EmoteData {
|
|||
return this->image1x != nullptr;
|
||||
}
|
||||
|
||||
messages::LazyLoadedImage *image1x = nullptr;
|
||||
messages::LazyLoadedImage *image2x = nullptr;
|
||||
messages::LazyLoadedImage *image3x = nullptr;
|
||||
messages::Image *image1x = nullptr;
|
||||
messages::Image *image2x = nullptr;
|
||||
messages::Image *image3x = nullptr;
|
||||
};
|
||||
|
||||
typedef ConcurrentMap<QString, EmoteData> EmoteMap;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
namespace util {
|
||||
QString ParseTagString(const QString &input)
|
||||
{
|
||||
QString output = input;
|
||||
|
@ -53,5 +53,5 @@ QString ParseTagString(const QString &input)
|
|||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace chatterino
|
||||
|
|
34
src/util/property.hpp
Normal file
34
src/util/property.hpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include "boost/noncopyable.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace util {
|
||||
template <typename T>
|
||||
class Property final : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
Property()
|
||||
{
|
||||
}
|
||||
|
||||
Property(const T &_value)
|
||||
: value(_value)
|
||||
{
|
||||
}
|
||||
|
||||
T &operator=(const T &f)
|
||||
{
|
||||
return value = f;
|
||||
}
|
||||
|
||||
operator T const &() const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
protected:
|
||||
T value;
|
||||
};
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
namespace chatterino {
|
||||
namespace widgets {
|
||||
|
||||
AccountPopupWidget::AccountPopupWidget(std::shared_ptr<Channel> _channel)
|
||||
AccountPopupWidget::AccountPopupWidget(SharedChannel _channel)
|
||||
: BaseWidget()
|
||||
, ui(new Ui::AccountPopup)
|
||||
, channel(_channel)
|
||||
|
@ -132,7 +132,7 @@ void AccountPopupWidget::setName(const QString &name)
|
|||
this->getUserId();
|
||||
}
|
||||
|
||||
void AccountPopupWidget::setChannel(std::shared_ptr<Channel> _channel)
|
||||
void AccountPopupWidget::setChannel(SharedChannel _channel)
|
||||
{
|
||||
this->channel = _channel;
|
||||
}
|
||||
|
|
|
@ -23,10 +23,10 @@ class AccountPopupWidget : public BaseWidget
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AccountPopupWidget(std::shared_ptr<Channel> _channel);
|
||||
AccountPopupWidget(SharedChannel _channel);
|
||||
|
||||
void setName(const QString &name);
|
||||
void setChannel(std::shared_ptr<Channel> _channel);
|
||||
void setChannel(SharedChannel _channel);
|
||||
|
||||
void updatePermissions();
|
||||
|
||||
|
@ -47,7 +47,7 @@ private:
|
|||
enum class permissions { User, Mod, Owner };
|
||||
permissions permission;
|
||||
|
||||
std::shared_ptr<Channel> channel;
|
||||
SharedChannel channel;
|
||||
|
||||
QString userID;
|
||||
QPixmap avatar;
|
||||
|
|
|
@ -34,7 +34,7 @@ EmotePopup::EmotePopup(singletons::ThemeManager &themeManager)
|
|||
this->loadEmojis();
|
||||
}
|
||||
|
||||
void EmotePopup::loadChannel(std::shared_ptr<Channel> _channel)
|
||||
void EmotePopup::loadChannel(SharedChannel _channel)
|
||||
{
|
||||
TwitchChannel *channel = dynamic_cast<TwitchChannel *>(_channel.get());
|
||||
|
||||
|
@ -42,29 +42,25 @@ void EmotePopup::loadChannel(std::shared_ptr<Channel> _channel)
|
|||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> emoteChannel(new Channel(""));
|
||||
SharedChannel emoteChannel(new Channel(""));
|
||||
|
||||
auto addEmotes = [&](util::EmoteMap &map, const QString &title, const QString &emoteDesc) {
|
||||
// TITLE
|
||||
messages::MessageBuilder builder1;
|
||||
|
||||
builder1.appendWord(Word(title, Word::Flags::Text, MessageColor(MessageColor::Text),
|
||||
singletons::FontManager::Medium, QString(), QString()));
|
||||
builder1.appendElement(new TextElement(title, MessageElement::Text));
|
||||
|
||||
builder1.getMessage()->centered = true;
|
||||
builder1.getMessage()->addFlags(Message::Centered);
|
||||
emoteChannel->addMessage(builder1.getMessage());
|
||||
|
||||
// EMOTES
|
||||
messages::MessageBuilder builder2;
|
||||
builder2.getMessage()->centered = true;
|
||||
builder2.getMessage()->setDisableCompactEmotes(true);
|
||||
|
||||
int preferredEmoteSize = singletons::SettingManager::getInstance().preferredEmoteQuality;
|
||||
builder2.getMessage()->addFlags(Message::Centered);
|
||||
builder2.getMessage()->addFlags(Message::DisableCompactEmotes);
|
||||
|
||||
map.each([&](const QString &key, const util::EmoteData &value) {
|
||||
builder2.appendWord(Word(value.getImageForSize(preferredEmoteSize),
|
||||
Word::Flags::AlwaysShow, key, emoteDesc,
|
||||
Link(Link::Type::InsertText, key)));
|
||||
builder2.appendElement((new EmoteElement(value, MessageElement::Flags::AlwaysShow)) //
|
||||
->setLink(Link(Link::InsertText, key)));
|
||||
});
|
||||
|
||||
emoteChannel->addMessage(builder2.getMessage());
|
||||
|
@ -85,30 +81,25 @@ void EmotePopup::loadChannel(std::shared_ptr<Channel> _channel)
|
|||
|
||||
void EmotePopup::loadEmojis()
|
||||
{
|
||||
int preferredEmoteSize = singletons::SettingManager::getInstance().preferredEmoteQuality;
|
||||
util::EmoteMap &emojis = singletons::EmoteManager::getInstance().getEmojis();
|
||||
|
||||
std::shared_ptr<Channel> emojiChannel(new Channel(""));
|
||||
SharedChannel emojiChannel(new Channel(""));
|
||||
|
||||
// title
|
||||
messages::MessageBuilder builder1;
|
||||
|
||||
builder1.appendWord(Word("emojis", Word::Flags::Text, MessageColor(MessageColor::Text),
|
||||
singletons::FontManager::Medium, QString(), QString()));
|
||||
|
||||
builder1.getMessage()->centered = true;
|
||||
builder1.appendElement(new TextElement("emojis", MessageElement::Text));
|
||||
builder1.getMessage()->addFlags(Message::Centered);
|
||||
emojiChannel->addMessage(builder1.getMessage());
|
||||
|
||||
// emojis
|
||||
messages::MessageBuilder builder;
|
||||
builder.getMessage()->centered = true;
|
||||
builder.getMessage()->setDisableCompactEmotes(true);
|
||||
builder.getMessage()->addFlags(Message::Centered);
|
||||
builder.getMessage()->setFlags(Message::DisableCompactEmotes);
|
||||
|
||||
emojis.each(
|
||||
[this, &builder, preferredEmoteSize](const QString &key, const util::EmoteData &value) {
|
||||
auto emoteImage = value.getImageForSize(preferredEmoteSize);
|
||||
builder.appendWord(Word(emoteImage, Word::Flags::AlwaysShow, key, "emoji",
|
||||
Link(Link::Type::InsertText, key)));
|
||||
emojis.each([this, &builder](const QString &key, const util::EmoteData &value) {
|
||||
builder.appendElement((new EmoteElement(value, MessageElement::Flags::AlwaysShow)) //
|
||||
->setLink(Link(Link::Type::InsertText, key)));
|
||||
});
|
||||
emojiChannel->addMessage(builder.getMessage());
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ class EmotePopup : public BaseWidget
|
|||
public:
|
||||
explicit EmotePopup(singletons::ThemeManager &);
|
||||
|
||||
void loadChannel(std::shared_ptr<Channel> channel);
|
||||
void loadChannel(SharedChannel channel);
|
||||
void loadEmojis();
|
||||
|
||||
private:
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#include "channelview.hpp"
|
||||
#include "debug/log.hpp"
|
||||
#include "messages/layouts/messagelayout.hpp"
|
||||
#include "messages/limitedqueuesnapshot.hpp"
|
||||
#include "messages/message.hpp"
|
||||
#include "messages/messageref.hpp"
|
||||
#include "singletons/channelmanager.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
#include "singletons/thememanager.hpp"
|
||||
|
@ -57,8 +57,7 @@ ChannelView::ChannelView(BaseWidget *parent)
|
|||
|
||||
singletons::WindowManager &windowManager = singletons::WindowManager::getInstance();
|
||||
|
||||
this->repaintGifsConnection =
|
||||
windowManager.repaintGifs.connect([&] { this->updateGifEmotes(); });
|
||||
this->repaintGifsConnection = windowManager.repaintGifs.connect([&] { this->queueUpdate(); });
|
||||
this->layoutConnection = windowManager.layout.connect([&](Channel *channel) {
|
||||
if (channel == nullptr || this->channel.get() == channel) {
|
||||
this->layoutMessages();
|
||||
|
@ -225,14 +224,6 @@ void ChannelView::clearMessages()
|
|||
this->queueUpdate();
|
||||
}
|
||||
|
||||
void ChannelView::updateGifEmotes()
|
||||
{
|
||||
if (!this->gifEmotes.empty()) {
|
||||
this->onlyUpdateEmotes = true;
|
||||
this->queueUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
Scrollbar &ChannelView::getScrollBar()
|
||||
{
|
||||
return this->scrollBar;
|
||||
|
@ -240,103 +231,106 @@ Scrollbar &ChannelView::getScrollBar()
|
|||
|
||||
QString ChannelView::getSelectedText()
|
||||
{
|
||||
auto messagesSnapshot = this->getMessagesSnapshot();
|
||||
// fourtf: xD
|
||||
// auto messagesSnapshot = this->getMessagesSnapshot();
|
||||
|
||||
QString text;
|
||||
bool isSingleMessage = this->selection.isSingleMessage();
|
||||
// QString text;
|
||||
// bool isSingleMessage = this->selection.isSingleMessage();
|
||||
|
||||
size_t i = std::max(0, this->selection.min.messageIndex);
|
||||
// size_t i = std::max(0, this->selection.min.messageIndex);
|
||||
|
||||
int charIndex = 0;
|
||||
// int charIndex = 0;
|
||||
|
||||
bool first = true;
|
||||
// bool first = true;
|
||||
|
||||
auto addPart = [&](const WordPart &part, int from = 0, int to = -1) {
|
||||
if (part.getCopyText().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// auto addPart = [&](const MessageLayoutElement &part, int from = 0, int to = -1) {
|
||||
// if (part.getCopyText().isEmpty()) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
if (part.getWord().isText()) {
|
||||
text += part.getText().mid(from, to);
|
||||
} else {
|
||||
text += part.getCopyText();
|
||||
}
|
||||
};
|
||||
// if (part.getWord().isText()) {
|
||||
// text += part.getText().mid(from, to);
|
||||
// } else {
|
||||
// text += part.getCopyText();
|
||||
// }
|
||||
// };
|
||||
|
||||
// first line
|
||||
for (const messages::WordPart &part : messagesSnapshot[i]->getWordParts()) {
|
||||
int charLength = part.getCharacterLength();
|
||||
// // first line
|
||||
// for (const messages::MessageLayoutElement &part : messagesSnapshot[i]->getWordParts()) {
|
||||
// int charLength = part.getCharacterLength();
|
||||
|
||||
if (charIndex + charLength < this->selection.min.charIndex) {
|
||||
charIndex += charLength;
|
||||
continue;
|
||||
}
|
||||
// if (charIndex + charLength < this->selection.min.charIndex) {
|
||||
// charIndex += charLength;
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
bool isSingleWord =
|
||||
isSingleMessage &&
|
||||
this->selection.max.charIndex - charIndex < part.getCharacterLength();
|
||||
// if (first) {
|
||||
// first = false;
|
||||
// bool isSingleWord =
|
||||
// isSingleMessage &&
|
||||
// this->selection.max.charIndex - charIndex < part.getCharacterLength();
|
||||
|
||||
if (isSingleWord) {
|
||||
// return single word
|
||||
addPart(part, this->selection.min.charIndex - charIndex,
|
||||
this->selection.max.charIndex - this->selection.min.charIndex);
|
||||
return text;
|
||||
} else {
|
||||
// add first word of the selection
|
||||
addPart(part, this->selection.min.charIndex - charIndex);
|
||||
}
|
||||
} else if (isSingleMessage && charIndex + charLength >= selection.max.charIndex) {
|
||||
addPart(part, 0, this->selection.max.charIndex - charIndex);
|
||||
// if (isSingleWord) {
|
||||
// // return single word
|
||||
// addPart(part, this->selection.min.charIndex - charIndex,
|
||||
// this->selection.max.charIndex - this->selection.min.charIndex);
|
||||
// return text;
|
||||
// } else {
|
||||
// // add first word of the selection
|
||||
// addPart(part, this->selection.min.charIndex - charIndex);
|
||||
// }
|
||||
// } else if (isSingleMessage && charIndex + charLength >= selection.max.charIndex) {
|
||||
// addPart(part, 0, this->selection.max.charIndex - charIndex);
|
||||
|
||||
return text;
|
||||
} else {
|
||||
text += part.getCopyText() + (part.hasTrailingSpace() ? " " : "");
|
||||
}
|
||||
// return text;
|
||||
// } else {
|
||||
// text += part.getCopyText() + (part.hasTrailingSpace() ? " " : "");
|
||||
// }
|
||||
|
||||
charIndex += charLength;
|
||||
}
|
||||
// charIndex += charLength;
|
||||
// }
|
||||
|
||||
text += "\n";
|
||||
// text += "\n";
|
||||
|
||||
// middle lines
|
||||
for (i++; (int)i < this->selection.max.messageIndex; i++) {
|
||||
for (const messages::WordPart &part : messagesSnapshot[i]->getWordParts()) {
|
||||
if (!part.getCopyText().isEmpty()) {
|
||||
text += part.getCopyText();
|
||||
// // middle lines
|
||||
// for (i++; (int)i < this->selection.max.messageIndex; i++) {
|
||||
// for (const messages::MessageLayoutElement &part : messagesSnapshot[i]->getWordParts())
|
||||
// {
|
||||
// if (!part.getCopyText().isEmpty()) {
|
||||
// text += part.getCopyText();
|
||||
|
||||
if (part.hasTrailingSpace()) {
|
||||
text += " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
text += "\n";
|
||||
}
|
||||
// if (part.hasTrailingSpace()) {
|
||||
// text += " ";
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// text += "\n";
|
||||
// }
|
||||
|
||||
// last line
|
||||
charIndex = 0;
|
||||
// // last line
|
||||
// charIndex = 0;
|
||||
|
||||
for (const messages::WordPart &part :
|
||||
messagesSnapshot[this->selection.max.messageIndex]->getWordParts()) {
|
||||
int charLength = part.getCharacterLength();
|
||||
// for (const messages::MessageLayoutElement &part :
|
||||
// messagesSnapshot[this->selection.max.messageIndex]->getWordParts()) {
|
||||
// int charLength = part.getCharacterLength();
|
||||
|
||||
if (charIndex + charLength >= this->selection.max.charIndex) {
|
||||
addPart(part, 0, this->selection.max.charIndex - charIndex);
|
||||
// if (charIndex + charLength >= this->selection.max.charIndex) {
|
||||
// addPart(part, 0, this->selection.max.charIndex - charIndex);
|
||||
|
||||
return text;
|
||||
}
|
||||
// return text;
|
||||
// }
|
||||
|
||||
text += part.getCopyText();
|
||||
// text += part.getCopyText();
|
||||
|
||||
if (part.hasTrailingSpace()) {
|
||||
text += " ";
|
||||
}
|
||||
// if (part.hasTrailingSpace()) {
|
||||
// text += " ";
|
||||
// }
|
||||
|
||||
charIndex += charLength;
|
||||
}
|
||||
// charIndex += charLength;
|
||||
// }
|
||||
|
||||
return text;
|
||||
// return text;
|
||||
return "";
|
||||
}
|
||||
|
||||
bool ChannelView::hasSelection()
|
||||
|
@ -360,7 +354,7 @@ bool ChannelView::getEnableScrollingToBottom() const
|
|||
return this->enableScrollingToBottom;
|
||||
}
|
||||
|
||||
messages::LimitedQueueSnapshot<SharedMessageRef> ChannelView::getMessagesSnapshot()
|
||||
messages::LimitedQueueSnapshot<MessageLayoutPtr> ChannelView::getMessagesSnapshot()
|
||||
{
|
||||
if (!this->paused) {
|
||||
this->snapshot = this->messages.getSnapshot();
|
||||
|
@ -369,7 +363,7 @@ messages::LimitedQueueSnapshot<SharedMessageRef> ChannelView::getMessagesSnapsho
|
|||
return this->snapshot;
|
||||
}
|
||||
|
||||
void ChannelView::setChannel(std::shared_ptr<Channel> newChannel)
|
||||
void ChannelView::setChannel(SharedChannel newChannel)
|
||||
{
|
||||
if (this->channel) {
|
||||
this->detachChannel();
|
||||
|
@ -378,12 +372,12 @@ void ChannelView::setChannel(std::shared_ptr<Channel> newChannel)
|
|||
|
||||
// on new message
|
||||
this->messageAppendedConnection =
|
||||
newChannel->messageAppended.connect([this](SharedMessage &message) {
|
||||
SharedMessageRef deleted;
|
||||
newChannel->messageAppended.connect([this](MessagePtr &message) {
|
||||
MessageLayoutPtr deleted;
|
||||
|
||||
auto messageRef = new MessageRef(message);
|
||||
auto messageRef = new MessageLayout(message);
|
||||
|
||||
if (this->messages.pushBack(SharedMessageRef(messageRef), deleted)) {
|
||||
if (this->messages.pushBack(MessageLayoutPtr(messageRef), deleted)) {
|
||||
if (this->scrollBar.isAtBottom()) {
|
||||
this->scrollBar.scrollToBottom();
|
||||
} else {
|
||||
|
@ -391,7 +385,7 @@ void ChannelView::setChannel(std::shared_ptr<Channel> newChannel)
|
|||
}
|
||||
}
|
||||
|
||||
if (message->containsHighlightedPhrase()) {
|
||||
if (!message->hasFlags(Message::DoNotTriggerNotification)) {
|
||||
this->highlightedMessageReceived.invoke();
|
||||
}
|
||||
|
||||
|
@ -402,12 +396,12 @@ void ChannelView::setChannel(std::shared_ptr<Channel> newChannel)
|
|||
});
|
||||
|
||||
this->messageAddedAtStartConnection =
|
||||
newChannel->messagesAddedAtStart.connect([this](std::vector<SharedMessage> &messages) {
|
||||
std::vector<SharedMessageRef> messageRefs;
|
||||
newChannel->messagesAddedAtStart.connect([this](std::vector<MessagePtr> &messages) {
|
||||
std::vector<MessageLayoutPtr> messageRefs;
|
||||
messageRefs.resize(messages.size());
|
||||
qDebug() << messages.size();
|
||||
for (int i = 0; i < messages.size(); i++) {
|
||||
messageRefs.at(i) = SharedMessageRef(new MessageRef(messages.at(i)));
|
||||
for (size_t i = 0; i < messages.size(); i++) {
|
||||
messageRefs.at(i) = MessageLayoutPtr(new MessageLayout(messages.at(i)));
|
||||
}
|
||||
|
||||
if (this->messages.pushFront(messageRefs).size() > 0) {
|
||||
|
@ -420,7 +414,7 @@ void ChannelView::setChannel(std::shared_ptr<Channel> newChannel)
|
|||
|
||||
std::vector<ScrollbarHighlight> highlights;
|
||||
highlights.reserve(messages.size());
|
||||
for (int i = 0; i < messages.size(); i++) {
|
||||
for (size_t i = 0; i < messages.size(); i++) {
|
||||
highlights.push_back(messages.at(i)->getScrollBarHighlight());
|
||||
}
|
||||
|
||||
|
@ -432,7 +426,7 @@ void ChannelView::setChannel(std::shared_ptr<Channel> newChannel)
|
|||
|
||||
// on message removed
|
||||
this->messageRemovedConnection =
|
||||
newChannel->messageRemovedFromStart.connect([this](SharedMessage &) {
|
||||
newChannel->messageRemovedFromStart.connect([this](MessagePtr &) {
|
||||
this->selection.min.messageIndex--;
|
||||
this->selection.max.messageIndex--;
|
||||
this->selection.start.messageIndex--;
|
||||
|
@ -443,8 +437,8 @@ void ChannelView::setChannel(std::shared_ptr<Channel> newChannel)
|
|||
|
||||
// on message replaced
|
||||
this->messageReplacedConnection =
|
||||
newChannel->messageReplaced.connect([this](size_t index, SharedMessage replacement) {
|
||||
SharedMessageRef newItem(new MessageRef(replacement));
|
||||
newChannel->messageReplaced.connect([this](size_t index, MessagePtr replacement) {
|
||||
MessageLayoutPtr newItem(new MessageLayout(replacement));
|
||||
|
||||
this->scrollBar.replaceHighlight(index, replacement->getScrollBarHighlight());
|
||||
|
||||
|
@ -455,11 +449,11 @@ void ChannelView::setChannel(std::shared_ptr<Channel> newChannel)
|
|||
auto snapshot = newChannel->getMessageSnapshot();
|
||||
|
||||
for (size_t i = 0; i < snapshot.getLength(); i++) {
|
||||
SharedMessageRef deleted;
|
||||
MessageLayoutPtr deleted;
|
||||
|
||||
auto messageRef = new MessageRef(snapshot[i]);
|
||||
auto messageRef = new MessageLayout(snapshot[i]);
|
||||
|
||||
this->messages.pushBack(SharedMessageRef(messageRef), deleted);
|
||||
this->messages.pushBack(MessageLayoutPtr(messageRef), deleted);
|
||||
}
|
||||
|
||||
this->channel = newChannel;
|
||||
|
@ -513,47 +507,20 @@ void ChannelView::setSelection(const SelectionItem &start, const SelectionItem &
|
|||
void ChannelView::paintEvent(QPaintEvent * /*event*/)
|
||||
{
|
||||
// BENCH(timer);
|
||||
|
||||
QPainter painter(this);
|
||||
|
||||
// only update gif emotes
|
||||
#ifndef Q_OS_MACOS
|
||||
// if (this->onlyUpdateEmotes) {
|
||||
// this->onlyUpdateEmotes = false;
|
||||
|
||||
// for (const GifEmoteData &item : this->gifEmotes) {
|
||||
// painter.fillRect(item.rect, this->themeManager.ChatBackground);
|
||||
|
||||
// painter.drawPixmap(item.rect, *item.image->getPixmap());
|
||||
// }
|
||||
|
||||
// return;
|
||||
// }
|
||||
#endif
|
||||
|
||||
// update all messages
|
||||
this->gifEmotes.clear();
|
||||
|
||||
painter.fillRect(rect(), this->themeManager.splits.background);
|
||||
|
||||
// draw messages
|
||||
this->drawMessages(painter, false);
|
||||
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
|
||||
// draw gif emotes
|
||||
for (GifEmoteData &item : this->gifEmotes) {
|
||||
painter.drawPixmap(item.rect, *item.image->getPixmap());
|
||||
}
|
||||
|
||||
// draw the overlays of the messages (such as disabled message blur-out)
|
||||
this->drawMessages(painter, true);
|
||||
this->drawMessages(painter);
|
||||
|
||||
// MARK(timer);
|
||||
}
|
||||
|
||||
// if overlays is false then it draws the message, if true then it draws things such as the grey
|
||||
// overlay when a message is disabled
|
||||
void ChannelView::drawMessages(QPainter &painter, bool overlays)
|
||||
void ChannelView::drawMessages(QPainter &painter)
|
||||
{
|
||||
auto messagesSnapshot = this->getMessagesSnapshot();
|
||||
|
||||
|
@ -566,83 +533,16 @@ void ChannelView::drawMessages(QPainter &painter, bool overlays)
|
|||
int y = -(messagesSnapshot[start].get()->getHeight() *
|
||||
(fmod(this->scrollBar.getCurrentValue(), 1)));
|
||||
|
||||
messages::MessageRef *end = nullptr;
|
||||
messages::MessageLayout *end = nullptr;
|
||||
|
||||
for (size_t i = start; i < messagesSnapshot.getLength(); ++i) {
|
||||
messages::MessageRef *messageRef = messagesSnapshot[i].get();
|
||||
messages::MessageLayout *layout = messagesSnapshot[i].get();
|
||||
|
||||
if (overlays) {
|
||||
if (messageRef->isDisabled()) {
|
||||
painter.fillRect(0, y, this->width(), messageRef->getHeight(),
|
||||
this->themeManager.messages.disabled);
|
||||
}
|
||||
} else {
|
||||
std::shared_ptr<QPixmap> buffer = messageRef->buffer;
|
||||
layout->paint(painter, y, i, this->selection);
|
||||
|
||||
// bool updateBuffer = messageRef->updateBuffer;
|
||||
bool updateBuffer = false;
|
||||
y += layout->getHeight();
|
||||
|
||||
if (!buffer) {
|
||||
QPixmap *pixmap;
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
|
||||
pixmap = new QPixmap(
|
||||
(int)(this->width() * painter.device()->devicePixelRatioF()),
|
||||
(int)(messageRef->getHeight() * painter.device()->devicePixelRatioF()));
|
||||
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
|
||||
#else
|
||||
pixmap = new QPixmap(this->width(), messageRef->getHeight());
|
||||
#endif
|
||||
buffer = std::shared_ptr<QPixmap>(pixmap);
|
||||
updateBuffer = true;
|
||||
}
|
||||
|
||||
updateBuffer |= this->selecting;
|
||||
|
||||
// update messages that have been changed
|
||||
if (updateBuffer) {
|
||||
this->updateMessageBuffer(messageRef, buffer.get(), i);
|
||||
// qDebug() << "updating buffer xD";
|
||||
}
|
||||
|
||||
// get gif emotes
|
||||
for (messages::WordPart const &wordPart : messageRef->getWordParts()) {
|
||||
if (wordPart.getWord().isImage()) {
|
||||
messages::LazyLoadedImage &lli = wordPart.getWord().getImage();
|
||||
|
||||
if (lli.getAnimated()) {
|
||||
GifEmoteData gifEmoteData;
|
||||
gifEmoteData.image = &lli;
|
||||
QRect rect(wordPart.getX(), wordPart.getY() + y, wordPart.getWidth(),
|
||||
wordPart.getHeight());
|
||||
|
||||
gifEmoteData.rect = rect;
|
||||
|
||||
this->gifEmotes.push_back(gifEmoteData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messageRef->buffer = buffer;
|
||||
|
||||
if (buffer) {
|
||||
//#ifdef Q_OS_MACOS
|
||||
// painter.setRenderHint(QPainter::SmoothPixmapTransform, false);
|
||||
// painter.drawPixmap(0, y,
|
||||
|
||||
// (int)(buffer->width() / painter.device()->devicePixelRatioF()),
|
||||
// (int)(buffer->height() / painter.device()->devicePixelRatioF()),
|
||||
// *buffer.get());
|
||||
//#else
|
||||
painter.drawPixmap(0, y, *buffer.get());
|
||||
//#endif
|
||||
}
|
||||
}
|
||||
|
||||
y += messageRef->getHeight();
|
||||
|
||||
end = messageRef;
|
||||
end = layout;
|
||||
if (y > height()) {
|
||||
break;
|
||||
}
|
||||
|
@ -662,222 +562,168 @@ void ChannelView::drawMessages(QPainter &painter, bool overlays)
|
|||
}
|
||||
|
||||
// delete the message buffers that aren't on screen
|
||||
for (std::shared_ptr<messages::MessageRef> item : this->messagesOnScreen) {
|
||||
item->buffer.reset();
|
||||
for (const std::shared_ptr<messages::MessageLayout> &item : this->messagesOnScreen) {
|
||||
item->deleteBuffer();
|
||||
}
|
||||
|
||||
this->messagesOnScreen.clear();
|
||||
|
||||
// add all messages on screen to the map
|
||||
for (size_t i = start; i < messagesSnapshot.getLength(); ++i) {
|
||||
std::shared_ptr<messages::MessageRef> messageRef = messagesSnapshot[i];
|
||||
std::shared_ptr<messages::MessageLayout> layout = messagesSnapshot[i];
|
||||
|
||||
this->messagesOnScreen.insert(messageRef);
|
||||
this->messagesOnScreen.insert(layout);
|
||||
|
||||
if (messageRef.get() == end) {
|
||||
if (layout.get() == end) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelView::updateMessageBuffer(messages::MessageRef *messageRef, QPixmap *buffer,
|
||||
int messageIndex)
|
||||
{
|
||||
QPainter painter(buffer);
|
||||
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
|
||||
// draw background
|
||||
// if (this->selectionMin.messageIndex <= messageIndex &&
|
||||
// this->selectionMax.messageIndex >= messageIndex) {
|
||||
// painter.fillRect(buffer->rect(), QColor(24, 55, 25));
|
||||
//} else {
|
||||
painter.fillRect(buffer->rect(),
|
||||
(messageRef->getMessage()->containsHighlightedPhrase())
|
||||
? this->themeManager.messages.backgrounds.highlighted
|
||||
: this->themeManager.messages.backgrounds.regular);
|
||||
// void ChannelView::drawMessageSelection(QPainter &painter, messages::MessageLayout *messageRef,
|
||||
// int messageIndex, int bufferHeight)
|
||||
//{
|
||||
// if (this->selection.min.messageIndex > messageIndex ||
|
||||
// this->selection.max.messageIndex < messageIndex) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// QColor selectionColor = this->themeManager.messages.selection;
|
||||
//
|
||||
// int charIndex = 0;
|
||||
// size_t i = 0;
|
||||
// auto &parts = messageRef->getWordParts();
|
||||
//
|
||||
// int currentLineNumber = 0;
|
||||
// QRect rect;
|
||||
//
|
||||
// if (parts.size() > 0) {
|
||||
// if (selection.min.messageIndex == messageIndex) {
|
||||
// rect.setTop(parts.at(0).getY());
|
||||
// }
|
||||
// rect.setLeft(parts.at(0).getX());
|
||||
// }
|
||||
//
|
||||
// // skip until selection start
|
||||
// if (this->selection.min.messageIndex == messageIndex && this->selection.min.charIndex != 0) {
|
||||
// for (; i < parts.size(); i++) {
|
||||
// const messages::MessageLayoutElement &part = parts.at(i);
|
||||
// auto characterLength = part.getCharacterLength();
|
||||
//
|
||||
// if (characterLength + charIndex > selection.min.charIndex) {
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// charIndex += characterLength;
|
||||
// currentLineNumber = part.getLineNumber();
|
||||
// }
|
||||
//
|
||||
// if (i >= parts.size()) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // handle word that has a cut of selection
|
||||
// const messages::MessageLayoutElement &part = parts.at(i);
|
||||
//
|
||||
// // check if selection if single word
|
||||
// int characterLength = part.getCharacterLength();
|
||||
// bool isSingleWord = charIndex + characterLength > this->selection.max.charIndex &&
|
||||
// this->selection.max.messageIndex == messageIndex;
|
||||
//
|
||||
// rect = part.getRect();
|
||||
// currentLineNumber = part.getLineNumber();
|
||||
//
|
||||
// if (part.getWord().isText()) {
|
||||
// int offset = this->selection.min.charIndex - charIndex;
|
||||
//
|
||||
// for (int j = 0; j < offset; j++) {
|
||||
// rect.setLeft(rect.left() + part.getCharWidth(j, this->getDpiMultiplier()));
|
||||
// }
|
||||
//
|
||||
// if (isSingleWord) {
|
||||
// int length = (this->selection.max.charIndex - charIndex) - offset;
|
||||
//
|
||||
// rect.setRight(part.getX());
|
||||
//
|
||||
// for (int j = 0; j < offset + length; j++) {
|
||||
// rect.setRight(rect.right() + part.getCharWidth(j, this->getDpiMultiplier()));
|
||||
// }
|
||||
//
|
||||
// painter.fillRect(rect, selectionColor);
|
||||
//
|
||||
// return;
|
||||
// }
|
||||
// } else {
|
||||
// if (isSingleWord) {
|
||||
// if (charIndex + 1 != this->selection.max.charIndex) {
|
||||
// rect.setRight(part.getX() + part.getWord().getImage().getScaledWidth());
|
||||
// }
|
||||
// painter.fillRect(rect, selectionColor);
|
||||
//
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (charIndex != this->selection.min.charIndex) {
|
||||
// rect.setLeft(part.getX() + part.getWord().getImage().getScaledWidth());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// i++;
|
||||
// charIndex += characterLength;
|
||||
// }
|
||||
//
|
||||
// // go through lines and draw selection
|
||||
// for (; i < parts.size(); i++) {
|
||||
// const messages::MessageLayoutElement &part = parts.at(i);
|
||||
//
|
||||
// int charLength = part.getCharacterLength();
|
||||
//
|
||||
// bool isLastSelectedWord = this->selection.max.messageIndex == messageIndex &&
|
||||
// charIndex + charLength > this->selection.max.charIndex;
|
||||
//
|
||||
// if (part.getLineNumber() == currentLineNumber) {
|
||||
// rect.setLeft(std::min(rect.left(), part.getX()));
|
||||
// rect.setTop(std::min(rect.top(), part.getY()));
|
||||
// rect.setRight(std::max(rect.right(), part.getRight()));
|
||||
// rect.setBottom(std::max(rect.bottom(), part.getBottom() - 1));
|
||||
// } else {
|
||||
// painter.fillRect(rect, selectionColor);
|
||||
//
|
||||
// currentLineNumber = part.getLineNumber();
|
||||
//
|
||||
// rect = part.getRect();
|
||||
// }
|
||||
//
|
||||
// if (isLastSelectedWord) {
|
||||
// if (part.getWord().isText()) {
|
||||
// int offset = this->selection.min.charIndex - charIndex;
|
||||
//
|
||||
// int length = (this->selection.max.charIndex - charIndex) - offset;
|
||||
//
|
||||
// rect.setRight(part.getX());
|
||||
//
|
||||
// for (int j = 0; j < offset + length; j++) {
|
||||
// rect.setRight(rect.right() + part.getCharWidth(j, this->getDpiMultiplier()));
|
||||
// }
|
||||
// } else {
|
||||
// if (this->selection.max.charIndex == charIndex) {
|
||||
// rect.setRight(part.getX());
|
||||
// }
|
||||
// }
|
||||
// painter.fillRect(rect, selectionColor);
|
||||
//
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// charIndex += charLength;
|
||||
// }
|
||||
//
|
||||
// if (this->selection.max.messageIndex != messageIndex) {
|
||||
// rect.setBottom(bufferHeight);
|
||||
// }
|
||||
//
|
||||
// painter.fillRect(rect, selectionColor);
|
||||
//}
|
||||
|
||||
// draw selection
|
||||
if (!selection.isEmpty()) {
|
||||
drawMessageSelection(painter, messageRef, messageIndex, buffer->height());
|
||||
}
|
||||
|
||||
// draw message
|
||||
for (messages::WordPart const &wordPart : messageRef->getWordParts()) {
|
||||
// image
|
||||
if (wordPart.getWord().isImage()) {
|
||||
messages::LazyLoadedImage &lli = wordPart.getWord().getImage();
|
||||
|
||||
const QPixmap *image = lli.getPixmap();
|
||||
|
||||
if (image != nullptr && !lli.getAnimated()) {
|
||||
painter.drawPixmap(QRect(wordPart.getX(), wordPart.getY(), wordPart.getWidth(),
|
||||
wordPart.getHeight()),
|
||||
*image);
|
||||
}
|
||||
}
|
||||
// text
|
||||
else {
|
||||
QColor color = wordPart.getWord().getTextColor().getColor(this->themeManager);
|
||||
|
||||
this->themeManager.normalizeColor(color);
|
||||
|
||||
painter.setPen(color);
|
||||
painter.setFont(wordPart.getWord().getFont(this->getDpiMultiplier()));
|
||||
|
||||
painter.drawText(QRectF(wordPart.getX(), wordPart.getY(), 10000, 10000),
|
||||
wordPart.getText(), QTextOption(Qt::AlignLeft | Qt::AlignTop));
|
||||
}
|
||||
}
|
||||
|
||||
messageRef->updateBuffer = false;
|
||||
}
|
||||
|
||||
void ChannelView::drawMessageSelection(QPainter &painter, messages::MessageRef *messageRef,
|
||||
int messageIndex, int bufferHeight)
|
||||
{
|
||||
if (this->selection.min.messageIndex > messageIndex ||
|
||||
this->selection.max.messageIndex < messageIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
QColor selectionColor = this->themeManager.messages.selection;
|
||||
|
||||
int charIndex = 0;
|
||||
size_t i = 0;
|
||||
auto &parts = messageRef->getWordParts();
|
||||
|
||||
int currentLineNumber = 0;
|
||||
QRect rect;
|
||||
|
||||
if (parts.size() > 0) {
|
||||
if (selection.min.messageIndex == messageIndex) {
|
||||
rect.setTop(parts.at(0).getY());
|
||||
}
|
||||
rect.setLeft(parts.at(0).getX());
|
||||
}
|
||||
|
||||
// skip until selection start
|
||||
if (this->selection.min.messageIndex == messageIndex && this->selection.min.charIndex != 0) {
|
||||
for (; i < parts.size(); i++) {
|
||||
const messages::WordPart &part = parts.at(i);
|
||||
auto characterLength = part.getCharacterLength();
|
||||
|
||||
if (characterLength + charIndex > selection.min.charIndex) {
|
||||
break;
|
||||
}
|
||||
|
||||
charIndex += characterLength;
|
||||
currentLineNumber = part.getLineNumber();
|
||||
}
|
||||
|
||||
if (i >= parts.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// handle word that has a cut of selection
|
||||
const messages::WordPart &part = parts.at(i);
|
||||
|
||||
// check if selection if single word
|
||||
int characterLength = part.getCharacterLength();
|
||||
bool isSingleWord = charIndex + characterLength > this->selection.max.charIndex &&
|
||||
this->selection.max.messageIndex == messageIndex;
|
||||
|
||||
rect = part.getRect();
|
||||
currentLineNumber = part.getLineNumber();
|
||||
|
||||
if (part.getWord().isText()) {
|
||||
int offset = this->selection.min.charIndex - charIndex;
|
||||
|
||||
for (int j = 0; j < offset; j++) {
|
||||
rect.setLeft(rect.left() + part.getCharWidth(j, this->getDpiMultiplier()));
|
||||
}
|
||||
|
||||
if (isSingleWord) {
|
||||
int length = (this->selection.max.charIndex - charIndex) - offset;
|
||||
|
||||
rect.setRight(part.getX());
|
||||
|
||||
for (int j = 0; j < offset + length; j++) {
|
||||
rect.setRight(rect.right() + part.getCharWidth(j, this->getDpiMultiplier()));
|
||||
}
|
||||
|
||||
painter.fillRect(rect, selectionColor);
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (isSingleWord) {
|
||||
if (charIndex + 1 != this->selection.max.charIndex) {
|
||||
rect.setRight(part.getX() + part.getWord().getImage().getScaledWidth());
|
||||
}
|
||||
painter.fillRect(rect, selectionColor);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (charIndex != this->selection.min.charIndex) {
|
||||
rect.setLeft(part.getX() + part.getWord().getImage().getScaledWidth());
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
charIndex += characterLength;
|
||||
}
|
||||
|
||||
// go through lines and draw selection
|
||||
for (; i < parts.size(); i++) {
|
||||
const messages::WordPart &part = parts.at(i);
|
||||
|
||||
int charLength = part.getCharacterLength();
|
||||
|
||||
bool isLastSelectedWord = this->selection.max.messageIndex == messageIndex &&
|
||||
charIndex + charLength > this->selection.max.charIndex;
|
||||
|
||||
if (part.getLineNumber() == currentLineNumber) {
|
||||
rect.setLeft(std::min(rect.left(), part.getX()));
|
||||
rect.setTop(std::min(rect.top(), part.getY()));
|
||||
rect.setRight(std::max(rect.right(), part.getRight()));
|
||||
rect.setBottom(std::max(rect.bottom(), part.getBottom() - 1));
|
||||
} else {
|
||||
painter.fillRect(rect, selectionColor);
|
||||
|
||||
currentLineNumber = part.getLineNumber();
|
||||
|
||||
rect = part.getRect();
|
||||
}
|
||||
|
||||
if (isLastSelectedWord) {
|
||||
if (part.getWord().isText()) {
|
||||
int offset = this->selection.min.charIndex - charIndex;
|
||||
|
||||
int length = (this->selection.max.charIndex - charIndex) - offset;
|
||||
|
||||
rect.setRight(part.getX());
|
||||
|
||||
for (int j = 0; j < offset + length; j++) {
|
||||
rect.setRight(rect.right() + part.getCharWidth(j, this->getDpiMultiplier()));
|
||||
}
|
||||
} else {
|
||||
if (this->selection.max.charIndex == charIndex) {
|
||||
rect.setRight(part.getX());
|
||||
}
|
||||
}
|
||||
painter.fillRect(rect, selectionColor);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
charIndex += charLength;
|
||||
}
|
||||
|
||||
if (this->selection.max.messageIndex != messageIndex) {
|
||||
rect.setBottom(bufferHeight);
|
||||
}
|
||||
|
||||
painter.fillRect(rect, selectionColor);
|
||||
}
|
||||
|
||||
void ChannelView::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
|
@ -888,7 +734,8 @@ void ChannelView::wheelEvent(QWheelEvent *event)
|
|||
float delta = event->delta() * 1.5 * mouseMultiplier;
|
||||
|
||||
auto snapshot = this->getMessagesSnapshot();
|
||||
int i = std::min((int)desired, (int)snapshot.getLength());
|
||||
int snapshotLength = (int)snapshot.getLength();
|
||||
int i = std::min((int)desired, snapshotLength);
|
||||
|
||||
if (delta > 0) {
|
||||
float scrollFactor = fmod(desired, 1);
|
||||
|
@ -916,7 +763,7 @@ void ChannelView::wheelEvent(QWheelEvent *event)
|
|||
float scrollFactor = 1 - fmod(desired, 1);
|
||||
float currentScrollLeft = (int)(scrollFactor * snapshot[i]->getHeight());
|
||||
|
||||
for (; i < snapshot.getLength(); i++) {
|
||||
for (; i < snapshotLength; i++) {
|
||||
if (delta < currentScrollLeft) {
|
||||
desired += scrollFactor * ((double)delta / currentScrollLeft);
|
||||
break;
|
||||
|
@ -925,7 +772,7 @@ void ChannelView::wheelEvent(QWheelEvent *event)
|
|||
desired += scrollFactor;
|
||||
}
|
||||
|
||||
if (i == snapshot.getLength() - 1) {
|
||||
if (i == snapshotLength - 1) {
|
||||
desired = snapshot.getLength();
|
||||
} else {
|
||||
snapshot[i + 1]->layout(LAYOUT_WIDTH, this->getDpiMultiplier());
|
||||
|
@ -957,12 +804,12 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
|
|||
}
|
||||
|
||||
auto tooltipWidget = TooltipWidget::getInstance();
|
||||
std::shared_ptr<messages::MessageRef> message;
|
||||
std::shared_ptr<messages::MessageLayout> layout;
|
||||
QPoint relativePos;
|
||||
int messageIndex;
|
||||
|
||||
// no message under cursor
|
||||
if (!tryGetMessageAt(event->pos(), message, relativePos, messageIndex)) {
|
||||
if (!tryGetMessageAt(event->pos(), layout, relativePos, messageIndex)) {
|
||||
this->setCursor(Qt::ArrowCursor);
|
||||
tooltipWidget->hide();
|
||||
return;
|
||||
|
@ -971,7 +818,7 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
|
|||
// is selecting
|
||||
if (this->selecting) {
|
||||
this->pause(500);
|
||||
int index = message->getSelectionIndex(relativePos);
|
||||
int index = layout->getSelectionIndex(relativePos);
|
||||
|
||||
this->setSelection(this->selection.start, SelectionItem(messageIndex, index));
|
||||
|
||||
|
@ -979,29 +826,28 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event)
|
|||
}
|
||||
|
||||
// message under cursor is collapsed
|
||||
if (message->isCollapsed()) {
|
||||
if (layout->getFlags() & MessageLayout::Collapsed) {
|
||||
this->setCursor(Qt::PointingHandCursor);
|
||||
tooltipWidget->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// check if word underneath cursor
|
||||
const messages::Word *hoverWord;
|
||||
if ((hoverWord = message->tryGetWordPart(relativePos)) == nullptr) {
|
||||
const messages::MessageLayoutElement *hoverLayoutElement = layout->getElementAt(relativePos);
|
||||
|
||||
if (hoverLayoutElement == nullptr) {
|
||||
this->setCursor(Qt::ArrowCursor);
|
||||
tooltipWidget->hide();
|
||||
return;
|
||||
}
|
||||
const auto &tooltip = hoverWord->getTooltip();
|
||||
const auto &tooltip = hoverLayoutElement->getCreator().getTooltip();
|
||||
|
||||
if (hoverWord->isImage()) {
|
||||
tooltipWidget->moveTo(event->globalPos());
|
||||
tooltipWidget->setText(tooltip);
|
||||
tooltipWidget->show();
|
||||
}
|
||||
|
||||
// check if word has a link
|
||||
if (hoverWord->getLink().isValid()) {
|
||||
if (hoverLayoutElement->getCreator().getLink().isValid()) {
|
||||
this->setCursor(Qt::PointingHandCursor);
|
||||
} else {
|
||||
this->setCursor(Qt::ArrowCursor);
|
||||
|
@ -1018,13 +864,13 @@ void ChannelView::mousePressEvent(QMouseEvent *event)
|
|||
|
||||
this->lastPressPosition = event->screenPos();
|
||||
|
||||
std::shared_ptr<messages::MessageRef> message;
|
||||
std::shared_ptr<messages::MessageLayout> layout;
|
||||
QPoint relativePos;
|
||||
int messageIndex;
|
||||
|
||||
this->mouseDown(event);
|
||||
|
||||
if (!tryGetMessageAt(event->pos(), message, relativePos, messageIndex)) {
|
||||
if (!tryGetMessageAt(event->pos(), layout, relativePos, messageIndex)) {
|
||||
setCursor(Qt::ArrowCursor);
|
||||
|
||||
auto messagesSnapshot = this->getMessagesSnapshot();
|
||||
|
@ -1045,11 +891,11 @@ void ChannelView::mousePressEvent(QMouseEvent *event)
|
|||
}
|
||||
|
||||
// check if message is collapsed
|
||||
if (message->isCollapsed()) {
|
||||
if (layout->getFlags() & MessageLayout::Collapsed) {
|
||||
return;
|
||||
}
|
||||
|
||||
int index = message->getSelectionIndex(relativePos);
|
||||
int index = layout->getSelectionIndex(relativePos);
|
||||
|
||||
auto selectionItem = SelectionItem(messageIndex, index);
|
||||
this->setSelection(selectionItem, selectionItem);
|
||||
|
@ -1087,30 +933,30 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event)
|
|||
|
||||
// show user thing pajaW
|
||||
|
||||
std::shared_ptr<messages::MessageRef> message;
|
||||
std::shared_ptr<messages::MessageLayout> layout;
|
||||
QPoint relativePos;
|
||||
int messageIndex;
|
||||
|
||||
if (!tryGetMessageAt(event->pos(), message, relativePos, messageIndex)) {
|
||||
if (!tryGetMessageAt(event->pos(), layout, relativePos, messageIndex)) {
|
||||
// No message at clicked position
|
||||
this->userPopupWidget.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// message under cursor is collapsed
|
||||
if (message->isCollapsed()) {
|
||||
message->setCollapsed(false);
|
||||
if (layout->getFlags() & MessageLayout::Collapsed) {
|
||||
layout->addFlags(MessageLayout::Collapsed);
|
||||
this->layoutMessages();
|
||||
return;
|
||||
}
|
||||
|
||||
const messages::Word *hoverWord;
|
||||
const messages::MessageLayoutElement *hoverLayoutElement = layout->getElementAt(relativePos);
|
||||
|
||||
if ((hoverWord = message->tryGetWordPart(relativePos)) == nullptr) {
|
||||
if (hoverLayoutElement == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &link = hoverWord->getLink();
|
||||
auto &link = hoverLayoutElement->getCreator().getLink();
|
||||
|
||||
switch (link.getType()) {
|
||||
case messages::Link::UserInfo: {
|
||||
|
@ -1131,7 +977,7 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event)
|
|||
}
|
||||
}
|
||||
|
||||
bool ChannelView::tryGetMessageAt(QPoint p, std::shared_ptr<messages::MessageRef> &_message,
|
||||
bool ChannelView::tryGetMessageAt(QPoint p, std::shared_ptr<messages::MessageLayout> &_message,
|
||||
QPoint &relativePos, int &index)
|
||||
{
|
||||
auto messagesSnapshot = this->getMessagesSnapshot();
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include "channel.hpp"
|
||||
#include "messages/lazyloadedimage.hpp"
|
||||
#include "messages/image.hpp"
|
||||
#include "messages/layouts/messagelayout.hpp"
|
||||
#include "messages/limitedqueuesnapshot.hpp"
|
||||
#include "messages/messageref.hpp"
|
||||
#include "messages/messageelement.hpp"
|
||||
#include "messages/selection.hpp"
|
||||
#include "messages/word.hpp"
|
||||
#include "widgets/accountpopup.hpp"
|
||||
#include "widgets/basewidget.hpp"
|
||||
#include "widgets/helper/rippleeffectlabel.hpp"
|
||||
|
@ -31,7 +31,6 @@ public:
|
|||
explicit ChannelView(BaseWidget *parent = 0);
|
||||
~ChannelView();
|
||||
|
||||
void updateGifEmotes();
|
||||
void queueUpdate();
|
||||
Scrollbar &getScrollBar();
|
||||
QString getSelectedText();
|
||||
|
@ -41,8 +40,8 @@ public:
|
|||
bool getEnableScrollingToBottom() const;
|
||||
void pause(int msecTimeout);
|
||||
|
||||
void setChannel(std::shared_ptr<Channel> channel);
|
||||
messages::LimitedQueueSnapshot<messages::SharedMessageRef> getMessagesSnapshot();
|
||||
void setChannel(SharedChannel channel);
|
||||
messages::LimitedQueueSnapshot<messages::MessageLayoutPtr> getMessagesSnapshot();
|
||||
void layoutMessages();
|
||||
|
||||
void clearMessages();
|
||||
|
@ -64,35 +63,25 @@ protected:
|
|||
virtual void mousePressEvent(QMouseEvent *event) override;
|
||||
virtual void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
|
||||
bool tryGetMessageAt(QPoint p, std::shared_ptr<messages::MessageRef> &message,
|
||||
bool tryGetMessageAt(QPoint p, std::shared_ptr<messages::MessageLayout> &message,
|
||||
QPoint &relativePos, int &index);
|
||||
|
||||
private:
|
||||
struct GifEmoteData {
|
||||
messages::LazyLoadedImage *image;
|
||||
QRect rect;
|
||||
};
|
||||
|
||||
QTimer updateTimer;
|
||||
bool updateQueued = false;
|
||||
bool messageWasAdded = false;
|
||||
bool paused = false;
|
||||
QTimer pauseTimeout;
|
||||
|
||||
messages::LimitedQueueSnapshot<messages::SharedMessageRef> snapshot;
|
||||
messages::LimitedQueueSnapshot<messages::MessageLayoutPtr> snapshot;
|
||||
|
||||
void detachChannel();
|
||||
void actuallyLayoutMessages();
|
||||
|
||||
void drawMessages(QPainter &painter, bool overlays);
|
||||
void updateMessageBuffer(messages::MessageRef *messageRef, QPixmap *buffer, int messageIndex);
|
||||
void drawMessageSelection(QPainter &painter, messages::MessageRef *messageRef, int messageIndex,
|
||||
int bufferHeight);
|
||||
void drawMessages(QPainter &painter);
|
||||
void setSelection(const messages::SelectionItem &start, const messages::SelectionItem &end);
|
||||
|
||||
std::shared_ptr<Channel> channel;
|
||||
|
||||
std::vector<GifEmoteData> gifEmotes;
|
||||
SharedChannel channel;
|
||||
|
||||
Scrollbar scrollBar;
|
||||
RippleEffectLabel *goToBottom;
|
||||
|
@ -112,7 +101,7 @@ private:
|
|||
messages::Selection selection;
|
||||
bool selecting = false;
|
||||
|
||||
messages::LimitedQueue<messages::SharedMessageRef> messages;
|
||||
messages::LimitedQueue<messages::MessageLayoutPtr> messages;
|
||||
|
||||
boost::signals2::connection messageAppendedConnection;
|
||||
boost::signals2::connection messageAddedAtStartConnection;
|
||||
|
@ -123,7 +112,7 @@ private:
|
|||
|
||||
std::vector<pajlada::Signals::ScopedConnection> managedConnections;
|
||||
|
||||
std::unordered_set<std::shared_ptr<messages::MessageRef>> messagesOnScreen;
|
||||
std::unordered_set<std::shared_ptr<messages::MessageLayout>> messagesOnScreen;
|
||||
|
||||
private slots:
|
||||
void wordTypeMaskChanged()
|
||||
|
|
|
@ -59,7 +59,7 @@ void SearchPopup::initLayout()
|
|||
}
|
||||
}
|
||||
|
||||
void SearchPopup::setChannel(std::shared_ptr<Channel> channel)
|
||||
void SearchPopup::setChannel(SharedChannel channel)
|
||||
{
|
||||
this->snapshot = channel->getMessageSnapshot();
|
||||
this->performSearch();
|
||||
|
@ -71,13 +71,13 @@ void SearchPopup::performSearch()
|
|||
{
|
||||
QString text = searchInput->text();
|
||||
|
||||
std::shared_ptr<Channel> channel(new Channel("search"));
|
||||
SharedChannel channel(new Channel("search"));
|
||||
|
||||
for (size_t i = 0; i < this->snapshot.getLength(); i++) {
|
||||
messages::SharedMessage message = this->snapshot[i];
|
||||
messages::MessagePtr message = this->snapshot[i];
|
||||
|
||||
if (text.isEmpty() ||
|
||||
message->getContent().indexOf(this->searchInput->text(), 0, Qt::CaseInsensitive) !=
|
||||
message->getSearchText().indexOf(this->searchInput->text(), 0, Qt::CaseInsensitive) !=
|
||||
-1) {
|
||||
channel->addMessage(message);
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ public:
|
|||
void setChannel(std::shared_ptr<Channel> channel);
|
||||
|
||||
private:
|
||||
messages::LimitedQueueSnapshot<messages::SharedMessage> snapshot;
|
||||
messages::LimitedQueueSnapshot<messages::MessagePtr> snapshot;
|
||||
QLineEdit *searchInput;
|
||||
ChannelView *channelView;
|
||||
|
||||
|
|
|
@ -48,8 +48,7 @@ SplitInput::SplitInput(Split *_chatWidget)
|
|||
this->textLengthLabel.setAlignment(Qt::AlignRight);
|
||||
|
||||
this->emotesLabel.getLabel().setTextFormat(Qt::RichText);
|
||||
this->emotesLabel.getLabel().setText(
|
||||
"<img src=':/images/emote.svg' width='12' height='12' "
|
||||
this->emotesLabel.getLabel().setText("<img src=':/images/emote.svg' width='12' height='12' "
|
||||
"/>");
|
||||
|
||||
connect(&this->emotesLabel, &RippleEffectLabel::clicked, [this] {
|
||||
|
@ -86,16 +85,17 @@ SplitInput::SplitInput(Split *_chatWidget)
|
|||
sendMessage = sendMessage.replace('\n', ' ');
|
||||
|
||||
c->sendMessage(sendMessage);
|
||||
prevMsg.append(message);
|
||||
this->prevMsg.append(message);
|
||||
|
||||
event->accept();
|
||||
if (!(event->modifiers() == Qt::ControlModifier)) {
|
||||
textInput.setText(QString());
|
||||
prevIndex = 0;
|
||||
} else if (textInput.toPlainText() == prevMsg.at(prevMsg.size() - 1)) {
|
||||
prevMsg.removeLast();
|
||||
this->textInput.setText(QString());
|
||||
this->prevIndex = 0;
|
||||
} else if (this->textInput.toPlainText() ==
|
||||
this->prevMsg.at(this->prevMsg.size() - 1)) {
|
||||
this->prevMsg.removeLast();
|
||||
}
|
||||
prevIndex = prevMsg.size();
|
||||
this->prevIndex = this->prevMsg.size();
|
||||
} else if (event->key() == Qt::Key_Up) {
|
||||
if (event->modifiers() == Qt::AltModifier) {
|
||||
SplitContainer *page =
|
||||
|
@ -108,9 +108,9 @@ SplitInput::SplitInput(Split *_chatWidget)
|
|||
|
||||
page->requestFocus(reqX, reqY);
|
||||
} else {
|
||||
if (prevMsg.size() && prevIndex) {
|
||||
prevIndex--;
|
||||
textInput.setText(prevMsg.at(prevIndex));
|
||||
if (this->prevMsg.size() && this->prevIndex) {
|
||||
this->prevIndex--;
|
||||
this->textInput.setText(this->prevMsg.at(this->prevIndex));
|
||||
}
|
||||
}
|
||||
} else if (event->key() == Qt::Key_Down) {
|
||||
|
@ -125,12 +125,13 @@ SplitInput::SplitInput(Split *_chatWidget)
|
|||
|
||||
page->requestFocus(reqX, reqY);
|
||||
} else {
|
||||
if (prevIndex != (prevMsg.size() - 1) && prevIndex != prevMsg.size()) {
|
||||
prevIndex++;
|
||||
textInput.setText(prevMsg.at(prevIndex));
|
||||
if (this->prevIndex != (this->prevMsg.size() - 1) &&
|
||||
this->prevIndex != this->prevMsg.size()) {
|
||||
this->prevIndex++;
|
||||
this->textInput.setText(this->prevMsg.at(this->prevIndex));
|
||||
} else {
|
||||
prevIndex = prevMsg.size();
|
||||
textInput.setText(QString());
|
||||
this->prevIndex = this->prevMsg.size();
|
||||
this->textInput.setText(QString());
|
||||
}
|
||||
}
|
||||
} else if (event->key() == Qt::Key_Left) {
|
||||
|
|
|
@ -48,7 +48,7 @@ private:
|
|||
QLabel textLengthLabel;
|
||||
RippleEffectLabel emotesLabel;
|
||||
QStringList prevMsg;
|
||||
unsigned int prevIndex = 0;
|
||||
int prevIndex = 0;
|
||||
virtual void refreshTheme() override;
|
||||
|
||||
private slots:
|
||||
|
|
|
@ -126,9 +126,9 @@ void Notebook::select(SplitContainer *page)
|
|||
this->performLayout();
|
||||
}
|
||||
|
||||
void Notebook::selectIndex(unsigned index)
|
||||
void Notebook::selectIndex(int index)
|
||||
{
|
||||
if (index >= this->pages.size()) {
|
||||
if (index < 0 || index >= this->pages.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ public:
|
|||
void removePage(SplitContainer *page);
|
||||
void removeCurrentPage();
|
||||
void select(SplitContainer *page);
|
||||
void selectIndex(unsigned index);
|
||||
void selectIndex(int index);
|
||||
|
||||
SplitContainer *getSelectedPage() const
|
||||
{
|
||||
|
|
|
@ -297,7 +297,7 @@ QVBoxLayout *SettingsDialog::createAppearanceTab()
|
|||
|
||||
auto v = new QVBoxLayout();
|
||||
v->addWidget(createCheckbox("Show timestamp", settings.showTimestamps));
|
||||
v->addWidget(createCheckbox("Show seconds in timestamp", settings.showTimestampSeconds));
|
||||
// fourtf: add timestamp format
|
||||
v->addWidget(createCheckbox("Show badges", settings.showBadges));
|
||||
v->addWidget(createCheckbox("Allow sending duplicate messages (add a space at the end)",
|
||||
settings.allowDuplicateMessages));
|
||||
|
|
|
@ -121,17 +121,17 @@ const std::string &Split::getUUID() const
|
|||
return this->uuid;
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> Split::getChannel() const
|
||||
SharedChannel Split::getChannel() const
|
||||
{
|
||||
return this->channel;
|
||||
}
|
||||
|
||||
std::shared_ptr<Channel> &Split::getChannelRef()
|
||||
SharedChannel &Split::getChannelRef()
|
||||
{
|
||||
return this->channel;
|
||||
}
|
||||
|
||||
void Split::setChannel(std::shared_ptr<Channel> _newChannel)
|
||||
void Split::setChannel(SharedChannel _newChannel)
|
||||
{
|
||||
this->view.setChannel(_newChannel);
|
||||
|
||||
|
@ -212,7 +212,8 @@ void Split::layoutMessages()
|
|||
|
||||
void Split::updateGifEmotes()
|
||||
{
|
||||
this->view.updateGifEmotes();
|
||||
qDebug() << "this shouldn't even exist";
|
||||
this->view.queueUpdate();
|
||||
}
|
||||
|
||||
void Split::giveFocus(Qt::FocusReason reason)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include "channel.hpp"
|
||||
#include "messages/layouts/messagelayout.hpp"
|
||||
#include "messages/layouts/messagelayoutelement.hpp"
|
||||
#include "messages/limitedqueuesnapshot.hpp"
|
||||
#include "messages/messageref.hpp"
|
||||
#include "messages/word.hpp"
|
||||
#include "messages/wordpart.hpp"
|
||||
#include "messages/messageelement.hpp"
|
||||
#include "widgets/basewidget.hpp"
|
||||
#include "widgets/helper/channelview.hpp"
|
||||
#include "widgets/helper/rippleeffectlabel.hpp"
|
||||
|
@ -55,8 +55,8 @@ public:
|
|||
}
|
||||
|
||||
const std::string &getUUID() const;
|
||||
std::shared_ptr<Channel> getChannel() const;
|
||||
std::shared_ptr<Channel> &getChannelRef();
|
||||
SharedChannel getChannel() const;
|
||||
SharedChannel &getChannelRef();
|
||||
void setFlexSizeX(double x);
|
||||
double getFlexSizeX();
|
||||
void setFlexSizeY(double y);
|
||||
|
@ -73,7 +73,7 @@ protected:
|
|||
|
||||
private:
|
||||
SplitContainer &parentPage;
|
||||
std::shared_ptr<Channel> channel;
|
||||
SharedChannel channel;
|
||||
|
||||
QVBoxLayout vbox;
|
||||
SplitHeader header;
|
||||
|
@ -84,7 +84,7 @@ private:
|
|||
|
||||
boost::signals2::connection channelIDChangedConnection;
|
||||
|
||||
void setChannel(std::shared_ptr<Channel> newChannel);
|
||||
void setChannel(SharedChannel newChannel);
|
||||
void doOpenAccountPopupWidget(AccountPopupWidget *widget, QString user);
|
||||
void channelNameUpdated(const std::string &newChannelName);
|
||||
|
||||
|
|
Loading…
Reference in a new issue