Merge branch 'master' of github.com:fourtf/chatterino2

This commit is contained in:
Rasmus Karlsson 2017-03-11 11:38:56 +01:00
commit 0ace6d2bc8
23 changed files with 536 additions and 116 deletions

10
.travis.yml Normal file
View file

@ -0,0 +1,10 @@
before_install:
- sudo add-apt-repository --yes ppa:ubuntu-sdk-team/ppa
- sudo apt-get update -qq
- sudo apt-get install qtbase5-dev qtdeclarative5-dev libqt5webkit5-dev libsqlite3-dev
- sudo apt-get install qt5-default qttools5-dev-tools
script:
- qmake -qt=qt5 -v
- qmake -qt=qt5
- make

View file

@ -6,3 +6,6 @@ Chatterino 2 is the second installment of my twitch chat client series "Chatteri
1. download the [boost library](https://sourceforge.net/projects/boost/files/boost/1.63.0/boost_1_63_0.zip/download) and extract it to `C:\local\boost`
2. download binaries for OpenSSL >= 1.0.2 or compile it from source. [example download](https://indy.fulgan.com/SSL/)
3. Place libeay32.dll and ssleay32.dll from OpenSSL in a directory in PATH.
## code style
The code is normally formated using clang format in qt creator. [clangformat.txt](https://github.com/fourtf/chatterino2/blob/master/clangformat.txt) contains the style file for clang format.

28
clangformat.txt Normal file
View file

@ -0,0 +1,28 @@
IndentCaseLabels: true
BasedOnStyle: Google
IndentWidth: 4
Standard: Auto
PointerBindsToType: false
Language: Cpp
SpacesBeforeTrailingComments: 2
AccessModifierOffset: -1
AlignEscapedNewlinesLeft: true
AlwaysBreakAfterDefinitionReturnType: true
AlwaysBreakBeforeMultilineStrings: false
BreakConstructorInitializersBeforeComma: true
# BreakBeforeBraces: Linux
BreakBeforeBraces: Custom
AccessModifierOffset: -4
ConstructorInitializerAllOnOneLineOrOnePerLine: false
AllowShortFunctionsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
DerivePointerBinding: false
BraceWrapping: {
AfterNamespace: 'false'
AfterClass: 'true'
BeforeElse: 'false'
AfterControlStatement: 'false'
AfterFunction: 'true'
BeforeCatch: 'false'
}

View file

@ -26,6 +26,10 @@ ConcurrentMap<int, messages::LazyLoadedImage *>
Emotes::ffzChannelEmoteFromCaches;
ConcurrentMap<long, messages::LazyLoadedImage *> Emotes::twitchEmoteFromCache;
ConcurrentMap<QString, messages::LazyLoadedImage *> Emotes::miscImageFromCache;
boost::signals2::signal<void()> Emotes::gifUpdateTimerSignal;
QTimer Emotes::gifUpdateTimer;
bool Emotes::gifUpdateTimerInitiated(false);
int Emotes::generation = 0;
@ -85,7 +89,7 @@ Emotes::loadBttvEmotes()
void
Emotes::loadFfzEmotes()
{
// bttv
// ffz
QNetworkAccessManager *manager = new QNetworkAccessManager();
QUrl url("https://api.frankerfacez.com/v1/set/global");

View file

@ -1,12 +1,17 @@
#ifndef EMOTES_H
#define EMOTES_H
#define GIF_FRAME_LENGTH 33
#include "concurrentmap.h"
#include "messages/lazyloadedimage.h"
#include "twitchemotevalue.h"
#include "windows.h"
#include <QMap>
#include <QMutex>
#include <QTimer>
#include <boost/signals2.hpp>
namespace chatterino {
@ -82,6 +87,24 @@ public:
generation++;
}
static boost::signals2::signal<void()> &
getGifUpdateSignal()
{
if (!gifUpdateTimerInitiated) {
gifUpdateTimerInitiated = true;
gifUpdateTimer.setInterval(30);
gifUpdateTimer.start();
QObject::connect(&gifUpdateTimer, &QTimer::timeout, [] {
gifUpdateTimerSignal();
Windows::repaintGifEmotes();
});
}
return gifUpdateTimerSignal;
}
private:
Emotes();
@ -102,10 +125,15 @@ private:
static QString getTwitchEmoteLink(long id, qreal &scale);
static QTimer gifUpdateTimer;
static bool gifUpdateTimerInitiated;
static void loadFfzEmotes();
static void loadBttvEmotes();
static int generation;
static boost::signals2::signal<void()> gifUpdateTimerSignal;
};
}

View file

@ -5,9 +5,12 @@
#include "ircmanager.h"
#include "windows.h"
#include <QBuffer>
#include <QImageReader>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QTimer>
#include <functional>
namespace chatterino {
@ -16,7 +19,10 @@ namespace messages {
LazyLoadedImage::LazyLoadedImage(const QString &url, qreal scale,
const QString &name, const QString &tooltip,
const QMargins &margin, bool isHat)
: pixmap(NULL)
: currentPixmap(NULL)
, allFrames()
, currentFrame(0)
, currentFrameOffset(0)
, url(url)
, name(name)
, tooltip(tooltip)
@ -31,7 +37,10 @@ LazyLoadedImage::LazyLoadedImage(const QString &url, qreal scale,
LazyLoadedImage::LazyLoadedImage(QPixmap *image, qreal scale,
const QString &name, const QString &tooltip,
const QMargins &margin, bool isHat)
: pixmap(image)
: currentPixmap(image)
, allFrames()
, currentFrame(0)
, currentFrameOffset(0)
, url()
, name(name)
, tooltip(tooltip)
@ -46,7 +55,6 @@ LazyLoadedImage::LazyLoadedImage(QPixmap *image, qreal scale,
void
LazyLoadedImage::loadImage()
{
// QThreadPool::globalInstance()->start(new LambdaQRunnable([=] {
QNetworkAccessManager *manager = new QNetworkAccessManager();
QUrl url(this->url);
@ -55,21 +63,64 @@ LazyLoadedImage::loadImage()
QNetworkReply *reply = manager->get(request);
QObject::connect(reply, &QNetworkReply::finished, [=] {
QPixmap *pixmap = new QPixmap();
pixmap->loadFromData(reply->readAll());
QByteArray array = reply->readAll();
QBuffer buffer(&array);
buffer.open(QIODevice::ReadOnly);
if (pixmap->isNull()) {
return;
QImage image;
QImageReader reader(&buffer);
bool first = true;
for (int index = 0; index < reader.imageCount(); ++index) {
if (reader.read(&image)) {
auto pixmap = new QPixmap(QPixmap::fromImage(image));
if (first) {
first = false;
this->currentPixmap = pixmap;
}
FrameData data;
data.duration = std::max(20, reader.nextImageDelay());
data.image = pixmap;
allFrames.push_back(data);
}
}
if (allFrames.size() > 1) {
this->animated = true;
Emotes::getGifUpdateSignal().connect([this] { gifUpdateTimout(); });
}
this->pixmap = pixmap;
Emotes::incGeneration();
Windows::layoutVisibleChatWidgets();
reply->deleteLater();
manager->deleteLater();
});
// }));
}
void
LazyLoadedImage::gifUpdateTimout()
{
this->currentFrameOffset += GIF_FRAME_LENGTH;
while (true) {
if (this->currentFrameOffset >
this->allFrames.at(this->currentFrame).duration) {
this->currentFrameOffset -=
this->allFrames.at(this->currentFrame).duration;
this->currentFrame =
(this->currentFrame + 1) % this->allFrames.size();
} else {
break;
}
}
this->currentPixmap = this->allFrames[this->currentFrame].image;
}
}
}

View file

@ -7,7 +7,7 @@
namespace chatterino {
namespace messages {
class LazyLoadedImage
class LazyLoadedImage : QObject
{
public:
explicit LazyLoadedImage(const QString &url, qreal scale = 1,
@ -15,7 +15,7 @@ public:
const QString &tooltip = "",
const QMargins &margin = QMargins(),
bool isHat = false);
explicit LazyLoadedImage(QPixmap *pixmap, qreal scale = 1,
explicit LazyLoadedImage(QPixmap *currentPixmap, qreal scale = 1,
const QString &name = "",
const QString &tooltip = "",
const QMargins &margin = QMargins(),
@ -29,7 +29,7 @@ public:
loadImage();
}
return pixmap;
return currentPixmap;
}
qreal
@ -77,23 +77,31 @@ public:
int
getWidth() const
{
if (pixmap == NULL) {
if (currentPixmap == NULL) {
return 16;
}
return pixmap->width();
return currentPixmap->width();
}
int
getHeight() const
{
if (pixmap == NULL) {
if (currentPixmap == NULL) {
return 16;
}
return pixmap->height();
return currentPixmap->height();
}
private:
QPixmap *pixmap;
struct FrameData {
QPixmap *image;
int duration;
};
QPixmap *currentPixmap;
std::vector<FrameData> allFrames;
int currentFrame;
int currentFrameOffset;
QString url;
QString name;
@ -106,6 +114,8 @@ private:
bool isLoading;
void loadImage();
void gifUpdateTimout();
};
}
}

View file

@ -3,8 +3,6 @@
#include "messages/limitedqueuesnapshot.h"
#include <boost/signals2.hpp>
#include <memory>
#include <mutex>
#include <vector>
@ -17,14 +15,13 @@ class LimitedQueue
{
public:
LimitedQueue(int limit = 100, int buffer = 25)
: vector(new std::vector<T>(limit + buffer))
, vectorPtr(this->vector)
, mutex()
: mutex()
, offset(0)
, length(0)
, limit(limit)
, buffer(buffer)
{
vector = std::make_shared<std::vector<T>>();
vector->reserve(this->limit + this->buffer);
}
void
@ -32,11 +29,10 @@ public:
{
std::lock_guard<std::mutex> lock(this->mutex);
this->vector = new std::vector<T>(this->limit + this->buffer);
this->vectorPtr = std::shared_ptr<std::vector<T>>(this->vector);
this->vector = std::make_shared<std::vector<T>>();
this->vector->reserve(this->limit + this->buffer);
this->offset = 0;
this->length = 0;
}
// return true if an item was deleted
@ -46,39 +42,36 @@ public:
{
std::lock_guard<std::mutex> lock(this->mutex);
if (this->length == this->limit) {
if (this->vector->size() >= this->limit) {
// vector is full
if (this->offset == this->buffer) {
deleted = this->vector->at(this->offset);
// create new vector
auto *vector = new std::vector<T>(this->limit + this->buffer);
auto newVector = std::make_shared<std::vector<T>>();
newVector->reserve(this->limit + this->buffer);
for (int i = 0; i < this->limit; i++) {
vector->at(i) = this->vector->at(i + this->offset);
for (unsigned int i = 0; i < this->limit - 1; i++) {
newVector->push_back(this->vector->at(i + this->offset));
}
vector->at(limit - 1) = item;
newVector->push_back(item);
this->offset = 0;
this->vector = vector;
this->vectorPtr = std::shared_ptr<std::vector<T>>(vector);
this->vector = newVector;
return true;
} else {
// append item and remove first
deleted = this->vector->at(this->offset);
this->vector->at(this->length + this->offset) = item;
//append item and increment offset("deleting" first element)
this->vector->push_back(item);
this->offset++;
return true;
}
} else {
// append item
this->vector->at(this->length) = item;
this->length++;
this->vector->push_back(item);
return false;
}
@ -87,21 +80,23 @@ public:
messages::LimitedQueueSnapshot<T>
getSnapshot()
{
std::lock_guard<std::mutex> lock(mutex);
std::lock_guard<std::mutex> lock(this->mutex);
return LimitedQueueSnapshot<T>(vectorPtr, offset, length);
if(vector->size() < limit) {
return LimitedQueueSnapshot<T>(this->vector, this->offset, this->vector->size());
} else {
return LimitedQueueSnapshot<T>(this->vector, this->offset, this->limit);
}
}
private:
std::vector<T> *vector;
std::shared_ptr<std::vector<T>> vectorPtr;
std::shared_ptr<std::vector<T>> vector;
std::mutex mutex;
int offset;
int length;
int limit;
int buffer;
unsigned int offset;
unsigned int limit;
unsigned int buffer;
};
} // namespace messages

View file

@ -1,7 +1,6 @@
#ifndef LIMITEDQUEUESNAPSHOT_H
#define LIMITEDQUEUESNAPSHOT_H
#include <cassert>
#include <memory>
#include <vector>
@ -14,8 +13,7 @@ class LimitedQueueSnapshot
public:
LimitedQueueSnapshot(std::shared_ptr<std::vector<T>> ptr, int offset,
int length)
: vectorPtr(ptr)
, vector(ptr.get())
: vector(ptr)
, offset(offset)
, length(length)
{
@ -24,20 +22,16 @@ public:
int
getLength()
{
return length;
return this->length;
}
T const &operator[](int index) const
{
// assert(index >= 0);
// assert(index < length);
return vector->at(index + offset);
return this->vector->at(index + this->offset);
}
private:
std::shared_ptr<std::vector<T>> vectorPtr;
std::vector<T> *vector;
std::shared_ptr<std::vector<T>> vector;
int offset;
int length;

View file

@ -24,19 +24,19 @@ public:
Link(Type getType, const QString &getValue);
bool
getIsValid()
getIsValid() const
{
return type == None;
}
Type
getType()
getType() const
{
return type;
}
const QString &
getValue()
getValue() const
{
return value;
}

View file

@ -10,6 +10,7 @@
#include "resources.h"
#include "settings.h"
#include <QObjectUserData>
#include <QStringList>
#include <ctime>
#include <list>
@ -228,7 +229,7 @@ Message::Message(const IrcPrivateMessage &ircMessage, Channel &channel,
end > ircMessage.content().length())
continue;
QString name = ircMessage.content().mid(start, end - start);
QString name = ircMessage.content().mid(start, end - start + 1);
twitchEmotes.push_back(std::pair<long int, LazyLoadedImage *>(
start, Emotes::getTwitchEmoteById(name, id)));

View file

@ -36,11 +36,13 @@ MessageRef::layout(int width, bool enableEmoteMargins)
bool recalculateImages =
this->emoteGeneration != Emotes::getGeneration();
bool recalculateText = this->fontGeneration != Fonts::getGeneration();
bool newWordTypes =
this->currentWordTypes != Settings::getInstance().getWordTypeMask();
qreal emoteScale = settings.emoteScale.get();
bool scaleEmotesByLineHeight = settings.scaleEmotesByLineHeight.get();
if (recalculateImages || recalculateText) {
if (recalculateImages || recalculateText || newWordTypes) {
this->emoteGeneration = Emotes::getGeneration();
this->fontGeneration = Fonts::getGeneration();
@ -72,6 +74,10 @@ MessageRef::layout(int width, bool enableEmoteMargins)
}
}
}
if (newWordTypes) {
this->currentWordTypes = Settings::getInstance().getWordTypeMask();
}
}
if (!redraw) {
@ -85,6 +91,7 @@ MessageRef::layout(int width, bool enableEmoteMargins)
int right = width - MARGIN_RIGHT - MARGIN_LEFT;
int lineNumber = 0;
int lineStart = 0;
int lineHeight = 0;
bool first = true;
@ -139,22 +146,23 @@ MessageRef::layout(int width, bool enableEmoteMargins)
this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y,
width, word.getHeight(),
mid, mid));
lineNumber, mid, mid));
y += metrics.height();
start = i - 1;
width = 0;
lineNumber++;
}
}
QString mid(text.mid(start));
width = metrics.width(mid);
this->wordParts.push_back(WordPart(word, MARGIN_LEFT,
y - word.getHeight(), width,
word.getHeight(), mid, mid));
this->wordParts.push_back(
WordPart(word, MARGIN_LEFT, y - word.getHeight(), width,
word.getHeight(), lineNumber, mid, mid));
x = width + MARGIN_LEFT + spaceWidth;
lineHeight = word.getHeight();
@ -164,8 +172,8 @@ MessageRef::layout(int width, bool enableEmoteMargins)
first = false;
} else if (first || x + word.getWidth() + xOffset <= right) {
// fits in the line
this->wordParts.push_back(
WordPart(word, x, y - word.getHeight(), word.getCopyText()));
this->wordParts.push_back(WordPart(word, x, y - word.getHeight(),
lineNumber, word.getCopyText()));
x += word.getWidth() + xOffset;
x += spaceWidth;
@ -179,8 +187,9 @@ MessageRef::layout(int width, bool enableEmoteMargins)
y += lineHeight;
this->wordParts.push_back(WordPart(
word, MARGIN_LEFT, y - word.getHeight(), word.getCopyText()));
this->wordParts.push_back(WordPart(word, MARGIN_LEFT,
y - word.getHeight(), lineNumber,
word.getCopyText()));
lineStart = this->wordParts.size() - 1;
@ -188,6 +197,8 @@ MessageRef::layout(int width, bool enableEmoteMargins)
x = word.getWidth() + MARGIN_LEFT;
x += spaceWidth;
lineNumber++;
}
}
@ -216,5 +227,112 @@ MessageRef::alignWordParts(int lineStart, int lineHeight)
wordPart2.setY(wordPart2.getY() + lineHeight);
}
}
bool
MessageRef::tryGetWordPart(QPoint point, messages::Word &word)
{
for (messages::WordPart &wordPart : this->wordParts) {
if (wordPart.getRect().contains(point)) {
word = wordPart.getWord();
return true;
}
}
return false;
}
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 (int i = 0; i < this->wordParts.size(); i++) {
WordPart &part = this->wordParts[i];
// return if curser under the word
if (position.y() >= part.getBottom()) {
break;
}
if (part.getLineNumber() != lineNumber) {
lineStart = i;
lineNumber = part.getLineNumber();
}
lineEnd = i;
}
// 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()) {
index += part.getWord().isImage() ? 2 : part.getText().length() + 1;
continue;
}
// cursor is over the word part
if (part.getWord().isImage()) {
index++;
} else {
auto text = part.getWord().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().width(text, j + 1);
}
}
break;
}
return index;
// go through all the wordparts
// for (int i = 0; i < this->wordParts; i < this->wordParts.size()) {
// WordPart &part = this->wordParts[i];
// // return if curser under the word
// if (position.y() >= part.getBottom()) {
// break;
// }
// // increment index and continue if the curser x is bigger than the
// words
// // right edge
// if (position.x() > part.getRight()) {
// index += part.getWord().isImage() ? 2 +
// part.getText().length() + 1;
// continue;
// }
// }
}
}
}

