Merge branch 'master' into apa-notification-on-live
This commit is contained in:
@@ -32,3 +32,5 @@ QStringAlias(UserName);
|
||||
QStringAlias(UserId);
|
||||
QStringAlias(Url);
|
||||
QStringAlias(Tooltip);
|
||||
QStringAlias(EmoteId);
|
||||
QStringAlias(EmoteName);
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
namespace chatterino {
|
||||
|
||||
template <typename T>
|
||||
class MutexValue : boost::noncopyable
|
||||
class Atomic : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
MutexValue()
|
||||
Atomic()
|
||||
{
|
||||
}
|
||||
|
||||
MutexValue(T &&val)
|
||||
Atomic(T &&val)
|
||||
: value_(val)
|
||||
{
|
||||
}
|
||||
@@ -32,6 +32,13 @@ public:
|
||||
this->value_ = val;
|
||||
}
|
||||
|
||||
void set(T &&val)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex_);
|
||||
|
||||
this->value_ = std::move(val);
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex_;
|
||||
T value_;
|
||||
+47
-16
@@ -18,16 +18,14 @@
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
//
|
||||
// Channel
|
||||
//
|
||||
Channel::Channel(const QString &name, Type type)
|
||||
: completionModel(name)
|
||||
: completionModel(*this)
|
||||
, name_(name)
|
||||
, type_(type)
|
||||
{
|
||||
QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout,
|
||||
[this]() {
|
||||
this->completionModel.clearExpiredStrings(); //
|
||||
});
|
||||
this->clearCompletionModelTimer_.start(60 * 1000);
|
||||
}
|
||||
|
||||
Channel::~Channel()
|
||||
@@ -67,8 +65,7 @@ void Channel::addMessage(MessagePtr message)
|
||||
|
||||
const QString &username = message->loginName;
|
||||
if (!username.isEmpty()) {
|
||||
// TODO: Add recent chatters display name. This should maybe be a
|
||||
// setting
|
||||
// TODO: Add recent chatters display name
|
||||
this->addRecentChatter(message);
|
||||
}
|
||||
|
||||
@@ -98,8 +95,6 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
|
||||
for (int i = snapshotLength - 1; i >= end; --i) {
|
||||
auto &s = snapshot[i];
|
||||
|
||||
qDebug() << s->parseTime << minimumTime;
|
||||
|
||||
if (s->parseTime < minimumTime) {
|
||||
break;
|
||||
}
|
||||
@@ -165,13 +160,14 @@ void Channel::disableAllMessages()
|
||||
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
|
||||
int snapshotLength = snapshot.getLength();
|
||||
for (int i = 0; i < snapshotLength; i++) {
|
||||
auto &s = snapshot[i];
|
||||
if (s->flags.hasAny({MessageFlag::System, MessageFlag::Timeout})) {
|
||||
auto &message = snapshot[i];
|
||||
if (message->flags.hasAny(
|
||||
{MessageFlag::System, MessageFlag::Timeout})) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// FOURTF: disabled for now
|
||||
// s->flags.EnableFlag(MessageFlag::Disabled);
|
||||
const_cast<Message *>(message.get())->flags.set(MessageFlag::Disabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +192,6 @@ void Channel::replaceMessage(MessagePtr message, MessagePtr replacement)
|
||||
|
||||
void Channel::addRecentChatter(const MessagePtr &message)
|
||||
{
|
||||
// Do nothing by default
|
||||
}
|
||||
|
||||
bool Channel::canSendMessage() const
|
||||
@@ -234,9 +229,45 @@ void Channel::onConnected()
|
||||
{
|
||||
}
|
||||
|
||||
std::weak_ptr<Channel> Channel::weak_from_this()
|
||||
//
|
||||
// Indirect channel
|
||||
//
|
||||
IndirectChannel::Data::Data(ChannelPtr _channel, Channel::Type _type)
|
||||
: channel(_channel)
|
||||
, type(_type)
|
||||
{
|
||||
return std::weak_ptr<Channel>(this->shared_from_this());
|
||||
}
|
||||
|
||||
IndirectChannel::IndirectChannel(ChannelPtr channel, Channel::Type type)
|
||||
: data_(std::make_unique<Data>(channel, type))
|
||||
{
|
||||
}
|
||||
|
||||
ChannelPtr IndirectChannel::get()
|
||||
{
|
||||
return data_->channel;
|
||||
}
|
||||
|
||||
void IndirectChannel::reset(ChannelPtr channel)
|
||||
{
|
||||
assert(this->data_->type != Channel::Type::Direct);
|
||||
|
||||
this->data_->channel = channel;
|
||||
this->data_->changed.invoke();
|
||||
}
|
||||
|
||||
pajlada::Signals::NoArgSignal &IndirectChannel::getChannelChanged()
|
||||
{
|
||||
return this->data_->changed;
|
||||
}
|
||||
|
||||
Channel::Type IndirectChannel::getType()
|
||||
{
|
||||
if (this->data_->type == Channel::Type::Direct) {
|
||||
return this->get()->getType();
|
||||
} else {
|
||||
return this->data_->type;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
+9
-43
@@ -1,10 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/CompletionModel.hpp"
|
||||
#include "messages/Image.hpp"
|
||||
#include "messages/LimitedQueue.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "util/ConcurrentMap.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
@@ -13,7 +10,9 @@
|
||||
#include <memory>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
|
||||
class Channel : public std::enable_shared_from_this<Channel>
|
||||
{
|
||||
@@ -52,7 +51,6 @@ public:
|
||||
void addOrReplaceTimeout(MessagePtr message);
|
||||
void disableAllMessages();
|
||||
void replaceMessage(MessagePtr message, MessagePtr replacement);
|
||||
virtual void addRecentChatter(const MessagePtr &message);
|
||||
|
||||
QStringList modList;
|
||||
|
||||
@@ -66,11 +64,9 @@ public:
|
||||
|
||||
CompletionModel completionModel;
|
||||
|
||||
// pre c++17 polyfill
|
||||
std::weak_ptr<Channel> weak_from_this();
|
||||
|
||||
protected:
|
||||
virtual void onConnected();
|
||||
virtual void addRecentChatter(const MessagePtr &message);
|
||||
|
||||
private:
|
||||
const QString name_;
|
||||
@@ -88,47 +84,17 @@ class IndirectChannel
|
||||
Channel::Type type;
|
||||
pajlada::Signals::NoArgSignal changed;
|
||||
|
||||
Data() = delete;
|
||||
Data(ChannelPtr _channel, Channel::Type _type)
|
||||
: channel(_channel)
|
||||
, type(_type)
|
||||
{
|
||||
}
|
||||
Data(ChannelPtr channel, Channel::Type type);
|
||||
};
|
||||
|
||||
public:
|
||||
IndirectChannel(ChannelPtr channel,
|
||||
Channel::Type type = Channel::Type::Direct)
|
||||
: data_(new Data(channel, type))
|
||||
{
|
||||
}
|
||||
Channel::Type type = Channel::Type::Direct);
|
||||
|
||||
ChannelPtr get()
|
||||
{
|
||||
return data_->channel;
|
||||
}
|
||||
|
||||
void update(ChannelPtr ptr)
|
||||
{
|
||||
assert(this->data_->type != Channel::Type::Direct);
|
||||
|
||||
this->data_->channel = ptr;
|
||||
this->data_->changed.invoke();
|
||||
}
|
||||
|
||||
pajlada::Signals::NoArgSignal &getChannelChanged()
|
||||
{
|
||||
return this->data_->changed;
|
||||
}
|
||||
|
||||
Channel::Type getType()
|
||||
{
|
||||
if (this->data_->type == Channel::Type::Direct) {
|
||||
return this->get()->getType();
|
||||
} else {
|
||||
return this->data_->type;
|
||||
}
|
||||
}
|
||||
ChannelPtr get();
|
||||
void reset(ChannelPtr channel);
|
||||
pajlada::Signals::NoArgSignal &getChannelChanged();
|
||||
Channel::Type getType();
|
||||
|
||||
private:
|
||||
std::shared_ptr<Data> data_;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "common/Aliases.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/ProviderId.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
@@ -40,4 +39,12 @@ std::weak_ptr<T> weakOf(T *element)
|
||||
return element->shared_from_this();
|
||||
}
|
||||
|
||||
struct Message;
|
||||
using MessagePtr = std::shared_ptr<const Message>;
|
||||
|
||||
enum class CopyMode {
|
||||
Everything,
|
||||
OnlyTextAndEmotes,
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
+83
-180
@@ -2,45 +2,30 @@
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "common/UsernameSet.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandController.hpp"
|
||||
#include "debug/Benchmark.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchServer.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
|
||||
#include <QtAlgorithms>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// -- TaggedString
|
||||
//
|
||||
// TaggedString
|
||||
//
|
||||
|
||||
CompletionModel::TaggedString::TaggedString(const QString &_str, Type _type)
|
||||
: str(_str)
|
||||
CompletionModel::TaggedString::TaggedString(const QString &_string, Type _type)
|
||||
: string(_string)
|
||||
, type(_type)
|
||||
, timeAdded(std::chrono::steady_clock::now())
|
||||
{
|
||||
}
|
||||
|
||||
bool CompletionModel::TaggedString::isExpired(
|
||||
const std::chrono::steady_clock::time_point &now) const
|
||||
{
|
||||
switch (this->type) {
|
||||
case Type::Username: {
|
||||
static std::chrono::minutes expirationTimer(10);
|
||||
|
||||
return (this->timeAdded + expirationTimer < now);
|
||||
} break;
|
||||
|
||||
default: {
|
||||
return false;
|
||||
} break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CompletionModel::TaggedString::isEmote() const
|
||||
{
|
||||
return this->type > Type::EmoteStart && this->type < Type::EmoteEnd;
|
||||
@@ -48,35 +33,23 @@ bool CompletionModel::TaggedString::isEmote() const
|
||||
|
||||
bool CompletionModel::TaggedString::operator<(const TaggedString &that) const
|
||||
{
|
||||
if (this->isEmote()) {
|
||||
if (that.isEmote()) {
|
||||
int k = QString::compare(this->str, that.str, Qt::CaseInsensitive);
|
||||
if (k == 0) {
|
||||
return this->str > that.str;
|
||||
}
|
||||
|
||||
return k < 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (this->isEmote() != that.isEmote()) {
|
||||
return this->isEmote();
|
||||
}
|
||||
|
||||
if (that.isEmote()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int k = QString::compare(this->str, that.str, Qt::CaseInsensitive);
|
||||
if (k == 0) {
|
||||
return false;
|
||||
}
|
||||
// try comparing insensitively, if they are the same then senstively
|
||||
// (fixes order of LuL and LUL)
|
||||
int k = QString::compare(this->string, that.string, Qt::CaseInsensitive);
|
||||
if (k == 0) return this->string > that.string;
|
||||
|
||||
return k < 0;
|
||||
}
|
||||
|
||||
// -- CompletionModel
|
||||
|
||||
CompletionModel::CompletionModel(const QString &channelName)
|
||||
: channelName_(channelName)
|
||||
//
|
||||
// CompletionModel
|
||||
//
|
||||
CompletionModel::CompletionModel(Channel &channel)
|
||||
: channel_(channel)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -87,160 +60,90 @@ int CompletionModel::columnCount(const QModelIndex &) const
|
||||
|
||||
QVariant CompletionModel::data(const QModelIndex &index, int) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->emotesMutex_);
|
||||
std::lock_guard<std::mutex> lock(this->itemsMutex_);
|
||||
|
||||
// TODO: Implement more safely
|
||||
auto it = this->emotes_.begin();
|
||||
auto it = this->items_.begin();
|
||||
std::advance(it, index.row());
|
||||
return QVariant(it->str);
|
||||
return QVariant(it->string);
|
||||
}
|
||||
|
||||
int CompletionModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->emotesMutex_);
|
||||
std::lock_guard<std::mutex> lock(this->itemsMutex_);
|
||||
|
||||
return this->emotes_.size();
|
||||
return this->items_.size();
|
||||
}
|
||||
|
||||
void CompletionModel::refresh()
|
||||
void CompletionModel::refresh(const QString &prefix)
|
||||
{
|
||||
Log("[CompletionModel:{}] Refreshing...]", this->channelName_);
|
||||
std::lock_guard<std::mutex> guard(this->itemsMutex_);
|
||||
this->items_.clear();
|
||||
|
||||
auto app = getApp();
|
||||
if (prefix.length() < 2) return;
|
||||
|
||||
// User-specific: Twitch Emotes
|
||||
if (auto account = app->accounts->twitch.getCurrent()) {
|
||||
for (const auto &emote : account->accessEmotes()->allEmoteNames) {
|
||||
// XXX: No way to discern between a twitch global emote and sub
|
||||
// emote right now
|
||||
this->addString(emote.string,
|
||||
TaggedString::Type::TwitchGlobalEmote);
|
||||
}
|
||||
}
|
||||
|
||||
// // Global: BTTV Global Emotes
|
||||
// std::vector<QString> &bttvGlobalEmoteCodes =
|
||||
// app->emotes->bttv.globalEmoteNames_; for (const auto &m :
|
||||
// bttvGlobalEmoteCodes) {
|
||||
// this->addString(m, TaggedString::Type::BTTVGlobalEmote);
|
||||
// }
|
||||
|
||||
// // Global: FFZ Global Emotes
|
||||
// std::vector<QString> &ffzGlobalEmoteCodes =
|
||||
// app->emotes->ffz.globalEmoteCodes; for (const auto &m :
|
||||
// ffzGlobalEmoteCodes) {
|
||||
// this->addString(m, TaggedString::Type::FFZGlobalEmote);
|
||||
// }
|
||||
|
||||
// Channel emotes
|
||||
if (auto channel = dynamic_cast<TwitchChannel *>(
|
||||
getApp()
|
||||
->twitch2->getChannelOrEmptyByID(this->channelName_)
|
||||
.get())) {
|
||||
auto bttv = channel->accessBttvEmotes();
|
||||
// auto it = bttv->begin();
|
||||
// for (const auto &emote : *bttv) {
|
||||
// }
|
||||
// std::vector<QString> &bttvChannelEmoteCodes =
|
||||
// app->emotes->bttv.channelEmoteName_[this->channelName_];
|
||||
// for (const auto &m : bttvChannelEmoteCodes) {
|
||||
// this->addString(m, TaggedString::Type::BTTVChannelEmote);
|
||||
// }
|
||||
|
||||
// Channel-specific: FFZ Channel Emotes
|
||||
for (const auto &emote : *channel->accessFfzEmotes()) {
|
||||
this->addString(emote.second->name.string,
|
||||
TaggedString::Type::FFZChannelEmote);
|
||||
}
|
||||
}
|
||||
|
||||
// Emojis
|
||||
const auto &emojiShortCodes = app->emotes->emojis.shortCodes;
|
||||
for (const auto &m : emojiShortCodes) {
|
||||
this->addString(":" + m + ":", TaggedString::Type::Emoji);
|
||||
}
|
||||
|
||||
// Commands
|
||||
for (auto &command : app->commands->items.getVector()) {
|
||||
this->addString(command.name, TaggedString::Command);
|
||||
}
|
||||
|
||||
for (auto &command : app->commands->getDefaultTwitchCommandList()) {
|
||||
this->addString(command, TaggedString::Command);
|
||||
}
|
||||
|
||||
// Channel-specific: Usernames
|
||||
// fourtf: only works with twitch chat
|
||||
// auto c =
|
||||
// ChannelManager::getInstance().getTwitchChannel(this->channelName);
|
||||
// auto usernames = c->getUsernamesForCompletions();
|
||||
// for (const auto &name : usernames) {
|
||||
// assert(!name.displayName.isEmpty());
|
||||
// this->addString(name.displayName);
|
||||
// this->addString('@' + name.displayName);
|
||||
|
||||
// if (!name.localizedName.isEmpty()) {
|
||||
// this->addString(name.localizedName);
|
||||
// this->addString('@' + name.localizedName);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
void CompletionModel::addString(const QString &str, TaggedString::Type type)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->emotesMutex_);
|
||||
|
||||
// Always add a space at the end of completions
|
||||
this->emotes_.insert({str + " ", type});
|
||||
}
|
||||
|
||||
void CompletionModel::addUser(const QString &username)
|
||||
{
|
||||
auto add = [this](const QString &str) {
|
||||
auto ts = this->createUser(str + " ");
|
||||
// Always add a space at the end of completions
|
||||
std::pair<std::set<TaggedString>::iterator, bool> p =
|
||||
this->emotes_.insert(ts);
|
||||
if (!p.second) {
|
||||
// No inseration was made, figure out if we need to replace the
|
||||
// username.
|
||||
|
||||
if (p.first->str > ts.str) {
|
||||
// Replace lowercase version of name with mixed-case version
|
||||
this->emotes_.erase(p.first);
|
||||
auto result2 = this->emotes_.insert(ts);
|
||||
assert(result2.second);
|
||||
} else {
|
||||
p.first->timeAdded = std::chrono::steady_clock::now();
|
||||
}
|
||||
}
|
||||
auto addString = [&](const QString &str, TaggedString::Type type) {
|
||||
if (str.startsWith(prefix, Qt::CaseInsensitive))
|
||||
this->items_.emplace(str + " ", type);
|
||||
};
|
||||
|
||||
add(username);
|
||||
add("@" + username);
|
||||
}
|
||||
if (auto channel = dynamic_cast<TwitchChannel *>(&this->channel_)) {
|
||||
// account emotes
|
||||
if (auto account = getApp()->accounts->twitch.getCurrent()) {
|
||||
for (const auto &emote : account->accessEmotes()->allEmoteNames) {
|
||||
// XXX: No way to discern between a twitch global emote and sub
|
||||
// emote right now
|
||||
addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
|
||||
}
|
||||
}
|
||||
|
||||
void CompletionModel::clearExpiredStrings()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->emotesMutex_);
|
||||
// Usernames
|
||||
if (prefix.length() >= UsernameSet::PrefixLength) {
|
||||
auto usernames = channel->accessChatters();
|
||||
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
for (const auto &name : usernames->subrange(Prefix(prefix))) {
|
||||
addString(name, TaggedString::Type::Username);
|
||||
addString("@" + name, TaggedString::Type::Username);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = this->emotes_.begin(); it != this->emotes_.end();) {
|
||||
const auto &taggedString = *it;
|
||||
// Bttv Global
|
||||
for (auto &emote : *channel->globalBttv().emotes()) {
|
||||
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
|
||||
}
|
||||
|
||||
if (taggedString.isExpired(now)) {
|
||||
// Log("String {} expired", taggedString.str);
|
||||
it = this->emotes_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
// Ffz Global
|
||||
for (auto &emote : *channel->globalFfz().emotes()) {
|
||||
addString(emote.first.string, TaggedString::Type::FFZChannelEmote);
|
||||
}
|
||||
|
||||
// Bttv Channel
|
||||
for (auto &emote : *channel->bttvEmotes()) {
|
||||
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
||||
}
|
||||
|
||||
// Ffz Channel
|
||||
for (auto &emote : *channel->ffzEmotes()) {
|
||||
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
|
||||
}
|
||||
|
||||
// Emojis
|
||||
if (prefix.startsWith(":")) {
|
||||
const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes;
|
||||
for (auto &m : emojiShortCodes) {
|
||||
addString(":" + m + ":", TaggedString::Type::Emoji);
|
||||
}
|
||||
}
|
||||
|
||||
// Commands
|
||||
for (auto &command : getApp()->commands->items.getVector()) {
|
||||
addString(command.name, TaggedString::Command);
|
||||
}
|
||||
|
||||
for (auto &command :
|
||||
getApp()->commands->getDefaultTwitchCommandList()) {
|
||||
addString(command, TaggedString::Command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CompletionModel::TaggedString CompletionModel::createUser(const QString &str)
|
||||
{
|
||||
return TaggedString{str, TaggedString::Type::Username};
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Common.hpp"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include <chrono>
|
||||
@@ -10,7 +8,7 @@
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class TwitchChannel;
|
||||
class Channel;
|
||||
|
||||
class CompletionModel : public QAbstractListModel
|
||||
{
|
||||
@@ -33,39 +31,30 @@ class CompletionModel : public QAbstractListModel
|
||||
Command,
|
||||
};
|
||||
|
||||
TaggedString(const QString &_str, Type _type);
|
||||
TaggedString(const QString &string, Type type);
|
||||
|
||||
bool isExpired(const std::chrono::steady_clock::time_point &now) const;
|
||||
bool isEmote() const;
|
||||
bool operator<(const TaggedString &that) const;
|
||||
|
||||
QString str;
|
||||
// Type will help decide the lifetime of the tagged strings
|
||||
QString string;
|
||||
Type type;
|
||||
|
||||
mutable std::chrono::steady_clock::time_point timeAdded;
|
||||
};
|
||||
|
||||
public:
|
||||
CompletionModel(const QString &channelName);
|
||||
CompletionModel(Channel &channel);
|
||||
|
||||
virtual int columnCount(const QModelIndex &) const override;
|
||||
virtual QVariant data(const QModelIndex &index, int) const override;
|
||||
virtual int rowCount(const QModelIndex &) const override;
|
||||
|
||||
void refresh();
|
||||
void addString(const QString &str, TaggedString::Type type);
|
||||
void addUser(const QString &str);
|
||||
|
||||
void clearExpiredStrings();
|
||||
void refresh(const QString &prefix);
|
||||
|
||||
private:
|
||||
TaggedString createUser(const QString &str);
|
||||
|
||||
mutable std::mutex emotesMutex_;
|
||||
std::set<TaggedString> emotes_;
|
||||
|
||||
QString channelName_;
|
||||
std::set<TaggedString> items_;
|
||||
mutable std::mutex itemsMutex_;
|
||||
Channel &channel_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
+10
-28
@@ -4,19 +4,17 @@
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
// = std::enable_if<std::is_enum<T>::value>::type
|
||||
|
||||
template <typename T, typename Q = typename std::underlying_type<T>::type>
|
||||
class FlagsEnum
|
||||
{
|
||||
public:
|
||||
FlagsEnum()
|
||||
: value(static_cast<T>(0))
|
||||
: value_(static_cast<T>(0))
|
||||
{
|
||||
}
|
||||
|
||||
FlagsEnum(T value)
|
||||
: value(value)
|
||||
: value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -29,22 +27,22 @@ public:
|
||||
|
||||
bool operator==(const FlagsEnum<T> &other)
|
||||
{
|
||||
return this->value == other.value;
|
||||
return this->value_ == other.value_;
|
||||
}
|
||||
|
||||
bool operator!=(const FlagsEnum &other)
|
||||
{
|
||||
return this->value != other.value;
|
||||
return this->value_ != other.value_;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
reinterpret_cast<Q &>(this->value) &= ~static_cast<Q>(flag);
|
||||
reinterpret_cast<Q &>(this->value_) &= ~static_cast<Q>(flag);
|
||||
}
|
||||
|
||||
void set(T flag, bool value)
|
||||
@@ -57,33 +55,17 @@ public:
|
||||
|
||||
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
|
||||
{
|
||||
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
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -93,7 +75,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
T value;
|
||||
T value_{};
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
+45
-51
@@ -5,69 +5,63 @@
|
||||
#include <QString>
|
||||
#include <QTextStream>
|
||||
|
||||
// ip 0.0.0.0 - 224.0.0.0
|
||||
#define IP \
|
||||
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" \
|
||||
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" \
|
||||
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))"
|
||||
#define PORT "(?::\\d{2,5})"
|
||||
#define WEB_CHAR1 "[_a-z\\x{00a1}-\\x{ffff}0-9]"
|
||||
#define WEB_CHAR2 "[a-z\\x{00a1}-\\x{ffff}0-9]"
|
||||
|
||||
#define SPOTIFY_1 "(?:artist|album|track|user:[^:]+:playlist):[a-zA-Z0-9]+"
|
||||
#define SPOTIFY_2 "user:[^:]+"
|
||||
#define SPOTIFY_3 "search:(?:[-\\w$\\.+!*'(),]+|%[a-fA-F0-9]{2})+"
|
||||
#define SPOTIFY_PARAMS "(?:" SPOTIFY_1 "|" SPOTIFY_2 "|" SPOTIFY_3 ")"
|
||||
#define SPOTIFY_LINK "(?x-mi:(spotify:" SPOTIFY_PARAMS "))"
|
||||
|
||||
#define WEB_PROTOCOL "(?:(?:https?|ftps?)://)?"
|
||||
#define WEB_USER "(?:\\S+(?::\\S*)?@)?"
|
||||
#define WEB_HOST "(?:(?:" WEB_CHAR1 "-*)*" WEB_CHAR2 "+)"
|
||||
#define WEB_DOMAIN "(?:\\.(?:" WEB_CHAR2 "-*)*" WEB_CHAR2 "+)*"
|
||||
#define WEB_TLD "(?:" + tldData + ")"
|
||||
#define WEB_RESOURCE_PATH "(?:[/?#]\\S*)"
|
||||
#define WEB_LINK \
|
||||
WEB_PROTOCOL WEB_USER "(?:" IP "|" WEB_HOST WEB_DOMAIN "\\." WEB_TLD PORT \
|
||||
"?" WEB_RESOURCE_PATH "?)"
|
||||
|
||||
#define LINK "^(?:" SPOTIFY_LINK "|" WEB_LINK ")$"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
LinkParser::LinkParser(const QString &unparsedString)
|
||||
{
|
||||
static QRegularExpression linkRegex = [] {
|
||||
static QRegularExpression newLineRegex("\r?\n");
|
||||
QFile tldFile(":/tlds.txt");
|
||||
tldFile.open(QFile::ReadOnly);
|
||||
QFile file(":/tlds.txt");
|
||||
file.open(QFile::ReadOnly);
|
||||
QTextStream tlds(&file);
|
||||
tlds.setCodec("UTF-8");
|
||||
|
||||
QTextStream t1(&tldFile);
|
||||
t1.setCodec("UTF-8");
|
||||
// tldData gets injected into the LINK macro
|
||||
auto tldData = tlds.readAll().replace(newLineRegex, "|");
|
||||
(void)tldData;
|
||||
|
||||
// Read the TLDs in and replace the newlines with pipes
|
||||
QString tldData = t1.readAll().replace(newLineRegex, "|");
|
||||
|
||||
const QString hyperlinkRegExp =
|
||||
"^"
|
||||
// Identifier for spotify
|
||||
"(?x-mi:(spotify:(?:"
|
||||
"(?:artist|album|track|user:[^:]+:playlist):"
|
||||
"[a-zA-Z0-9]+|user:[^:]+|search:"
|
||||
"(?:[-\\w$\\.+!*'(),]+|%[a-fA-F0-9]{2})+)))"
|
||||
// If nothing matches then just go on
|
||||
"|"
|
||||
"^"
|
||||
// Identifier for http and ftp
|
||||
"(?:(?:https?|ftps?)://)?"
|
||||
// user:pass authentication
|
||||
"(?:\\S+(?::\\S*)?@)?"
|
||||
"(?:"
|
||||
// IP address dotted notation octets
|
||||
// excludes loopback network 0.0.0.0
|
||||
// excludes reserved space >= 224.0.0.0
|
||||
// excludes network & broacast addresses
|
||||
// (first & last IP address of each class)
|
||||
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])"
|
||||
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}"
|
||||
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))"
|
||||
"|"
|
||||
// host name
|
||||
"(?:(?:[_a-z\\x{00a1}-\\x{ffff}0-9]-*)*[a-z\\x{00a1}-\\x{ffff}0-9]+"
|
||||
")"
|
||||
// domain name
|
||||
"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}0-9]-*)*[a-z\\x{00a1}-\\x{ffff}0-"
|
||||
"9]+)*"
|
||||
// TLD identifier
|
||||
//"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}]{2,}))"
|
||||
"(?:[\\.](?:" +
|
||||
tldData +
|
||||
"))"
|
||||
"\\.?"
|
||||
")"
|
||||
// port number
|
||||
"(?::\\d{2,5})?"
|
||||
// resource path
|
||||
"(?:[/?#]\\S*)?"
|
||||
"$";
|
||||
|
||||
return QRegularExpression(hyperlinkRegExp,
|
||||
return QRegularExpression(LINK,
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
}();
|
||||
|
||||
this->match_ = linkRegex.match(unparsedString);
|
||||
}
|
||||
|
||||
bool LinkParser::hasMatch() const
|
||||
{
|
||||
return this->match_.hasMatch();
|
||||
}
|
||||
|
||||
QString LinkParser::getCaptured() const
|
||||
{
|
||||
return this->match_.captured();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -10,15 +10,8 @@ class LinkParser
|
||||
public:
|
||||
explicit LinkParser(const QString &unparsedString);
|
||||
|
||||
bool hasMatch() const
|
||||
{
|
||||
return this->match_.hasMatch();
|
||||
}
|
||||
|
||||
QString getCaptured() const
|
||||
{
|
||||
return this->match_.captured();
|
||||
}
|
||||
bool hasMatch() const;
|
||||
QString getCaptured() const;
|
||||
|
||||
private:
|
||||
QRegularExpressionMatch match_;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename Type>
|
||||
class LockedObject
|
||||
{
|
||||
public:
|
||||
LockedObject &operator=(const LockedObject<Type> &other)
|
||||
{
|
||||
this->mutex_.lock();
|
||||
|
||||
this->data = other.getValue();
|
||||
|
||||
this->mutex_.unlock();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
LockedObject &operator=(const Type &other)
|
||||
{
|
||||
this->mutex_.lock();
|
||||
|
||||
this->data = other;
|
||||
|
||||
this->mutex_.unlock();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
Type value_;
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "Common.hpp"
|
||||
|
||||
class QNetworkReply;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class Outcome;
|
||||
class NetworkResult;
|
||||
|
||||
using NetworkSuccessCallback = std::function<Outcome(NetworkResult)>;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "common/NetworkData.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
|
||||
@@ -42,9 +41,7 @@ QString NetworkData::getHash()
|
||||
void NetworkData::writeToCache(const QByteArray &bytes)
|
||||
{
|
||||
if (this->useQuickLoadCache_) {
|
||||
auto app = getApp();
|
||||
|
||||
QFile cachedFile(app->paths->cacheDirectory + "/" + this->getHash());
|
||||
QFile cachedFile(getPaths()->cacheDirectory() + "/" + this->getHash());
|
||||
|
||||
if (cachedFile.open(QIODevice::WriteOnly)) {
|
||||
cachedFile.write(bytes);
|
||||
|
||||
@@ -19,6 +19,7 @@ struct NetworkData {
|
||||
QNetworkRequest request_;
|
||||
const QObject *caller_ = nullptr;
|
||||
bool useQuickLoadCache_{};
|
||||
bool executeConcurrently{};
|
||||
|
||||
NetworkReplyCreatedCallback onReplyCreated_;
|
||||
NetworkErrorCallback onError_;
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
namespace chatterino {
|
||||
|
||||
QThread NetworkManager::workerThread;
|
||||
QNetworkAccessManager NetworkManager::NaM;
|
||||
QNetworkAccessManager NetworkManager::accessManager;
|
||||
|
||||
void NetworkManager::init()
|
||||
{
|
||||
NetworkManager::NaM.moveToThread(&NetworkManager::workerThread);
|
||||
NetworkManager::accessManager.moveToThread(&NetworkManager::workerThread);
|
||||
NetworkManager::workerThread.start();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/NetworkRequester.hpp"
|
||||
#include "common/NetworkWorker.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
@@ -20,7 +11,7 @@ class NetworkManager : public QObject
|
||||
|
||||
public:
|
||||
static QThread workerThread;
|
||||
static QNetworkAccessManager NaM;
|
||||
static QNetworkAccessManager accessManager;
|
||||
|
||||
static void init();
|
||||
static void deinit();
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
#include "common/NetworkRequest.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/NetworkData.hpp"
|
||||
#include "common/NetworkManager.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
|
||||
#include <QFile>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace chatterino {
|
||||
@@ -80,6 +83,11 @@ void NetworkRequest::setTimeout(int ms)
|
||||
this->timer->timeoutMS_ = ms;
|
||||
}
|
||||
|
||||
void NetworkRequest::setExecuteConcurrently(bool value)
|
||||
{
|
||||
this->data->executeConcurrently = value;
|
||||
}
|
||||
|
||||
void NetworkRequest::makeAuthorizedV5(const QString &clientID,
|
||||
const QString &oauthToken)
|
||||
{
|
||||
@@ -130,16 +138,15 @@ void NetworkRequest::execute()
|
||||
} break;
|
||||
|
||||
default: {
|
||||
Log("[Execute] Unhandled request type");
|
||||
log("[Execute] Unhandled request type");
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
Outcome NetworkRequest::tryLoadCachedFile()
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
QFile cachedFile(app->paths->cacheDirectory + "/" + this->data->getHash());
|
||||
QFile cachedFile(getPaths()->cacheDirectory() + "/" +
|
||||
this->data->getHash());
|
||||
|
||||
if (!cachedFile.exists()) {
|
||||
// File didn't exist
|
||||
@@ -180,14 +187,15 @@ void NetworkRequest::doRequest()
|
||||
auto reply = [&]() -> QNetworkReply * {
|
||||
switch (data->requestType_) {
|
||||
case NetworkRequestType::Get:
|
||||
return NetworkManager::NaM.get(data->request_);
|
||||
return NetworkManager::accessManager.get(data->request_);
|
||||
|
||||
case NetworkRequestType::Put:
|
||||
return NetworkManager::NaM.put(data->request_,
|
||||
data->payload_);
|
||||
return NetworkManager::accessManager.put(data->request_,
|
||||
data->payload_);
|
||||
|
||||
case NetworkRequestType::Delete:
|
||||
return NetworkManager::NaM.deleteResource(data->request_);
|
||||
return NetworkManager::accessManager.deleteResource(
|
||||
data->request_);
|
||||
|
||||
default:
|
||||
return nullptr;
|
||||
@@ -195,13 +203,13 @@ void NetworkRequest::doRequest()
|
||||
}();
|
||||
|
||||
if (reply == nullptr) {
|
||||
Log("Unhandled request type");
|
||||
log("Unhandled request type");
|
||||
return;
|
||||
}
|
||||
|
||||
if (timer->isStarted()) {
|
||||
timer->onTimeout(worker, [reply, data]() {
|
||||
Log("Aborted!");
|
||||
log("Aborted!");
|
||||
reply->abort();
|
||||
if (data->onError_) {
|
||||
data->onError_(-2);
|
||||
@@ -228,7 +236,16 @@ void NetworkRequest::doRequest()
|
||||
NetworkResult result(bytes);
|
||||
|
||||
DebugCount::increase("http request success");
|
||||
data->onSuccess_(result);
|
||||
// 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);
|
||||
}
|
||||
// log("finished {}", data->request_.url().toString());
|
||||
|
||||
reply->deleteLater();
|
||||
};
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/NetworkCommon.hpp"
|
||||
#include "common/NetworkData.hpp"
|
||||
#include "common/NetworkRequester.hpp"
|
||||
#include "common/NetworkResult.hpp"
|
||||
#include "common/NetworkTimer.hpp"
|
||||
#include "common/NetworkWorker.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
class NetworkData;
|
||||
|
||||
class NetworkRequest
|
||||
{
|
||||
@@ -27,19 +26,15 @@ class NetworkRequest
|
||||
bool executed_ = false;
|
||||
|
||||
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(
|
||||
const std::string &url,
|
||||
NetworkRequestType requestType = NetworkRequestType::Get);
|
||||
explicit NetworkRequest(
|
||||
QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
|
||||
|
||||
NetworkRequest(NetworkRequest &&other) = default;
|
||||
NetworkRequest &operator=(NetworkRequest &&other) = default;
|
||||
|
||||
~NetworkRequest();
|
||||
|
||||
void setRequestType(NetworkRequestType newRequestType);
|
||||
@@ -55,14 +50,13 @@ public:
|
||||
void setRawHeader(const char *headerName, const QByteArray &value);
|
||||
void setRawHeader(const char *headerName, const QString &value);
|
||||
void setTimeout(int ms);
|
||||
void setExecuteConcurrently(bool value);
|
||||
void makeAuthorizedV5(const QString &clientID,
|
||||
const QString &oauthToken = QString());
|
||||
|
||||
void execute();
|
||||
|
||||
private:
|
||||
// Returns true if the file was successfully loaded from cache
|
||||
// Returns false if the cache file either didn't exist, or it contained
|
||||
// "invalid" data "invalid" is specified by the onSuccess callback
|
||||
Outcome tryLoadCachedFile();
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ rapidjson::Document NetworkResult::parseRapidJson() const
|
||||
ret.Parse(this->data_.data(), this->data_.length());
|
||||
|
||||
if (result.Code() != rapidjson::kParseErrorNone) {
|
||||
Log("JSON parse error: {} ({})",
|
||||
log("JSON parse error: {} ({})",
|
||||
rapidjson::GetParseError_En(result.Code()), result.Offset());
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "boost/noncopyable.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
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_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -1,43 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <pajlada/settings/serialize.hpp>
|
||||
|
||||
namespace pajlada {
|
||||
namespace Settings {
|
||||
|
||||
template <>
|
||||
struct Serialize<QString> {
|
||||
static rapidjson::Value get(const QString &value,
|
||||
rapidjson::Document::AllocatorType &a)
|
||||
{
|
||||
return rapidjson::Value(value.toUtf8(), a);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Deserialize<QString> {
|
||||
static QString get(const rapidjson::Value &value, bool *error = nullptr)
|
||||
{
|
||||
if (!value.IsString()) {
|
||||
PAJLADA_REPORT_ERROR(error)
|
||||
PAJLADA_THROW_EXCEPTION(
|
||||
"Deserialized rapidjson::Value is not a string");
|
||||
return QString{};
|
||||
}
|
||||
|
||||
try {
|
||||
return QString::fromUtf8(value.GetString(),
|
||||
value.GetStringLength());
|
||||
} catch (const std::exception &) {
|
||||
// int x = 5;
|
||||
} catch (...) {
|
||||
// int y = 5;
|
||||
}
|
||||
|
||||
return QString{};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Settings
|
||||
} // namespace pajlada
|
||||
@@ -1,34 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename TValue>
|
||||
class SimpleSignalVector
|
||||
{
|
||||
public:
|
||||
SimpleSignalVector &operator=(std::vector<TValue> &other)
|
||||
{
|
||||
this->data_ = other;
|
||||
|
||||
this->updated.invoke();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator std::vector<TValue> &()
|
||||
{
|
||||
return this->data_;
|
||||
}
|
||||
|
||||
pajlada::Signals::NoArgSignal updated;
|
||||
|
||||
private:
|
||||
std::vector<TValue> data_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -46,16 +46,15 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
T *element_;
|
||||
std::mutex *mutex_;
|
||||
bool isValid_ = true;
|
||||
T *element_{};
|
||||
std::mutex *mutex_{};
|
||||
bool isValid_{true};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class UniqueAccess
|
||||
{
|
||||
public:
|
||||
// template <typename X = decltype(T())>
|
||||
UniqueAccess()
|
||||
: element_(T())
|
||||
{
|
||||
@@ -88,7 +87,8 @@ public:
|
||||
return AccessGuard<T>(this->element_, this->mutex_);
|
||||
}
|
||||
|
||||
template <typename X = T, typename = std::enable_if_t<!std::is_const_v<X>>>
|
||||
template <typename X = T,
|
||||
typename = std::enable_if_t<!std::is_const<X>::value>>
|
||||
AccessGuard<const X> accessConst() const
|
||||
{
|
||||
return AccessGuard<const T>(this->element_, this->mutex_);
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#include "UsernameSet.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
//
|
||||
// UsernameSet
|
||||
//
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::begin() const
|
||||
{
|
||||
return this->items.begin();
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::end() const
|
||||
{
|
||||
return this->items.end();
|
||||
}
|
||||
|
||||
UsernameSet::Range UsernameSet::subrange(const Prefix &prefix) const
|
||||
{
|
||||
auto it = this->firstKeyForPrefix.find(prefix);
|
||||
if (it != this->firstKeyForPrefix.end()) {
|
||||
auto start = this->items.find(it->second);
|
||||
auto end = start;
|
||||
|
||||
while (end != this->items.end() && prefix.isStartOf(*end)) {
|
||||
end++;
|
||||
}
|
||||
return {start, end};
|
||||
}
|
||||
|
||||
return {this->items.end(), this->items.end()};
|
||||
}
|
||||
|
||||
std::set<QString>::size_type UsernameSet::size() const
|
||||
{
|
||||
return this->items.size();
|
||||
}
|
||||
|
||||
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(const QString &value)
|
||||
{
|
||||
this->insertPrefix(value);
|
||||
|
||||
return this->items.insert(value);
|
||||
}
|
||||
|
||||
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(QString &&value)
|
||||
{
|
||||
this->insertPrefix(value);
|
||||
|
||||
return this->items.insert(std::move(value));
|
||||
}
|
||||
|
||||
void UsernameSet::insertPrefix(const QString &value)
|
||||
{
|
||||
auto &string = this->firstKeyForPrefix[Prefix(value)];
|
||||
|
||||
if (string.isNull() || value < string) string = value;
|
||||
}
|
||||
|
||||
//
|
||||
// Range
|
||||
//
|
||||
|
||||
UsernameSet::Range::Range(ConstIterator start, ConstIterator end)
|
||||
: start_(start)
|
||||
, end_(end)
|
||||
{
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::Range::begin()
|
||||
{
|
||||
return this->start_;
|
||||
}
|
||||
|
||||
UsernameSet::ConstIterator UsernameSet::Range::end()
|
||||
{
|
||||
return this->end_;
|
||||
}
|
||||
|
||||
//
|
||||
// Prefix
|
||||
//
|
||||
|
||||
Prefix::Prefix(const QString &string)
|
||||
: first(string.size() >= 1 ? string[0].toLower() : '\0')
|
||||
, second(string.size() >= 2 ? string[1].toLower() : '\0')
|
||||
{
|
||||
}
|
||||
|
||||
bool Prefix::operator==(const Prefix &other) const
|
||||
{
|
||||
return std::tie(this->first, this->second) ==
|
||||
std::tie(other.first, other.second);
|
||||
}
|
||||
|
||||
bool Prefix::isStartOf(const QString &string) const
|
||||
{
|
||||
if (string.size() == 0) {
|
||||
return this->first == QChar('\0') && this->second == QChar('\0');
|
||||
} else if (string.size() == 1) {
|
||||
return this->first == string[0].toLower() &&
|
||||
this->second == QChar('\0');
|
||||
} else {
|
||||
return this->first == string[0].toLower() &&
|
||||
this->second == string[1].toLower();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace chatterino {
|
||||
class Prefix
|
||||
{
|
||||
public:
|
||||
Prefix(const QString &string);
|
||||
bool operator==(const Prefix &other) const;
|
||||
bool isStartOf(const QString &string) const;
|
||||
|
||||
private:
|
||||
QChar first;
|
||||
QChar second;
|
||||
|
||||
friend struct std::hash<Prefix>;
|
||||
};
|
||||
} // namespace chatterino
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<chatterino::Prefix> {
|
||||
size_t operator()(const chatterino::Prefix &prefix) const
|
||||
{
|
||||
return (size_t(prefix.first.unicode()) << 16) |
|
||||
size_t(prefix.second.unicode());
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
namespace chatterino {
|
||||
class UsernameSet
|
||||
{
|
||||
public:
|
||||
static constexpr int PrefixLength = 2;
|
||||
|
||||
using Iterator = std::set<QString>::iterator;
|
||||
using ConstIterator = std::set<QString>::const_iterator;
|
||||
|
||||
class Range
|
||||
{
|
||||
public:
|
||||
Range(ConstIterator start, ConstIterator end);
|
||||
|
||||
ConstIterator begin();
|
||||
ConstIterator end();
|
||||
|
||||
private:
|
||||
ConstIterator start_;
|
||||
ConstIterator end_;
|
||||
};
|
||||
|
||||
ConstIterator begin() const;
|
||||
ConstIterator end() const;
|
||||
Range subrange(const Prefix &prefix) const;
|
||||
|
||||
std::set<QString>::size_type size() const;
|
||||
|
||||
std::pair<Iterator, bool> insert(const QString &value);
|
||||
std::pair<Iterator, bool> insert(QString &&value);
|
||||
|
||||
private:
|
||||
void insertPrefix(const QString &string);
|
||||
|
||||
std::set<QString> items;
|
||||
std::unordered_map<Prefix, QString> firstKeyForPrefix;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -5,9 +5,9 @@
|
||||
#define CHATTERINO_VERSION "2.0.4"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#define CHATTERINO_OS "win"
|
||||
# define CHATTERINO_OS "win"
|
||||
#elif defined(Q_OS_MACOS)
|
||||
#define CHATTERINO_OS "macos"
|
||||
# define CHATTERINO_OS "macos"
|
||||
#elif defined(Q_OS_LINUX)
|
||||
#define CHATTERINO_OS "linux"
|
||||
# define CHATTERINO_OS "linux"
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user