simplified Image

This commit is contained in:
fourtf 2018-08-10 18:56:17 +02:00
parent 6344fa6b23
commit edfae49cc9
22 changed files with 146 additions and 314 deletions

View file

@ -6,7 +6,7 @@
message(----) message(----)
QT += widgets core gui network multimedia svg QT += widgets core gui network multimedia svg concurrent
CONFIG += communi CONFIG += communi
COMMUNI += core model util COMMUNI += core model util
CONFIG += c++14 CONFIG += c++14
@ -105,7 +105,6 @@ SOURCES += \
src/Application.cpp \ src/Application.cpp \
src/common/Channel.cpp \ src/common/Channel.cpp \
src/common/CompletionModel.cpp \ src/common/CompletionModel.cpp \
src/common/Emotemap.cpp \
src/common/NetworkData.cpp \ src/common/NetworkData.cpp \
src/common/NetworkManager.cpp \ src/common/NetworkManager.cpp \
src/common/NetworkRequest.cpp \ src/common/NetworkRequest.cpp \
@ -258,7 +257,6 @@ HEADERS += \
src/common/Channel.hpp \ src/common/Channel.hpp \
src/common/Common.hpp \ src/common/Common.hpp \
src/common/CompletionModel.hpp \ src/common/CompletionModel.hpp \
src/common/Emotemap.hpp \
src/common/FlagsEnum.hpp \ src/common/FlagsEnum.hpp \
src/common/LockedObject.hpp \ src/common/LockedObject.hpp \
src/common/MutexValue.hpp \ src/common/MutexValue.hpp \

View file

@ -1,46 +0,0 @@
#include "Emotemap.hpp"
#include "Application.hpp"
#include "singletons/Settings.hpp"
namespace chatterino {
// EmoteData::EmoteData(Image *image)
// : image1x(image)
//{
//}
//// Emotes must have a 1x image to be valid
// bool EmoteData::isValid() const
//{
// return this->image1x != nullptr;
//}
// Image *EmoteData::getImage(float scale) const
//{
// int quality = getApp()->settings->preferredEmoteQuality;
// if (quality == 0) {
// scale *= getApp()->settings->emoteScale.getValue();
// quality = [&] {
// if (scale <= 1)
// return 1;
// if (scale <= 2)
// return 2;
// return 3;
// }();
// }
// Image *_image;
// if (quality == 3 && this->image3x != nullptr) {
// _image = this->image3x;
// } else if (quality >= 2 && this->image2x != nullptr) {
// _image = this->image2x;
// } else {
// _image = this->image1x;
// }
// return _image;
//}
} // namespace chatterino

View file

@ -1,27 +0,0 @@
#pragma once
#include "messages/Image.hpp"
#include "util/ConcurrentMap.hpp"
namespace chatterino {
// struct EmoteData {
// EmoteData() = default;
// EmoteData(Image *image);
// // Emotes must have a 1x image to be valid
// bool isValid() const;
// Image *getImage(float scale) const;
// // Link to the emote page i.e.
// https://www.frankerfacez.com/emoticon/144722-pajaCringe QString pageLink;
// Image *image1x = nullptr;
// Image *image2x = nullptr;
// Image *image3x = nullptr;
//};
// using EmoteMap = ConcurrentMap<QString, EmoteData>;
} // namespace chatterino

View file

@ -4,19 +4,17 @@
namespace chatterino { namespace chatterino {
// = std::enable_if<std::is_enum<T>::value>::type
template <typename T, typename Q = typename std::underlying_type<T>::type> template <typename T, typename Q = typename std::underlying_type<T>::type>
class FlagsEnum class FlagsEnum
{ {
public: public:
FlagsEnum() FlagsEnum()
: value(static_cast<T>(0)) : value_(static_cast<T>(0))
{ {
} }
FlagsEnum(T value) FlagsEnum(T value)
: value(value) : value_(value)
{ {
} }
@ -29,22 +27,22 @@ public:
bool operator==(const FlagsEnum<T> &other) bool operator==(const FlagsEnum<T> &other)
{ {
return this->value == other.value; return this->value_ == other.value_;
} }
bool operator!=(const FlagsEnum &other) bool operator!=(const FlagsEnum &other)
{ {
return this->value != other.value; return this->value_ != other.value_;
} }
void set(T flag) void set(T flag)
{ {
reinterpret_cast<Q &>(this->value) |= static_cast<Q>(flag); reinterpret_cast<Q &>(this->value_) |= static_cast<Q>(flag);
} }
void unset(T flag) void unset(T flag)
{ {
reinterpret_cast<Q &>(this->value) &= ~static_cast<Q>(flag); reinterpret_cast<Q &>(this->value_) &= ~static_cast<Q>(flag);
} }
void set(T flag, bool value) void set(T flag, bool value)
@ -57,33 +55,17 @@ public:
bool has(T flag) const bool has(T flag) const
{ {
return static_cast<Q>(this->value) & static_cast<Q>(flag); return static_cast<Q>(this->value_) & static_cast<Q>(flag);
} }
// bool hasAny(std::initializer_list<T> flags) const
//{
// for (auto flag : flags) {
// if (this->has(flag)) return true;
// }
// return false;
//}
bool hasAny(FlagsEnum flags) const bool hasAny(FlagsEnum flags) const
{ {
return static_cast<Q>(this->value) & static_cast<Q>(flags.value); return static_cast<Q>(this->value_) & static_cast<Q>(flags.value_);
} }
// bool hasAll(std::initializer_list<T> flags) const
//{
// for (auto flag : flags) {
// if (!this->has(flag)) return false;
// }
// return true;
//}
bool hasAll(FlagsEnum<T> flags) const bool hasAll(FlagsEnum<T> flags) const
{ {
return (static_cast<Q>(this->value) & static_cast<Q>(flags.value)) && return (static_cast<Q>(this->value_) & static_cast<Q>(flags.value_)) &&
static_cast<Q>(flags->value); static_cast<Q>(flags->value);
} }
@ -93,7 +75,7 @@ public:
} }
private: private:
T value; T value_{};
}; };
} // namespace chatterino } // namespace chatterino