View file

@ -37,6 +37,10 @@ public:
std::shared_ptr<QPixmap> buffer = nullptr;
bool updateBuffer = false;
bool tryGetWordPart(QPoint point, messages::Word &word);
int getSelectionIndex(QPoint position);
private:
Message *message;
std::shared_ptr<Message> messagePtr;
@ -48,6 +52,7 @@ private:
int currentLayoutWidth = -1;
int fontGeneration = -1;
int emoteGeneration = -1;
Word::Type currentWordTypes = Word::None;
void alignWordParts(int lineStart, int lineHeight);
};

View file

@ -65,6 +65,9 @@ public:
ButtonTimeout
};
Word()
{
}
explicit Word(LazyLoadedImage *image, Type getType, const QString &copytext,
const QString &getTooltip, const Link &getLink = Link());
explicit Word(const QString &text, Type getType, const QColor &getColor,
@ -182,7 +185,7 @@ public:
}
std::vector<short> &
getCharacterWidthCache()
getCharacterWidthCache() const
{
return this->characterWidthCache;
}
@ -206,7 +209,7 @@ private:
Fonts::Type font = Fonts::Medium;
Link link;
std::vector<short> characterWidthCache;
mutable std::vector<short> characterWidthCache;
};
} // namespace messages

View file

@ -4,8 +4,8 @@
namespace chatterino {
namespace messages {
WordPart::WordPart(Word &word, int x, int y, const QString &copyText,
bool allowTrailingSpace)
WordPart::WordPart(Word &word, int x, int y, int lineNumber,
const QString &copyText, bool allowTrailingSpace)
: m_word(word)
, copyText(copyText)
, text(word.isText() ? m_word.getText() : QString())
@ -13,13 +13,14 @@ WordPart::WordPart(Word &word, int x, int y, const QString &copyText,
, y(y)
, width(word.getWidth())
, height(word.getHeight())
, lineNumber(lineNumber)
, _trailingSpace(word.hasTrailingSpace() & allowTrailingSpace)
{
}
WordPart::WordPart(Word &word, int x, int y, int width, int height,
const QString &copyText, const QString &customText,
bool allowTrailingSpace)
int lineNumber, const QString &copyText,
const QString &customText, bool allowTrailingSpace)
: m_word(word)
, copyText(copyText)
, text(customText)
@ -27,6 +28,7 @@ WordPart::WordPart(Word &word, int x, int y, int width, int height,
, y(y)
, width(width)
, height(height)
, lineNumber(lineNumber)
, _trailingSpace(word.hasTrailingSpace() & allowTrailingSpace)
{
}

View file

@ -12,12 +12,12 @@ class Word;
class WordPart
{
public:
WordPart(Word &getWord, int getX, int getY, const QString &getCopyText,
bool allowTrailingSpace = true);
WordPart(Word &getWord, int getX, int getY, int lineNumber,
const QString &getCopyText, bool allowTrailingSpace = true);
WordPart(Word &getWord, int getX, int getY, int getWidth, int getHeight,
const QString &getCopyText, const QString &customText,
bool allowTrailingSpace = true);
int lineNumber, const QString &getCopyText,
const QString &customText, bool allowTrailingSpace = true);
const Word &
getWord() const
@ -98,6 +98,12 @@ public:
return this->text;
}
int
getLineNumber()
{
return this->lineNumber;
}
private:
Word &m_word;
@ -109,6 +115,8 @@ private:
int width;
int height;
int lineNumber;
bool _trailingSpace;
};
}

View file

@ -8,7 +8,9 @@
#include <math.h>
#include <QDebug>
#include <QGraphicsBlurEffect>
#include <QPainter>
#include <chrono>
#include <functional>
namespace chatterino {
@ -18,8 +20,11 @@ ChatWidgetView::ChatWidgetView(ChatWidget *parent)
: QWidget()
, chatWidget(parent)
, scrollbar(this)
, onlyUpdateEmotes(false)
{
this->setAttribute(Qt::WA_OpaquePaintEvent);
this->scrollbar.setSmallChange(5);
this->setMouseTracking(true);
QObject::connect(&Settings::getInstance(), &Settings::wordTypeMaskChanged,
this, &ChatWidgetView::wordTypeMaskChanged);
@ -49,33 +54,26 @@ ChatWidgetView::layoutMessages()
int start = this->scrollbar.getCurrentValue();
if (messages.getLength() <= start) {
// The scrollbar wants to show more values than we can offer
// layout the visible messages in the view
if (messages.getLength() > start) {
int y = -(messages[start].get()->getHeight() *
(fmod(this->scrollbar.getCurrentValue(), 1)));
// just return for now
return false;
for (int i = start; i < messages.getLength(); ++i) {
auto messagePtr = messages[i];
auto message = messagePtr.get();
redraw |= message->layout(this->width(), true);
// Lower start value to the last message
// start = messages.getLength() - 1;
}
y += message->getHeight();
int y = -(messages[start].get()->getHeight() *
(fmod(this->scrollbar.getCurrentValue(), 1)));
for (int i = start; i < messages.getLength(); ++i) {
auto messagePtr = messages[i];
auto message = messagePtr.get();
redraw |= message->layout(this->width(), true);
y += message->getHeight();
if (y >= height()) {
break;
if (y >= height()) {
break;
}
}
}
// layout the messages at the bottom to determine the scrollbar thumb size
int h = this->height() - 8;
for (int i = messages.getLength() - 1; i >= 0; i--) {
@ -97,6 +95,10 @@ ChatWidgetView::layoutMessages()
this->scrollbar.setVisible(showScrollbar);
if (!showScrollbar) {
this->scrollbar.setDesiredValue(0);
}
this->scrollbar.setMaximum(messages.getLength());
return redraw;
@ -109,10 +111,12 @@ ChatWidgetView::resizeEvent(QResizeEvent *)
this->scrollbar.move(width() - this->scrollbar.width(), 0);
layoutMessages();
update();
}
void
ChatWidgetView::paintEvent(QPaintEvent *)
ChatWidgetView::paintEvent(QPaintEvent *event)
{
QPainter _painter(this);
@ -120,6 +124,24 @@ ChatWidgetView::paintEvent(QPaintEvent *)
ColorScheme &scheme = ColorScheme::getInstance();
// only update gif emotes
if (onlyUpdateEmotes) {
onlyUpdateEmotes = false;
for (GifEmoteData &item : this->gifEmotes) {
_painter.fillRect(item.rect, scheme.ChatBackground);
_painter.drawPixmap(item.rect, *item.image->getPixmap());
}
return;
}
// update all messages
gifEmotes.clear();
_painter.fillRect(rect(), scheme.ChatBackground);
// code for tesing colors
/*
QColor color;
@ -178,16 +200,13 @@ ChatWidgetView::paintEvent(QPaintEvent *)
updateBuffer = true;
}
// update messages that have been changed
if (updateBuffer) {
QPainter painter(buffer);
painter.fillRect(buffer->rect(), scheme.ChatBackground);
for (messages::WordPart const &wordPart :
messageRef->getWordParts()) {
painter.setPen(QColor(255, 0, 0));
painter.drawRect(wordPart.getX(), wordPart.getY(),
wordPart.getWidth(), wordPart.getHeight());
// image
if (wordPart.getWord().isImage()) {
messages::LazyLoadedImage &lli =
@ -221,6 +240,24 @@ ChatWidgetView::paintEvent(QPaintEvent *)
messageRef->updateBuffer = false;
}
// get gif emotes
for (messages::WordPart const &wordPart : messageRef->getWordParts()) {
if (wordPart.getWord().isImage()) {
messages::LazyLoadedImage &lli = wordPart.getWord().getImage();
if (lli.getAnimated()) {
GifEmoteData data;
data.image = &lli;
QRect rect(wordPart.getX(), wordPart.getY() + y,
wordPart.getWidth(), wordPart.getHeight());
data.rect = rect;
gifEmotes.push_back(data);
}
}
}
messageRef->buffer = bufferPtr;
_painter.drawPixmap(0, y, *buffer);
@ -231,16 +268,89 @@ ChatWidgetView::paintEvent(QPaintEvent *)
break;
}
}
for (GifEmoteData &item : this->gifEmotes) {
_painter.fillRect(item.rect, scheme.ChatBackground);
_painter.drawPixmap(item.rect, *item.image->getPixmap());
}
}
void
ChatWidgetView::wheelEvent(QWheelEvent *event)
{
this->scrollbar.setDesiredValue(
this->scrollbar.getDesiredValue() -
event->delta() / 10.0 *
Settings::getInstance().mouseScrollMultiplier.get(),
true);
if (this->scrollbar.isVisible()) {
this->scrollbar.setDesiredValue(
this->scrollbar.getDesiredValue() -
event->delta() / 10.0 *
Settings::getInstance().mouseScrollMultiplier.get(),
true);
}
}
void
ChatWidgetView::mouseMoveEvent(QMouseEvent *event)
{
std::shared_ptr<messages::MessageRef> message;
QPoint relativePos;
if (!tryGetMessageAt(event->pos(), message, relativePos)) {
this->setCursor(Qt::ArrowCursor);
return;
}
auto _message = message->getMessage();
auto user = _message->getUserName();
messages::Word hoverWord;
if (!message->tryGetWordPart(relativePos, hoverWord)) {
this->setCursor(Qt::ArrowCursor);
return;
}
int index = message->getSelectionIndex(relativePos);
qDebug() << index;
if (hoverWord.getLink().getIsValid()) {
this->setCursor(Qt::PointingHandCursor);
qDebug() << hoverWord.getLink().getValue();
} else {
this->setCursor(Qt::ArrowCursor);
}
}
bool
ChatWidgetView::tryGetMessageAt(QPoint p,
std::shared_ptr<messages::MessageRef> &_message,
QPoint &relativePos)
{
auto messages = chatWidget->getMessagesSnapshot();
int start = this->scrollbar.getCurrentValue();
if (start >= messages.getLength()) {
return false;
}
int y = -(messages[start].get()->getHeight() *
(fmod(this->scrollbar.getCurrentValue(), 1))) +
12;
for (int i = start; i < messages.getLength(); ++i) {
auto message = messages[i];
y += message->getHeight();
if (p.y() < y) {
relativePos = QPoint(p.x(), y - p.y());
_message = message;
return true;
}
}
return false;
}
}
}

View file

@ -2,6 +2,7 @@
#define CHATVIEW_H
#include "channel.h"
#include "messages/lazyloadedimage.h"
#include "messages/messageref.h"
#include "messages/word.h"
#include "widgets/scrollbar.h"
@ -25,24 +26,44 @@ public:
bool layoutMessages();
void
updateGifEmotes()
{
this->onlyUpdateEmotes = true;
this->update();
}
protected:
void resizeEvent(QResizeEvent *);
void paintEvent(QPaintEvent *);
void wheelEvent(QWheelEvent *event);
void mouseMoveEvent(QMouseEvent *event);
bool tryGetMessageAt(QPoint p,
std::shared_ptr<messages::MessageRef> &message,
QPoint &relativePos);
private:
struct GifEmoteData {
messages::LazyLoadedImage *image;
QRect rect;
};
std::vector<GifEmoteData> gifEmotes;
ChatWidget *chatWidget;
ScrollBar scrollbar;
bool onlyUpdateEmotes;
private slots:
void
wordTypeMaskChanged()
{
if (layoutMessages()) {
update();
}
layoutMessages();
update();
}
};
}

View file

@ -44,7 +44,7 @@ MainWindow::layoutVisibleChatWidgets(Channel *channel)
if (channel == NULL || channel == widget->getChannel().get()) {
if (widget->getView().layoutMessages()) {
widget->update();
widget->getView().update();
}
}
}
@ -66,11 +66,29 @@ MainWindow::repaintVisibleChatWidgets(Channel *channel)
if (channel == NULL || channel == widget->getChannel().get()) {
widget->getView().layoutMessages();
widget->update();
widget->getView().update();
}
}
}
void
MainWindow::repaintGifEmotes()
{
auto *page = notebook.getSelectedPage();
if (page == NULL) {
return;
}
const std::vector<ChatWidget *> &widgets = page->getChatWidgets();
for (auto it = widgets.begin(); it != widgets.end(); ++it) {
ChatWidget *widget = *it;
widget->getView().updateGifEmotes();
}
}
void
MainWindow::load(const boost::property_tree::ptree &tree)
{

View file

@ -20,6 +20,7 @@ public:
void layoutVisibleChatWidgets(Channel *channel = NULL);
void repaintVisibleChatWidgets(Channel *channel = NULL);
void repaintGifEmotes();
void load(const boost::property_tree::ptree &tree);
boost::property_tree::ptree save();

View file

@ -152,6 +152,7 @@ Notebook::performLayout(bool animated)
if (Settings::getInstance().hideUserButton.get()) {
userButton.hide();
} else {
userButton.move(x, 0);
userButton.show();
x += 24;
}

View file

@ -38,6 +38,14 @@ Windows::repaintVisibleChatWidgets(Channel *channel)
}
}
void
Windows::repaintGifEmotes()
{
if (Windows::mainWindow != nullptr) {
Windows::mainWindow->repaintGifEmotes();
}
}
void
Windows::updateAll()
{

View file

@ -12,6 +12,7 @@ class Windows
public:
static void layoutVisibleChatWidgets(Channel *channel = NULL);
static void repaintVisibleChatWidgets(Channel *channel = NULL);
static void repaintGifEmotes();
static void updateAll();
static widgets::MainWindow &