View file

@ -19,6 +19,7 @@ struct NetworkData {
QNetworkRequest request_; QNetworkRequest request_;
const QObject *caller_ = nullptr; const QObject *caller_ = nullptr;
bool useQuickLoadCache_{}; bool useQuickLoadCache_{};
bool executeConcurrently{};
NetworkReplyCreatedCallback onReplyCreated_; NetworkReplyCreatedCallback onReplyCreated_;
NetworkErrorCallback onError_; NetworkErrorCallback onError_;

View file

@ -8,6 +8,7 @@
#include "util/DebugCount.hpp" #include "util/DebugCount.hpp"
#include <QFile> #include <QFile>
#include <QtConcurrent>
#include <cassert> #include <cassert>
namespace chatterino { namespace chatterino {
@ -80,6 +81,11 @@ void NetworkRequest::setTimeout(int ms)
this->timer->timeoutMS_ = ms; this->timer->timeoutMS_ = ms;
} }
void NetworkRequest::setExecuteConcurrently(bool value)
{
this->data->executeConcurrently = value;
}
void NetworkRequest::makeAuthorizedV5(const QString &clientID, void NetworkRequest::makeAuthorizedV5(const QString &clientID,
const QString &oauthToken) const QString &oauthToken)
{ {
@ -228,7 +234,16 @@ void NetworkRequest::doRequest()
NetworkResult result(bytes); NetworkResult result(bytes);
DebugCount::increase("http request success"); DebugCount::increase("http request success");
Log("starting {}", data->request_.url().toString());
if (data->onSuccess_) {
if (data->executeConcurrently)
QtConcurrent::run(
[onSuccess = std::move(data->onSuccess_),
result = std::move(result)] { onSuccess(result); });
else
data->onSuccess_(result); data->onSuccess_(result);
}
Log("finished {}", data->request_.url().toString());
reply->deleteLater(); reply->deleteLater();
}; };

View file

@ -27,19 +27,15 @@ class NetworkRequest
bool executed_ = false; bool executed_ = false;
public: public:
NetworkRequest() = delete;
NetworkRequest(const NetworkRequest &other) = delete;
NetworkRequest &operator=(const NetworkRequest &other) = delete;
NetworkRequest(NetworkRequest &&other) = default;
NetworkRequest &operator=(NetworkRequest &&other) = default;
explicit NetworkRequest( explicit NetworkRequest(
const std::string &url, const std::string &url,
NetworkRequestType requestType = NetworkRequestType::Get); NetworkRequestType requestType = NetworkRequestType::Get);
explicit NetworkRequest( explicit NetworkRequest(
QUrl url, NetworkRequestType requestType = NetworkRequestType::Get); QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
NetworkRequest(NetworkRequest &&other) = default;
NetworkRequest &operator=(NetworkRequest &&other) = default;
~NetworkRequest(); ~NetworkRequest();
void setRequestType(NetworkRequestType newRequestType); void setRequestType(NetworkRequestType newRequestType);
@ -55,6 +51,7 @@ public:
void setRawHeader(const char *headerName, const QByteArray &value); void setRawHeader(const char *headerName, const QByteArray &value);
void setRawHeader(const char *headerName, const QString &value); void setRawHeader(const char *headerName, const QString &value);
void setTimeout(int ms); void setTimeout(int ms);
void setExecuteConcurrently(bool value);
void makeAuthorizedV5(const QString &clientID, void makeAuthorizedV5(const QString &clientID,
const QString &oauthToken = QString()); const QString &oauthToken = QString());

View file

@ -60,8 +60,7 @@ ModerationAction::ModerationAction(const QString &action)
// str); // str);
// } // }
} else if (action.startsWith("/ban ")) { } else if (action.startsWith("/ban ")) {
this->image_ = this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban);
Image::fromNonOwningPixmap(&getApp()->resources->buttons.ban);
} else { } else {
QString xD = action; QString xD = action;

View file

@ -37,29 +37,4 @@ using EmoteIdMap = std::unordered_map<EmoteId, EmotePtr>;
using WeakEmoteMap = std::unordered_map<EmoteName, std::weak_ptr<const Emote>>; using WeakEmoteMap = std::unordered_map<EmoteName, std::weak_ptr<const Emote>>;
using WeakEmoteIdMap = std::unordered_map<EmoteId, std::weak_ptr<const Emote>>; using WeakEmoteIdMap = std::unordered_map<EmoteId, std::weak_ptr<const Emote>>;
// struct EmoteData2 {
// EmoteName name;
// ImageSet images;
// Tooltip tooltip;
// Url homePage;
//};
//
// class Emote
//{
// public:
// Emote(EmoteData2 &&data);
// Emote(const EmoteData2 &data);
//
// const Url &getHomePage() const;
// const EmoteName &getName() const;
// const Tooltip &getTooltip() const;
// const ImageSet &getImages() const;
// const QString &getCopyString() const;
// bool operator==(const Emote &other) const;
// bool operator!=(const Emote &other) const;
//
// private:
// EmoteData2 data_;
//};
} // namespace chatterino } // namespace chatterino

View file

@ -21,22 +21,14 @@
namespace chatterino { namespace chatterino {
namespace { namespace {
const QPixmap *getPixmap(const Pixmap &pixmap)
{
if (pixmap.which() == 0)
return boost::get<const QPixmap *>(pixmap);
else
return boost::get<std::unique_ptr<QPixmap>>(pixmap).get();
}
// Frames // Frames
Frames::Frames() Frames::Frames()
{ {
DebugCount::increase("images"); DebugCount::increase("images");
} }
Frames::Frames(std::vector<Frame> &&frames) Frames::Frames(const QVector<Frame<QPixmap>> &frames)
: items_(std::move(frames)) : items_(frames)
{ {
DebugCount::increase("images"); DebugCount::increase("images");
if (this->animated()) DebugCount::increase("animated images"); if (this->animated()) DebugCount::increase("animated images");
@ -68,22 +60,22 @@ bool Frames::animated() const
return this->items_.size() > 1; return this->items_.size() > 1;
} }
const QPixmap *Frames::current() const boost::optional<QPixmap> Frames::current() const
{ {
if (this->items_.size() == 0) return nullptr; if (this->items_.size() == 0) return boost::none;
return getPixmap(this->items_[this->index_].pixmap); return this->items_[this->index_].image;
} }
const QPixmap *Frames::first() const boost::optional<QPixmap> Frames::first() const
{ {
if (this->items_.size() == 0) return nullptr; if (this->items_.size() == 0) return boost::none;
return getPixmap(this->items_.front().pixmap); return this->items_.front().image;
} }
// functions // functions
std::vector<ParseFrame> readFrames(QImageReader &reader, const Url &url) QVector<Frame<QImage>> readFrames(QImageReader &reader, const Url &url)
{ {
std::vector<ParseFrame> frames; QVector<Frame<QImage>> frames;
if (reader.imageCount() == 0) { if (reader.imageCount() == 0) {
Log("Error while reading image {}: '{}'", url.string, Log("Error while reading image {}: '{}'", url.string,
@ -97,7 +89,7 @@ std::vector<ParseFrame> readFrames(QImageReader &reader, const Url &url)
QPixmap::fromImage(image); QPixmap::fromImage(image);
int duration = std::max(20, reader.nextImageDelay()); int duration = std::max(20, reader.nextImageDelay());
frames.push_back(ParseFrame{image, duration}); frames.push_back(Frame<QImage>{image, duration});
} }
} }
@ -109,36 +101,22 @@ std::vector<ParseFrame> readFrames(QImageReader &reader, const Url &url)
return frames; return frames;
} }
void queueLoadedEvent()
{
static auto eventQueued = false;
if (!eventQueued) {
eventQueued = true;
QTimer::singleShot(250, [] {
getApp()->windows->incGeneration();
getApp()->windows->layoutChannelViews();
eventQueued = false;
});
}
}
// parsed // parsed
template <typename Assign> template <typename Assign>
void asd(std::queue<std::pair<Assign, std::vector<Frame>>> &queued, void assignDelayed(
std::queue<std::pair<Assign, QVector<Frame<QPixmap>>>> &queued,
std::mutex &mutex, std::atomic_bool &loadedEventQueued) std::mutex &mutex, std::atomic_bool &loadedEventQueued)
{ {
std::lock_guard<std::mutex> lock(mutex); std::lock_guard<std::mutex> lock(mutex);
int i = 0; int i = 0;
while (!queued.empty()) { while (!queued.empty()) {
queued.front().first(std::move(queued.front().second)); queued.front().first(queued.front().second);
queued.pop(); queued.pop();
if (++i > 50) { if (++i > 50) {
QTimer::singleShot(3, QTimer::singleShot(
[&] { asd(queued, mutex, loadedEventQueued); }); 3, [&] { assignDelayed(queued, mutex, loadedEventQueued); });
return; return;
} }
} }
@ -148,34 +126,31 @@ void asd(std::queue<std::pair<Assign, std::vector<Frame>>> &queued,
} }
template <typename Assign> template <typename Assign>
auto makeConvertCallback(std::vector<ParseFrame> parsed, Assign assign) auto makeConvertCallback(const QVector<Frame<QImage>> &parsed, Assign assign)
{ {
return [parsed = std::move(parsed), assign] { return [parsed, assign] {
// BenchmarkGuard guard("convert image");
// convert to pixmap // convert to pixmap
auto frames = std::vector<Frame>(); auto frames = QVector<Frame<QPixmap>>();
std::transform(parsed.begin(), parsed.end(), std::back_inserter(frames), std::transform(parsed.begin(), parsed.end(), std::back_inserter(frames),
[](auto &frame) { [](auto &frame) {
return Frame{std::make_unique<QPixmap>( return Frame<QPixmap>{
QPixmap::fromImage(frame.image)), QPixmap::fromImage(frame.image), frame.duration};
frame.duration};
}); });
// put into stack // put into stack
static std::queue<std::pair<Assign, std::vector<Frame>>> queued; static std::queue<std::pair<Assign, QVector<Frame<QPixmap>>>> queued;
static std::mutex mutex; static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex); std::lock_guard<std::mutex> lock(mutex);
queued.emplace(assign, std::move(frames)); queued.emplace(assign, frames);
static std::atomic_bool loadedEventQueued{false}; static std::atomic_bool loadedEventQueued{false};
if (!loadedEventQueued) { if (!loadedEventQueued) {
loadedEventQueued = true; loadedEventQueued = true;
QTimer::singleShot(100, QTimer::singleShot(
[=] { asd(queued, mutex, loadedEventQueued); }); 100, [=] { assignDelayed(queued, mutex, loadedEventQueued); });
} }
}; };
} }
@ -200,12 +175,7 @@ ImagePtr Image::fromUrl(const Url &url, qreal scale)
return shared; return shared;
} }
ImagePtr Image::fromOwningPixmap(std::unique_ptr<QPixmap> pixmap, qreal scale) ImagePtr Image::fromPixmap(const QPixmap &pixmap, qreal scale)
{
return ImagePtr(new Image(std::move(pixmap), scale));
}
ImagePtr Image::fromNonOwningPixmap(QPixmap *pixmap, qreal scale)
{ {
return ImagePtr(new Image(pixmap, scale)); return ImagePtr(new Image(pixmap, scale));
} }
@ -228,20 +198,10 @@ Image::Image(const Url &url, qreal scale)
{ {
} }
Image::Image(std::unique_ptr<QPixmap> owning, qreal scale) Image::Image(const QPixmap &pixmap, qreal scale)
: scale_(scale) : scale_(scale)
, frames_(QVector<Frame<QPixmap>>{Frame<QPixmap>{pixmap}})
{ {
std::vector<Frame> vec;
vec.push_back(Frame{std::move(owning)});
this->frames_ = std::move(vec);
}
Image::Image(QPixmap *nonOwning, qreal scale)
: scale_(scale)
{
std::vector<Frame> vec;
vec.push_back(Frame{nonOwning});
this->frames_ = std::move(vec);
} }
const Url &Image::url() const const Url &Image::url() const
@ -249,7 +209,7 @@ const Url &Image::url() const
return this->url_; return this->url_;
} }
const QPixmap *Image::pixmap() const boost::optional<QPixmap> Image::pixmap() const
{ {
assertInGuiThread(); assertInGuiThread();
@ -266,7 +226,7 @@ qreal Image::scale() const
return this->scale_; return this->scale_;
} }
bool Image::empty() const bool Image::isEmpty() const
{ {
return this->empty_; return this->empty_;
} }
@ -301,23 +261,14 @@ int Image::height() const
void Image::load() void Image::load()
{ {
NetworkRequest req(this->url().string); NetworkRequest req(this->url().string);
req.setExecuteConcurrently(true);
req.setCaller(&this->object_); req.setCaller(&this->object_);
req.setUseQuickLoadCache(true); req.setUseQuickLoadCache(true);
req.onSuccess([that = this, weak = weakOf(this)](auto result) -> Outcome { req.onSuccess([that = this, weak = weakOf(this)](auto result) -> Outcome {
assertInGuiThread();
auto shared = weak.lock(); auto shared = weak.lock();
if (!shared) return Failure; if (!shared) return Failure;
static auto parseThread = [] { auto data = result.getData();
auto thread = std::make_unique<QThread>();
thread->start();
return thread;
}();
postToThread(
[data = result.getData(), weak, that] {
// BenchmarkGuard guard("parse image");
// const cast since we are only reading from it // const cast since we are only reading from it
QBuffer buffer(const_cast<QByteArray *>(&data)); QBuffer buffer(const_cast<QByteArray *>(&data));
@ -325,13 +276,9 @@ void Image::load()
QImageReader reader(&buffer); QImageReader reader(&buffer);
auto parsed = readFrames(reader, that->url()); auto parsed = readFrames(reader, that->url());
postToThread( postToThread(makeConvertCallback(parsed, [weak](auto frames) {
makeConvertCallback(std::move(parsed), [weak](auto frames) { if (auto shared = weak.lock()) shared->frames_ = frames;
if (auto shared = weak.lock())
shared->frames_ = std::move(frames);
})); }));
},
parseThread.get());
return Success; return Success;
}); });
@ -341,7 +288,7 @@ void Image::load()
bool Image::operator==(const Image &other) const bool Image::operator==(const Image &other) const
{ {
if (this->empty() && other.empty()) return true; if (this->isEmpty() && other.isEmpty()) return true;
if (!this->url_.string.isEmpty() && this->url_ == other.url_) return true; if (!this->url_.string.isEmpty() && this->url_ == other.url_) return true;
if (this->frames_.first() == other.frames_.first()) return true; if (this->frames_.first() == other.frames_.first()) return true;

View file

@ -5,6 +5,7 @@
#include <QPixmap> #include <QPixmap>
#include <QString> #include <QString>
#include <QThread> #include <QThread>
#include <QVector>
#include <atomic> #include <atomic>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <boost/variant.hpp> #include <boost/variant.hpp>
@ -15,31 +16,27 @@
namespace chatterino { namespace chatterino {
namespace { namespace {
using Pixmap = boost::variant<const QPixmap *, std::unique_ptr<QPixmap>>; template <typename Image>
struct Frame { struct Frame {
Pixmap pixmap; Image image;
int duration;
};
struct ParseFrame {
QImage image;
int duration; int duration;
}; };
class Frames class Frames
{ {
public: public:
Frames(); Frames();
Frames(std::vector<Frame> &&frames); Frames(const QVector<Frame<QPixmap>> &frames);
~Frames(); ~Frames();
Frames(Frames &&other) = default; Frames(Frames &&other) = default;
Frames &operator=(Frames &&other) = default; Frames &operator=(Frames &&other) = default;
bool animated() const; bool animated() const;
void advance(); void advance();
const QPixmap *current() const; boost::optional<QPixmap> current() const;
const QPixmap *first() const; boost::optional<QPixmap> first() const;
private: private:
std::vector<Frame> items_; QVector<Frame<QPixmap>> items_;
int index_{0}; int index_{0};
int durationOffset_{0}; int durationOffset_{0};
}; };
@ -52,15 +49,13 @@ class Image : public std::enable_shared_from_this<Image>, boost::noncopyable
{ {
public: public:
static ImagePtr fromUrl(const Url &url, qreal scale = 1); static ImagePtr fromUrl(const Url &url, qreal scale = 1);
static ImagePtr fromOwningPixmap(std::unique_ptr<QPixmap> pixmap, static ImagePtr fromPixmap(const QPixmap &pixmap, qreal scale = 1);
qreal scale = 1);
static ImagePtr fromNonOwningPixmap(QPixmap *pixmap, qreal scale = 1);
static ImagePtr getEmpty(); static ImagePtr getEmpty();
const Url &url() const; const Url &url() const;
const QPixmap *pixmap() const; boost::optional<QPixmap> pixmap() const;
qreal scale() const; qreal scale() const;
bool empty() const; bool isEmpty() const;
int width() const; int width() const;
int height() const; int height() const;
bool animated() const; bool animated() const;
@ -71,8 +66,7 @@ public:
private: private:
Image(); Image();
Image(const Url &url, qreal scale); Image(const Url &url, qreal scale);
Image(std::unique_ptr<QPixmap> owning, qreal scale); Image(const QPixmap &nonOwning, qreal scale);
Image(QPixmap *nonOwning, qreal scale);
void load(); void load();

View file

@ -71,11 +71,11 @@ const ImagePtr &ImageSet::getImage(float scale) const
scale = 1; scale = 1;
} }
if (!this->imageX3_->empty() && quality == 3) { if (!this->imageX3_->isEmpty() && quality == 3) {
return this->imageX3_; return this->imageX3_;
} }
if (!this->imageX2_->empty() && quality == 2) { if (!this->imageX2_->isEmpty() && quality == 2) {
return this->imageX3_; return this->imageX3_;
} }

View file

@ -21,8 +21,6 @@ public:
const ImagePtr &getImage(float scale) const; const ImagePtr &getImage(float scale) const;
ImagePtr getImage(float scale);
bool operator==(const ImageSet &other) const; bool operator==(const ImageSet &other) const;
bool operator!=(const ImageSet &other) const; bool operator!=(const ImageSet &other) const;

View file

@ -1,7 +1,6 @@
#include "messages/MessageElement.hpp" #include "messages/MessageElement.hpp"
#include "Application.hpp" #include "Application.hpp"
#include "common/Emotemap.hpp"
#include "controllers/moderationactions/ModerationActions.hpp" #include "controllers/moderationactions/ModerationActions.hpp"
#include "debug/Benchmark.hpp" #include "debug/Benchmark.hpp"
#include "messages/layouts/MessageLayoutContainer.hpp" #include "messages/layouts/MessageLayoutContainer.hpp"
@ -102,7 +101,7 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
if (flags.hasAny(this->getFlags())) { if (flags.hasAny(this->getFlags())) {
if (flags.has(MessageElementFlag::EmoteImages)) { if (flags.has(MessageElementFlag::EmoteImages)) {
auto image = this->emote_->images.getImage(container.getScale()); auto image = this->emote_->images.getImage(container.getScale());
if (image->empty()) return; if (image->isEmpty()) return;
auto size = QSize(int(container.getScale() * image->width()), auto size = QSize(int(container.getScale() * image->width()),
int(container.getScale() * image->height())); int(container.getScale() * image->height()));

View file

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "common/Emotemap.hpp"
#include "common/FlagsEnum.hpp" #include "common/FlagsEnum.hpp"
#include "messages/Emote.hpp" #include "messages/Emote.hpp"
#include "messages/Image.hpp" #include "messages/Image.hpp"

View file

@ -1,6 +1,5 @@
#pragma once #pragma once
#include "common/Emotemap.hpp"
#include "common/SimpleSignalVector.hpp" #include "common/SimpleSignalVector.hpp"
#include "messages/Emote.hpp" #include "messages/Emote.hpp"
#include "util/ConcurrentMap.hpp" #include "util/ConcurrentMap.hpp"

View file

@ -22,6 +22,32 @@
#include <QTimer> #include <QTimer>
namespace chatterino { namespace chatterino {
namespace {
auto parseRecentMessages(const QJsonObject &jsonRoot, TwitchChannel &channel)
{
QJsonArray jsonMessages = jsonRoot.value("messages").toArray();
std::vector<MessagePtr> messages;
if (jsonMessages.empty()) return messages;
for (const auto jsonMessage : jsonMessages) {
auto content = jsonMessage.toString().toUtf8();
// passing nullptr as the channel makes the message invalid but we don't
// check for that anyways
auto message = Communi::IrcMessage::fromData(content, nullptr);
auto privMsg = dynamic_cast<Communi::IrcPrivateMessage *>(message);
assert(privMsg);
MessageParseArgs args;
TwitchMessageBuilder builder(&channel, privMsg, args);
if (!builder.isIgnored()) {
messages.push_back(builder.build());
}
}
return messages;
}
} // namespace
TwitchChannel::TwitchChannel(const QString &name) TwitchChannel::TwitchChannel(const QString &name)
: Channel(name, Channel::Type::Twitch) : Channel(name, Channel::Type::Twitch)
@ -436,45 +462,23 @@ void TwitchChannel::loadRecentMessages()
NetworkRequest request(genericURL.arg(this->getRoomId())); NetworkRequest request(genericURL.arg(this->getRoomId()));
request.makeAuthorizedV5(getDefaultClientID()); request.makeAuthorizedV5(getDefaultClientID());
request.setCaller(QThread::currentThread()); request.setCaller(QThread::currentThread());
// can't be concurrent right now due to SignalVector
// request.setExecuteConcurrently(true);
request.onSuccess( request.onSuccess([that = this](auto result) -> Outcome {
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome { auto messages = parseRecentMessages(result.parseJson(), *that);
ChannelPtr shared = weak.lock();
if (!shared) return Failure;
return this->parseRecentMessages(result.parseJson()); // postToThread([that, weak = weakOf<Channel>(that),
// messages = std::move(messages)]() mutable {
that->addMessagesAtStart(messages);
// });
return Success;
}); });
request.execute(); request.execute();
} }
Outcome TwitchChannel::parseRecentMessages(const QJsonObject &jsonRoot)
{
QJsonArray jsonMessages = jsonRoot.value("messages").toArray();
if (jsonMessages.empty()) return Failure;
std::vector<MessagePtr> messages;
for (const auto jsonMessage : jsonMessages) {
auto content = jsonMessage.toString().toUtf8();
// passing nullptr as the channel makes the message invalid but we don't
// check for that anyways
auto message = Communi::IrcMessage::fromData(content, nullptr);
auto privMsg = dynamic_cast<Communi::IrcPrivateMessage *>(message);
assert(privMsg);
MessageParseArgs args;
TwitchMessageBuilder builder(this, privMsg, args);
if (!builder.isIgnored()) {
messages.push_back(builder.build());
}
}
this->addMessagesAtStart(messages);
return Success;
}
void TwitchChannel::refreshPubsub() void TwitchChannel::refreshPubsub()
{ {
// listen to moderation actions // listen to moderation actions
@ -581,7 +585,7 @@ void TwitchChannel::loadBadges()
void TwitchChannel::loadCheerEmotes() void TwitchChannel::loadCheerEmotes()
{ {
auto url = Url{"https://api.twitch.tv/kraken/bits/actions?channel_id=" + /*auto url = Url{"https://api.twitch.tv/kraken/bits/actions?channel_id=" +
this->getRoomId()}; this->getRoomId()};
auto request = NetworkRequest::twitchRequest(url.string); auto request = NetworkRequest::twitchRequest(url.string);
request.setCaller(QThread::currentThread()); request.setCaller(QThread::currentThread());
@ -589,6 +593,7 @@ void TwitchChannel::loadCheerEmotes()
request.onSuccess( request.onSuccess(
[this, weak = weakOf<Channel>(this)](auto result) -> Outcome { [this, weak = weakOf<Channel>(this)](auto result) -> Outcome {
auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson()); auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson());
std::vector<CheerEmoteSet> emoteSets;
for (auto &set : cheerEmoteSets) { for (auto &set : cheerEmoteSets) {
auto cheerEmoteSet = CheerEmoteSet(); auto cheerEmoteSet = CheerEmoteSet();
@ -631,13 +636,15 @@ void TwitchChannel::loadCheerEmotes()
return lhs.minBits < rhs.minBits; // return lhs.minBits < rhs.minBits; //
}); });
this->cheerEmoteSets_.emplace_back(cheerEmoteSet); emoteSets.emplace_back(cheerEmoteSet);
} }
*this->cheerEmoteSets_.access() = std::move(emoteSets);
return Success; return Success;
}); });
request.execute(); request.execute();
*/
} }
boost::optional<EmotePtr> TwitchChannel::getTwitchBadge( boost::optional<EmotePtr> TwitchChannel::getTwitchBadge(

View file

@ -116,7 +116,6 @@ private:
void refreshViewerList(); void refreshViewerList();
Outcome parseViewerList(const QJsonObject &jsonRoot); Outcome parseViewerList(const QJsonObject &jsonRoot);
void loadRecentMessages(); void loadRecentMessages();
Outcome parseRecentMessages(const QJsonObject &jsonRoot);
void setLive(bool newLiveStatus); void setLive(bool newLiveStatus);
@ -144,7 +143,7 @@ private:
// "subscribers": { "0": ... "3": ... "6": ... // "subscribers": { "0": ... "3": ... "6": ...
UniqueAccess<std::map<QString, std::map<QString, EmotePtr>>> badgeSets_; UniqueAccess<std::map<QString, std::map<QString, EmotePtr>>> badgeSets_;
std::vector<CheerEmoteSet> cheerEmoteSets_; UniqueAccess<std::vector<CheerEmoteSet>> cheerEmoteSets_;
// -- // --
QByteArray messageSuffix_; QByteArray messageSuffix_;

View file

@ -3,7 +3,6 @@
#include <QString> #include <QString>
#include <unordered_map> #include <unordered_map>
#include "common/Emotemap.hpp"
#include "common/UniqueAccess.hpp" #include "common/UniqueAccess.hpp"
#include "messages/Emote.hpp" #include "messages/Emote.hpp"
#include "providers/twitch/EmoteValue.hpp" #include "providers/twitch/EmoteValue.hpp"

View file

@ -715,41 +715,38 @@ void TwitchMessageBuilder::appendTwitchBadges()
//} //}
} else if (badge == "staff/1") { } else if (badge == "staff/1") {
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromNonOwningPixmap(&app->resources->twitch.staff), Image::fromPixmap(app->resources->twitch.staff),
MessageElementFlag::BadgeGlobalAuthority) MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Twitch Staff"); ->setTooltip("Twitch Staff");
} else if (badge == "admin/1") { } else if (badge == "admin/1") {
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromNonOwningPixmap(&app->resources->twitch.admin), Image::fromPixmap(app->resources->twitch.admin),
MessageElementFlag::BadgeGlobalAuthority) MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Twitch Admin"); ->setTooltip("Twitch Admin");
} else if (badge == "global_mod/1") { } else if (badge == "global_mod/1") {
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromNonOwningPixmap( Image::fromPixmap(app->resources->twitch.globalmod),
&app->resources->twitch.globalmod),
MessageElementFlag::BadgeGlobalAuthority) MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Twitch Global Moderator"); ->setTooltip("Twitch Global Moderator");
} else if (badge == "moderator/1") { } else if (badge == "moderator/1") {
// TODO: Implement custom FFZ moderator badge // TODO: Implement custom FFZ moderator badge
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromNonOwningPixmap( Image::fromPixmap(app->resources->twitch.moderator),
&app->resources->twitch.moderator),
MessageElementFlag::BadgeChannelAuthority) MessageElementFlag::BadgeChannelAuthority)
->setTooltip("Twitch Channel Moderator"); ->setTooltip("Twitch Channel Moderator");
} else if (badge == "turbo/1") { } else if (badge == "turbo/1") {
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromNonOwningPixmap(&app->resources->twitch.turbo), Image::fromPixmap(app->resources->twitch.turbo),
MessageElementFlag::BadgeGlobalAuthority) MessageElementFlag::BadgeGlobalAuthority)
->setTooltip("Twitch Turbo Subscriber"); ->setTooltip("Twitch Turbo Subscriber");
} else if (badge == "broadcaster/1") { } else if (badge == "broadcaster/1") {
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromNonOwningPixmap( Image::fromPixmap(app->resources->twitch.broadcaster),
&app->resources->twitch.broadcaster),
MessageElementFlag::BadgeChannelAuthority) MessageElementFlag::BadgeChannelAuthority)
->setTooltip("Twitch Broadcaster"); ->setTooltip("Twitch Broadcaster");
} else if (badge == "premium/1") { } else if (badge == "premium/1") {
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromNonOwningPixmap(&app->resources->twitch.prime), Image::fromPixmap(app->resources->twitch.prime),
MessageElementFlag::BadgeVanity) MessageElementFlag::BadgeVanity)
->setTooltip("Twitch Prime Subscriber"); ->setTooltip("Twitch Prime Subscriber");
} else if (badge.startsWith("partner/")) { } else if (badge.startsWith("partner/")) {
@ -757,8 +754,8 @@ void TwitchMessageBuilder::appendTwitchBadges()
switch (index) { switch (index) {
case 1: { case 1: {
this->emplace<ImageElement>( this->emplace<ImageElement>(
Image::fromNonOwningPixmap( Image::fromPixmap(app->resources->twitch.verified,
&app->resources->twitch.verified, 0.25), 0.25),
MessageElementFlag::BadgeVanity) MessageElementFlag::BadgeVanity)
->setTooltip("Twitch Verified"); ->setTooltip("Twitch Verified");
} break; } break;

View file

@ -52,7 +52,7 @@ void addEmoteContextMenuItems(const Emote &emote,
// Add copy and open links for 1x, 2x, 3x // Add copy and open links for 1x, 2x, 3x
auto addImageLink = [&](const ImagePtr &image, char scale) { auto addImageLink = [&](const ImagePtr &image, char scale) {
if (!image->empty()) { if (!image->isEmpty()) {
copyMenu->addAction( copyMenu->addAction(
QString(scale) + "x link", [url = image->url()] { QString(scale) + "x link", [url = image->url()] {
QApplication::clipboard()->setText(url.string); QApplication::clipboard()->setText(url.string);

View file

@ -322,11 +322,11 @@ ChannelPtr LookPage::createPreviewChannel()
{ {
MessageBuilder builder; MessageBuilder builder;
builder.emplace<TimestampElement>(QTime(8, 13, 42)); builder.emplace<TimestampElement>(QTime(8, 13, 42));
builder.emplace<ImageElement>(Image::fromNonOwningPixmap(&getApp()->resources->twitch.moderator), MessageElementFlag::BadgeChannelAuthority); builder.emplace<ImageElement>(Image::fromPixmap(getApp()->resources->twitch.moderator), MessageElementFlag::BadgeChannelAuthority);
builder.emplace<ImageElement>(Image::fromNonOwningPixmap(&getApp()->resources->twitch.subscriber, 0.25), MessageElementFlag::BadgeSubscription); builder.emplace<ImageElement>(Image::fromPixmap(getApp()->resources->twitch.subscriber, 0.25), MessageElementFlag::BadgeSubscription);
builder.emplace<TextElement>("username1:", MessageElementFlag::Username, QColor("#0094FF"), FontStyle::ChatMediumBold); builder.emplace<TextElement>("username1:", MessageElementFlag::Username, QColor("#0094FF"), FontStyle::ChatMediumBold);
builder.emplace<TextElement>("This is a preview message", MessageElementFlag::Text); builder.emplace<TextElement>("This is a preview message", MessageElementFlag::Text);
builder.emplace<ImageElement>(Image::fromNonOwningPixmap(&getApp()->resources->pajaDank, 0.25), MessageElementFlag::AlwaysShow); builder.emplace<ImageElement>(Image::fromPixmap(getApp()->resources->pajaDank, 0.25), MessageElementFlag::AlwaysShow);
builder.emplace<TextElement>("@fourtf", MessageElementFlag::BoldUsername, MessageColor::Text, FontStyle::ChatMediumBold); builder.emplace<TextElement>("@fourtf", MessageElementFlag::BoldUsername, MessageColor::Text, FontStyle::ChatMediumBold);
builder.emplace<TextElement>("@fourtf", MessageElementFlag::NonBoldUsername); builder.emplace<TextElement>("@fourtf", MessageElementFlag::NonBoldUsername);
channel->addMessage(builder.release()); channel->addMessage(builder.release());
@ -334,7 +334,7 @@ ChannelPtr LookPage::createPreviewChannel()
{ {
MessageBuilder message; MessageBuilder message;
message.emplace<TimestampElement>(QTime(8, 15, 21)); message.emplace<TimestampElement>(QTime(8, 15, 21));
message.emplace<ImageElement>(Image::fromNonOwningPixmap(&getApp()->resources->twitch.broadcaster), MessageElementFlag::BadgeChannelAuthority); message.emplace<ImageElement>(Image::fromPixmap(getApp()->resources->twitch.broadcaster), MessageElementFlag::BadgeChannelAuthority);
message.emplace<TextElement>("username2:", MessageElementFlag::Username, QColor("#FF6A00"), FontStyle::ChatMediumBold); message.emplace<TextElement>("username2:", MessageElementFlag::Username, QColor("#FF6A00"), FontStyle::ChatMediumBold);
message.emplace<TextElement>("This is another one", MessageElementFlag::Text); message.emplace<TextElement>("This is another one", MessageElementFlag::Text);
// message.emplace<ImageElement>(Image::fromNonOwningPixmap(&getApp()->resources->ppHop), MessageElementFlag::BttvEmote); // message.emplace<ImageElement>(Image::fromNonOwningPixmap(&getApp()->resources->ppHop), MessageElementFlag::BttvEmote);