Merge branch 'master' into logging
This commit is contained in:
+12
-61
@@ -1,11 +1,12 @@
|
||||
#include "application.hpp"
|
||||
|
||||
#include "controllers/accounts/accountcontroller.hpp"
|
||||
#include "controllers/commands/commandcontroller.hpp"
|
||||
#include "controllers/highlights/highlightcontroller.hpp"
|
||||
#include "controllers/ignores/ignorecontroller.hpp"
|
||||
#include "controllers/taggedusers/taggeduserscontroller.hpp"
|
||||
#include "providers/twitch/pubsub.hpp"
|
||||
#include "providers/twitch/twitchserver.hpp"
|
||||
#include "singletons/accountmanager.hpp"
|
||||
#include "singletons/emotemanager.hpp"
|
||||
#include "singletons/fontmanager.hpp"
|
||||
#include "singletons/loggingmanager.hpp"
|
||||
@@ -19,12 +20,6 @@
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
using namespace chatterino::singletons;
|
||||
|
||||
namespace chatterino {
|
||||
@@ -34,7 +29,7 @@ namespace {
|
||||
bool isBigEndian()
|
||||
{
|
||||
int test = 1;
|
||||
char *p = (char *)&test;
|
||||
char *p = reinterpret_cast<char *>(&test);
|
||||
|
||||
return p[0] == 0;
|
||||
}
|
||||
@@ -61,6 +56,7 @@ void Application::construct()
|
||||
isAppConstructed = true;
|
||||
|
||||
// 1. Instantiate all classes
|
||||
this->settings = new singletons::SettingManager;
|
||||
this->paths = new singletons::PathManager(this->argc, this->argv);
|
||||
this->themes = new singletons::ThemeManager;
|
||||
this->windows = new singletons::WindowManager;
|
||||
@@ -68,9 +64,9 @@ void Application::construct()
|
||||
this->commands = new controllers::commands::CommandController;
|
||||
this->highlights = new controllers::highlights::HighlightController;
|
||||
this->ignores = new controllers::ignores::IgnoreController;
|
||||
this->accounts = new singletons::AccountManager;
|
||||
this->taggedUsers = new controllers::taggedusers::TaggedUsersController;
|
||||
this->accounts = new controllers::accounts::AccountController;
|
||||
this->emotes = new singletons::EmoteManager;
|
||||
this->settings = new singletons::SettingManager;
|
||||
this->fonts = new singletons::FontManager;
|
||||
this->resources = new singletons::ResourceManager;
|
||||
|
||||
@@ -92,12 +88,13 @@ void Application::initialize()
|
||||
|
||||
// 2. Initialize/load classes
|
||||
this->settings->initialize();
|
||||
this->windows->initialize();
|
||||
|
||||
this->nativeMessaging->registerHost();
|
||||
|
||||
this->settings->load();
|
||||
this->commands->load();
|
||||
this->logging->initialize();
|
||||
this->windows->initialize();
|
||||
|
||||
this->resources->initialize();
|
||||
|
||||
@@ -109,7 +106,6 @@ void Application::initialize()
|
||||
this->accounts->load();
|
||||
|
||||
this->twitch.server->initialize();
|
||||
this->logging->initialize();
|
||||
|
||||
// XXX
|
||||
this->settings->updateWordTypeMask();
|
||||
@@ -182,8 +178,9 @@ void Application::initialize()
|
||||
}
|
||||
|
||||
auto msg = messages::Message::createTimeoutMessage(action);
|
||||
msg->flags |= messages::Message::PubSub;
|
||||
|
||||
util::postToThread([chan, msg] { chan->addMessage(msg); });
|
||||
util::postToThread([chan, msg] { chan->addOrReplaceTimeout(msg); });
|
||||
});
|
||||
|
||||
this->twitch.pubsub->sig.moderation.userUnbanned.connect([&](const auto &action) {
|
||||
@@ -205,10 +202,10 @@ void Application::initialize()
|
||||
// TODO(pajlada): Unlisten to all authed topics instead of only moderation topics
|
||||
// this->twitch.pubsub->UnlistenAllAuthedTopics();
|
||||
|
||||
this->twitch.pubsub->listenToWhispers(this->accounts->Twitch.getCurrent()); //
|
||||
this->twitch.pubsub->listenToWhispers(this->accounts->twitch.getCurrent()); //
|
||||
};
|
||||
|
||||
this->accounts->Twitch.currentUserChanged.connect(RequestModerationActions);
|
||||
this->accounts->twitch.currentUserChanged.connect(RequestModerationActions);
|
||||
|
||||
RequestModerationActions();
|
||||
}
|
||||
@@ -231,52 +228,6 @@ void Application::save()
|
||||
this->commands->save();
|
||||
}
|
||||
|
||||
void Application::runNativeMessagingHost()
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
app->nativeMessaging = new singletons::NativeMessagingManager;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
_setmode(_fileno(stdin), _O_BINARY);
|
||||
_setmode(_fileno(stdout), _O_BINARY);
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
bool bigEndian = isBigEndian();
|
||||
#endif
|
||||
|
||||
while (true) {
|
||||
char size_c[4];
|
||||
std::cin.read(size_c, 4);
|
||||
|
||||
if (std::cin.eof()) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t size = *reinterpret_cast<uint32_t *>(size_c);
|
||||
#if 0
|
||||
// To avoid breaking strict-aliasing rules and potentially inducing undefined behaviour, the following code can be run instead
|
||||
uint32_t size = 0;
|
||||
if (bigEndian) {
|
||||
size = size_c[3] | static_cast<uint32_t>(size_c[2]) << 8 |
|
||||
static_cast<uint32_t>(size_c[1]) << 16 | static_cast<uint32_t>(size_c[0]) << 24;
|
||||
} else {
|
||||
size = size_c[0] | static_cast<uint32_t>(size_c[1]) << 8 |
|
||||
static_cast<uint32_t>(size_c[2]) << 16 | static_cast<uint32_t>(size_c[3]) << 24;
|
||||
}
|
||||
#endif
|
||||
|
||||
char *b = (char *)malloc(size + 1);
|
||||
std::cin.read(b, size);
|
||||
*(b + size) = '\0';
|
||||
|
||||
app->nativeMessaging->sendToGuiProcess(QByteArray(b, size));
|
||||
|
||||
free(b);
|
||||
}
|
||||
}
|
||||
|
||||
Application *getApp()
|
||||
{
|
||||
assert(staticApp != nullptr);
|
||||
|
||||
+8
-1
@@ -26,6 +26,12 @@ class HighlightController;
|
||||
namespace ignores {
|
||||
class IgnoreController;
|
||||
}
|
||||
namespace taggedusers {
|
||||
class TaggedUsersController;
|
||||
}
|
||||
namespace accounts {
|
||||
class AccountController;
|
||||
}
|
||||
} // namespace controllers
|
||||
|
||||
namespace singletons {
|
||||
@@ -67,7 +73,8 @@ public:
|
||||
controllers::commands::CommandController *commands = nullptr;
|
||||
controllers::highlights::HighlightController *highlights = nullptr;
|
||||
controllers::ignores::IgnoreController *ignores = nullptr;
|
||||
singletons::AccountManager *accounts = nullptr;
|
||||
controllers::taggedusers::TaggedUsersController *taggedUsers = nullptr;
|
||||
controllers::accounts::AccountController *accounts = nullptr;
|
||||
singletons::EmoteManager *emotes = nullptr;
|
||||
singletons::NativeMessagingManager *nativeMessaging = nullptr;
|
||||
singletons::SettingManager *settings = nullptr;
|
||||
|
||||
+76
-47
@@ -45,6 +45,11 @@ Channel::Type Channel::getType() const
|
||||
return this->type;
|
||||
}
|
||||
|
||||
bool Channel::isTwitchChannel() const
|
||||
{
|
||||
return this->type >= Twitch && this->type < TwitchEnd;
|
||||
}
|
||||
|
||||
bool Channel::isEmpty() const
|
||||
{
|
||||
return this->name.isEmpty();
|
||||
@@ -60,57 +65,14 @@ void Channel::addMessage(MessagePtr message)
|
||||
auto app = getApp();
|
||||
MessagePtr deleted;
|
||||
|
||||
bool isTimeout = (message->flags & Message::Timeout) != 0;
|
||||
|
||||
if (!isTimeout) {
|
||||
const QString &username = message->loginName;
|
||||
if (!username.isEmpty()) {
|
||||
// TODO: Add recent chatters display name. This should maybe be a setting
|
||||
this->addRecentChatter(message);
|
||||
}
|
||||
const QString &username = message->loginName;
|
||||
if (!username.isEmpty()) {
|
||||
// TODO: Add recent chatters display name. This should maybe be a setting
|
||||
this->addRecentChatter(message);
|
||||
}
|
||||
|
||||
app->logging->addMessage(this->name, message);
|
||||
|
||||
if (isTimeout) {
|
||||
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
|
||||
bool addMessage = true;
|
||||
int snapshotLength = snapshot.getLength();
|
||||
|
||||
int end = std::max(0, snapshotLength - 20);
|
||||
|
||||
for (int i = snapshotLength - 1; i >= end; --i) {
|
||||
auto &s = snapshot[i];
|
||||
if (s->flags.HasFlag(Message::Untimeout) && s->timeoutUser == message->timeoutUser) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == message->timeoutUser) {
|
||||
assert(message->banAction != nullptr);
|
||||
MessagePtr replacement(
|
||||
Message::createTimeoutMessage(*(message->banAction), s->count + 1));
|
||||
this->replaceMessage(s, replacement);
|
||||
addMessage = false;
|
||||
}
|
||||
}
|
||||
|
||||
// disable the messages from the user
|
||||
for (int i = 0; i < snapshotLength; i++) {
|
||||
auto &s = snapshot[i];
|
||||
if ((s->flags & (Message::Timeout | Message::Untimeout)) == 0 &&
|
||||
s->loginName == message->timeoutUser) {
|
||||
s->flags.EnableFlag(Message::Disabled);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Might need the following line
|
||||
// WindowManager::getInstance().repaintVisibleChatWidgets(this);
|
||||
|
||||
if (!addMessage) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->messages.pushBack(message, deleted)) {
|
||||
this->messageRemovedFromStart.invoke(deleted);
|
||||
}
|
||||
@@ -118,6 +80,73 @@ void Channel::addMessage(MessagePtr message)
|
||||
this->messageAppended.invoke(message);
|
||||
}
|
||||
|
||||
void Channel::addOrReplaceTimeout(messages::MessagePtr message)
|
||||
{
|
||||
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
|
||||
int snapshotLength = snapshot.getLength();
|
||||
|
||||
int end = std::max(0, snapshotLength - 20);
|
||||
|
||||
bool addMessage = true;
|
||||
|
||||
QTime minimumTime = QTime::currentTime().addSecs(-5);
|
||||
|
||||
for (int i = snapshotLength - 1; i >= end; --i) {
|
||||
auto &s = snapshot[i];
|
||||
|
||||
qDebug() << s->parseTime << minimumTime;
|
||||
|
||||
if (s->parseTime < minimumTime) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (s->flags.HasFlag(Message::Untimeout) && s->timeoutUser == message->timeoutUser) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == message->timeoutUser) {
|
||||
if (message->flags.HasFlag(Message::PubSub) && !s->flags.HasFlag(Message::PubSub)) {
|
||||
this->replaceMessage(s, message);
|
||||
addMessage = false;
|
||||
break;
|
||||
}
|
||||
if (!message->flags.HasFlag(Message::PubSub) && s->flags.HasFlag(Message::PubSub)) {
|
||||
addMessage = false;
|
||||
break;
|
||||
}
|
||||
|
||||
int count = s->count + 1;
|
||||
|
||||
messages::MessagePtr replacement(Message::createSystemMessage(
|
||||
message->searchText + QString("(") + QString::number(count) + " times)"));
|
||||
|
||||
replacement->timeoutUser = message->timeoutUser;
|
||||
replacement->count = count;
|
||||
replacement->flags = message->flags;
|
||||
|
||||
this->replaceMessage(s, replacement);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// disable the messages from the user
|
||||
for (int i = 0; i < snapshotLength; i++) {
|
||||
auto &s = snapshot[i];
|
||||
if ((s->flags & (Message::Timeout | Message::Untimeout)) == 0 &&
|
||||
s->loginName == message->timeoutUser) {
|
||||
s->flags.EnableFlag(Message::Disabled);
|
||||
}
|
||||
}
|
||||
|
||||
if (addMessage) {
|
||||
this->addMessage(message);
|
||||
}
|
||||
|
||||
// XXX: Might need the following line
|
||||
// WindowManager::getInstance().repaintVisibleChatWidgets(this);
|
||||
}
|
||||
|
||||
void Channel::addMessagesAtStart(std::vector<messages::MessagePtr> &_messages)
|
||||
{
|
||||
std::vector<messages::MessagePtr> addedMessages = this->messages.pushFront(_messages);
|
||||
|
||||
@@ -29,6 +29,7 @@ public:
|
||||
TwitchWhispers,
|
||||
TwitchWatching,
|
||||
TwitchMentions,
|
||||
TwitchEnd,
|
||||
};
|
||||
|
||||
explicit Channel(const QString &_name, Type type);
|
||||
@@ -43,11 +44,13 @@ public:
|
||||
pajlada::Signals::NoArgSignal destroyed;
|
||||
|
||||
Type getType() const;
|
||||
bool isTwitchChannel() const;
|
||||
virtual bool isEmpty() const;
|
||||
messages::LimitedQueueSnapshot<messages::MessagePtr> getMessageSnapshot();
|
||||
|
||||
void addMessage(messages::MessagePtr message);
|
||||
void addMessagesAtStart(std::vector<messages::MessagePtr> &messages);
|
||||
void addOrReplaceTimeout(messages::MessagePtr message);
|
||||
void replaceMessage(messages::MessagePtr message, messages::MessagePtr replacement);
|
||||
virtual void addRecentChatter(const std::shared_ptr<messages::Message> &message);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "debug/log.hpp"
|
||||
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
#include <boost/preprocessor.hpp>
|
||||
|
||||
#include <string>
|
||||
@@ -20,4 +21,8 @@ inline QString qS(const std::string &string)
|
||||
return QString::fromStdString(string);
|
||||
}
|
||||
|
||||
const Qt::KeyboardModifiers showSplitOverlayModifiers = Qt::ControlModifier | Qt::AltModifier;
|
||||
const Qt::KeyboardModifiers showAddSplitRegions = Qt::ControlModifier | Qt::AltModifier;
|
||||
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
#include "account.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace chatterino {
|
||||
namespace controllers {
|
||||
namespace accounts {
|
||||
|
||||
Account::Account(const QString &category)
|
||||
Account::Account(ProviderId _providerId)
|
||||
: providerId(_providerId)
|
||||
{
|
||||
static QString twitch("Twitch");
|
||||
|
||||
this->category = [&]() {
|
||||
switch (_providerId) {
|
||||
case ProviderId::Twitch:
|
||||
return twitch;
|
||||
}
|
||||
return QString("Unknown ProviderId");
|
||||
}();
|
||||
}
|
||||
|
||||
const QString &Account::getCategory() const
|
||||
@@ -13,16 +25,17 @@ const QString &Account::getCategory() const
|
||||
return this->category;
|
||||
}
|
||||
|
||||
ProviderId Account::getProviderId() const
|
||||
{
|
||||
return this->providerId;
|
||||
}
|
||||
|
||||
bool Account::operator<(const Account &other) const
|
||||
{
|
||||
if (this->category < other.category) {
|
||||
return true;
|
||||
} else if (this->category == other.category) {
|
||||
if (this->toString() < other.toString()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
QString a = this->toString();
|
||||
QString b = other.toString();
|
||||
|
||||
return std::tie(this->category, a) < std::tie(other.category, b);
|
||||
}
|
||||
|
||||
} // namespace accounts
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "providerid.hpp"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
@@ -9,14 +11,17 @@ namespace accounts {
|
||||
class Account
|
||||
{
|
||||
public:
|
||||
Account(const QString &category);
|
||||
Account(ProviderId providerId);
|
||||
virtual ~Account() = default;
|
||||
|
||||
virtual QString toString() const = 0;
|
||||
const QString &getCategory() const;
|
||||
ProviderId getProviderId() const;
|
||||
|
||||
bool operator<(const Account &other) const;
|
||||
|
||||
private:
|
||||
ProviderId providerId;
|
||||
QString category;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,13 +8,42 @@ namespace accounts {
|
||||
|
||||
AccountController::AccountController()
|
||||
{
|
||||
this->twitch.accounts.itemInserted.connect([this](const auto &args) {
|
||||
this->accounts.insertItem(std::dynamic_pointer_cast<Account>(args.item));
|
||||
});
|
||||
|
||||
this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
|
||||
if (args.caller != this) {
|
||||
auto &accs = this->twitch.accounts.getVector();
|
||||
auto it = std::find(accs.begin(), accs.end(), args.item);
|
||||
assert(it != accs.end());
|
||||
|
||||
this->accounts.removeItem(it - accs.begin());
|
||||
}
|
||||
});
|
||||
|
||||
this->accounts.itemRemoved.connect([this](const auto &args) {
|
||||
switch (args.item->getProviderId()) {
|
||||
case ProviderId::Twitch: {
|
||||
auto &accs = this->twitch.accounts.getVector();
|
||||
auto it = std::find(accs.begin(), accs.end(), args.item);
|
||||
assert(it != accs.end());
|
||||
|
||||
this->twitch.accounts.removeItem(it - accs.begin(), this);
|
||||
} break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void AccountController::load()
|
||||
{
|
||||
this->twitch.load();
|
||||
}
|
||||
|
||||
AccountModel *AccountController::createModel(QObject *parent)
|
||||
{
|
||||
AccountModel *model = new AccountModel(parent);
|
||||
|
||||
//(util::BaseSignalVector<stdAccount> *)
|
||||
model->init(&this->accounts);
|
||||
return model;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <QObject>
|
||||
|
||||
#include "controllers/accounts/account.hpp"
|
||||
#include "providers/twitch/twitchaccountmanager.hpp"
|
||||
#include "util/sharedptrelementless.hpp"
|
||||
#include "util/signalvector2.hpp"
|
||||
|
||||
@@ -19,6 +20,10 @@ public:
|
||||
|
||||
AccountModel *createModel(QObject *parent);
|
||||
|
||||
void load();
|
||||
|
||||
providers::twitch::TwitchAccountManager twitch;
|
||||
|
||||
private:
|
||||
util::SortedSignalVector<std::shared_ptr<Account>, util::SharedPtrElementLess<Account>>
|
||||
accounts;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "accountmodel.hpp"
|
||||
|
||||
#include "util/standarditemhelper.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace controllers {
|
||||
namespace accounts {
|
||||
@@ -10,16 +12,49 @@ AccountModel::AccountModel(QObject *parent)
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
std::shared_ptr<Account> AccountModel::getItemFromRow(std::vector<QStandardItem *> &row)
|
||||
std::shared_ptr<Account> AccountModel::getItemFromRow(std::vector<QStandardItem *> &,
|
||||
const std::shared_ptr<Account> &original)
|
||||
{
|
||||
return nullptr;
|
||||
return original;
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void AccountModel::getRowFromItem(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
row[0]->setData(item->toString(), Qt::DisplayRole);
|
||||
util::setStringItem(row[0], item->toString(), false);
|
||||
row[0]->setData(QFont("Segoe UI", 10), Qt::FontRole);
|
||||
}
|
||||
|
||||
int AccountModel::beforeInsert(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row, int proposedIndex)
|
||||
{
|
||||
if (this->categoryCount[item->getCategory()]++ == 0) {
|
||||
auto row = this->createRow();
|
||||
|
||||
util::setStringItem(row[0], item->getCategory(), false, false);
|
||||
row[0]->setData(QFont("Segoe UI Light", 16), Qt::FontRole);
|
||||
|
||||
this->insertCustomRow(std::move(row), proposedIndex);
|
||||
|
||||
return proposedIndex + 1;
|
||||
}
|
||||
|
||||
return proposedIndex;
|
||||
}
|
||||
|
||||
void AccountModel::afterRemoved(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row, int index)
|
||||
{
|
||||
auto it = this->categoryCount.find(item->getCategory());
|
||||
assert(it != this->categoryCount.end());
|
||||
|
||||
if (it->second <= 1) {
|
||||
this->categoryCount.erase(it);
|
||||
this->removeCustomRow(index - 1);
|
||||
} else {
|
||||
it->second--;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace accounts
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "controllers/accounts/account.hpp"
|
||||
#include "util/qstringhash.hpp"
|
||||
#include "util/signalvectormodel.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace chatterino {
|
||||
namespace controllers {
|
||||
namespace accounts {
|
||||
@@ -16,13 +19,23 @@ public:
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual std::shared_ptr<Account> getItemFromRow(std::vector<QStandardItem *> &row) override;
|
||||
virtual std::shared_ptr<Account> getItemFromRow(
|
||||
std::vector<QStandardItem *> &row, const std::shared_ptr<Account> &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row) override;
|
||||
|
||||
virtual int beforeInsert(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row, int proposedIndex) override;
|
||||
|
||||
virtual void afterRemoved(const std::shared_ptr<Account> &item,
|
||||
std::vector<QStandardItem *> &row, int index) override;
|
||||
|
||||
friend class AccountController;
|
||||
|
||||
private:
|
||||
std::unordered_map<QString, int> categoryCount;
|
||||
};
|
||||
|
||||
} // namespace accounts
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "commandcontroller.hpp"
|
||||
|
||||
#include "application.hpp"
|
||||
#include "controllers/accounts/accountcontroller.hpp"
|
||||
#include "controllers/commands/command.hpp"
|
||||
#include "controllers/commands/commandmodel.hpp"
|
||||
#include "messages/messagebuilder.hpp"
|
||||
#include "providers/twitch/twitchchannel.hpp"
|
||||
#include "providers/twitch/twitchserver.hpp"
|
||||
#include "singletons/accountmanager.hpp"
|
||||
#include "singletons/pathmanager.hpp"
|
||||
#include "util/signalvector2.hpp"
|
||||
|
||||
@@ -110,7 +110,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||
|
||||
return "";
|
||||
} else if (commandName == "/uptime") {
|
||||
const auto &streamStatus = twitchChannel->GetStreamStatus();
|
||||
const auto &streamStatus = twitchChannel->getStreamStatus();
|
||||
|
||||
QString messageText =
|
||||
streamStatus.live ? streamStatus.uptime : "Channel is not live.";
|
||||
@@ -121,7 +121,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||
} else if (commandName == "/ignore" && words.size() >= 2) {
|
||||
auto app = getApp();
|
||||
|
||||
auto user = app->accounts->Twitch.getCurrent();
|
||||
auto user = app->accounts->twitch.getCurrent();
|
||||
auto target = words.at(1);
|
||||
|
||||
if (user->isAnon()) {
|
||||
@@ -138,7 +138,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||
} else if (commandName == "/unignore" && words.size() >= 2) {
|
||||
auto app = getApp();
|
||||
|
||||
auto user = app->accounts->Twitch.getCurrent();
|
||||
auto user = app->accounts->twitch.getCurrent();
|
||||
auto target = words.at(1);
|
||||
|
||||
if (user->isAnon()) {
|
||||
@@ -161,7 +161,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||
|
||||
messages::MessageBuilder b;
|
||||
|
||||
b.emplace<messages::TextElement>(app->accounts->Twitch.getCurrent()->getUserName(),
|
||||
b.emplace<messages::TextElement>(app->accounts->twitch.getCurrent()->getUserName(),
|
||||
messages::MessageElement::Text);
|
||||
b.emplace<messages::TextElement>("->", messages::MessageElement::Text);
|
||||
b.emplace<messages::TextElement>(words[1], messages::MessageElement::Text);
|
||||
@@ -169,7 +169,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
|
||||
QString rest = "";
|
||||
|
||||
for (int i = 2; i < words.length(); i++) {
|
||||
rest += words[i];
|
||||
rest += words[i] + " ";
|
||||
}
|
||||
|
||||
b.emplace<messages::TextElement>(rest, messages::MessageElement::Text);
|
||||
|
||||
@@ -11,7 +11,7 @@ CommandModel::CommandModel(QObject *parent)
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row)
|
||||
Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row, const Command &original)
|
||||
{
|
||||
return Command(row[0]->data(Qt::EditRole).toString(), row[1]->data(Qt::EditRole).toString());
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ class CommandModel : public util::SignalVectorModel<Command>
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual Command getItemFromRow(std::vector<QStandardItem *> &row) override;
|
||||
virtual Command getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const Command &command) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const Command &item, std::vector<QStandardItem *> &row) override;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "application.hpp"
|
||||
#include "controllers/highlights/highlightmodel.hpp"
|
||||
#include "widgets/notificationpopup.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace controllers {
|
||||
@@ -21,7 +22,6 @@ void HighlightController::initialize()
|
||||
}
|
||||
|
||||
this->phrases.delayedItemsChanged.connect([this] { //
|
||||
int xd = this->phrases.getVector().size();
|
||||
this->highlightsSetting.setValue(this->phrases.getVector());
|
||||
});
|
||||
}
|
||||
@@ -34,6 +34,15 @@ HighlightModel *HighlightController::createModel(QObject *parent)
|
||||
return model;
|
||||
}
|
||||
|
||||
void HighlightController::addHighlight(const messages::MessagePtr &msg)
|
||||
{
|
||||
// static widgets::NotificationPopup popup;
|
||||
|
||||
// popup.updatePosition();
|
||||
// popup.addMessage(msg);
|
||||
// popup.show();
|
||||
}
|
||||
|
||||
} // namespace highlights
|
||||
} // namespace controllers
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "controllers/highlights/highlightphrase.hpp"
|
||||
#include "messages/message.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
#include "util/signalvector2.hpp"
|
||||
|
||||
@@ -21,6 +22,8 @@ public:
|
||||
|
||||
HighlightModel *createModel(QObject *parent);
|
||||
|
||||
void addHighlight(const messages::MessagePtr &msg);
|
||||
|
||||
private:
|
||||
bool initialized = false;
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ HighlightModel::HighlightModel(QObject *parent)
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
HighlightPhrase HighlightModel::getItemFromRow(std::vector<QStandardItem *> &row)
|
||||
HighlightPhrase HighlightModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const HighlightPhrase &original)
|
||||
{
|
||||
// key, alert, sound, regex
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ class HighlightModel : public util::SignalVectorModel<HighlightPhrase>
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row) override;
|
||||
virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const HighlightPhrase &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const HighlightPhrase &item,
|
||||
|
||||
@@ -15,7 +15,8 @@ IgnoreModel::IgnoreModel(QObject *parent)
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
IgnorePhrase IgnoreModel::getItemFromRow(std::vector<QStandardItem *> &row)
|
||||
IgnorePhrase IgnoreModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const IgnorePhrase &original)
|
||||
{
|
||||
// key, regex
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ class IgnoreModel : public util::SignalVectorModel<IgnorePhrase>
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual IgnorePhrase getItemFromRow(std::vector<QStandardItem *> &row) override;
|
||||
virtual IgnorePhrase getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const IgnorePhrase &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const IgnorePhrase &item,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "taggeduser.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace chatterino {
|
||||
namespace controllers {
|
||||
namespace taggedusers {
|
||||
|
||||
TaggedUser::TaggedUser(ProviderId _provider, const QString &_name, const QString &_id)
|
||||
: provider(_provider)
|
||||
, name(_name)
|
||||
, id(_id)
|
||||
{
|
||||
}
|
||||
|
||||
bool TaggedUser::operator<(const TaggedUser &other) const
|
||||
{
|
||||
return std::tie(this->provider, this->name, this->id) <
|
||||
std::tie(other.provider, other.name, other.id);
|
||||
}
|
||||
|
||||
} // namespace taggedusers
|
||||
} // namespace controllers
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <providerid.hpp>
|
||||
|
||||
namespace chatterino {
|
||||
namespace controllers {
|
||||
namespace taggedusers {
|
||||
|
||||
class TaggedUser
|
||||
{
|
||||
public:
|
||||
TaggedUser(ProviderId provider, const QString &name, const QString &id);
|
||||
|
||||
bool operator<(const TaggedUser &other) const;
|
||||
|
||||
ProviderId provider;
|
||||
QString name;
|
||||
QString id;
|
||||
};
|
||||
|
||||
} // namespace taggedusers
|
||||
} // namespace controllers
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,23 @@
|
||||
#include "taggeduserscontroller.hpp"
|
||||
|
||||
#include "controllers/taggedusers/taggedusersmodel.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace controllers {
|
||||
namespace taggedusers {
|
||||
|
||||
TaggedUsersController::TaggedUsersController()
|
||||
{
|
||||
}
|
||||
|
||||
TaggedUsersModel *TaggedUsersController::createModel(QObject *parent)
|
||||
{
|
||||
TaggedUsersModel *model = new TaggedUsersModel(parent);
|
||||
model->init(&this->users);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
} // namespace taggedusers
|
||||
} // namespace controllers
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "controllers/taggedusers/taggeduser.hpp"
|
||||
#include "util/signalvector2.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace controllers {
|
||||
namespace taggedusers {
|
||||
|
||||
class TaggedUsersModel;
|
||||
|
||||
class TaggedUsersController
|
||||
{
|
||||
public:
|
||||
TaggedUsersController();
|
||||
|
||||
util::SortedSignalVector<TaggedUser, std::less<TaggedUser>> users;
|
||||
|
||||
TaggedUsersModel *createModel(QObject *parent = nullptr);
|
||||
};
|
||||
|
||||
} // namespace taggedusers
|
||||
} // namespace controllers
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "taggedusersmodel.hpp"
|
||||
|
||||
#include "application.hpp"
|
||||
#include "util/standarditemhelper.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace controllers {
|
||||
namespace taggedusers {
|
||||
|
||||
// commandmodel
|
||||
TaggedUsersModel::TaggedUsersModel(QObject *parent)
|
||||
: util::SignalVectorModel<TaggedUser>(1, parent)
|
||||
{
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
TaggedUser TaggedUsersModel::getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const TaggedUser &original)
|
||||
{
|
||||
return original;
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void TaggedUsersModel::getRowFromItem(const TaggedUser &item, std::vector<QStandardItem *> &row)
|
||||
{
|
||||
util::setStringItem(row[0], item.name);
|
||||
}
|
||||
|
||||
void TaggedUsersModel::afterInit()
|
||||
{
|
||||
// std::vector<QStandardItem *> row = this->createRow();
|
||||
// util::setBoolItem(row[0], getApp()->settings->enableHighlightsSelf.getValue(), true,
|
||||
// false); row[0]->setData("Your username (automatic)", Qt::DisplayRole);
|
||||
// util::setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(), true,
|
||||
// false); util::setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(),
|
||||
// true, false); row[3]->setFlags(0); this->insertCustomRow(row, 0);
|
||||
}
|
||||
|
||||
// void TaggedUserModel::customRowSetData(const std::vector<QStandardItem *> &row, int column,
|
||||
// const QVariant &value, int role)
|
||||
//{
|
||||
// switch (column) {
|
||||
// case 0: {
|
||||
// if (role == Qt::CheckStateRole) {
|
||||
// getApp()->settings->enableHighlightsSelf.setValue(value.toBool());
|
||||
// }
|
||||
// } break;
|
||||
// case 1: {
|
||||
// if (role == Qt::CheckStateRole) {
|
||||
// getApp()->settings->enableHighlightTaskbar.setValue(value.toBool());
|
||||
// }
|
||||
// } break;
|
||||
// case 2: {
|
||||
// if (role == Qt::CheckStateRole) {
|
||||
// getApp()->settings->enableHighlightSound.setValue(value.toBool());
|
||||
// }
|
||||
// } break;
|
||||
// case 3: {
|
||||
// // empty element
|
||||
// } break;
|
||||
// }
|
||||
//}
|
||||
|
||||
} // namespace taggedusers
|
||||
} // namespace controllers
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "controllers/taggedusers/taggeduser.hpp"
|
||||
#include "util/signalvectormodel.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace controllers {
|
||||
namespace taggedusers {
|
||||
|
||||
class TaggedUsersController;
|
||||
|
||||
class TaggedUsersModel : public util::SignalVectorModel<TaggedUser>
|
||||
{
|
||||
explicit TaggedUsersModel(QObject *parent);
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual TaggedUser getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const TaggedUser &original) override;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const TaggedUser &item, std::vector<QStandardItem *> &row) override;
|
||||
|
||||
virtual void afterInit() override;
|
||||
|
||||
// virtual void customRowSetData(const std::vector<QStandardItem *> &row, int column,
|
||||
// const QVariant &value, int role) override;
|
||||
|
||||
friend class TaggedUsersController;
|
||||
};
|
||||
|
||||
} // namespace taggedusers
|
||||
} // namespace controllers
|
||||
} // namespace chatterino
|
||||
+108
-6
@@ -1,6 +1,7 @@
|
||||
#include "application.hpp"
|
||||
#include "singletons/nativemessagingmanager.hpp"
|
||||
#include "singletons/pathmanager.hpp"
|
||||
#include "singletons/updatemanager.hpp"
|
||||
#include "util/networkmanager.hpp"
|
||||
#include "widgets/lastruncrashdialog.hpp"
|
||||
|
||||
@@ -9,6 +10,7 @@
|
||||
#include <QFile>
|
||||
#include <QLibrary>
|
||||
#include <QStringList>
|
||||
#include <QStyleFactory>
|
||||
|
||||
#ifdef USEWINSDK
|
||||
#include "util/nativeeventhelper.hpp"
|
||||
@@ -17,7 +19,15 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
int runGui(int argc, char *argv[]);
|
||||
void runNativeMessagingHost();
|
||||
void installCustomPalette();
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
@@ -30,7 +40,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
// TODO: can be any argument
|
||||
if (args.size() > 0 && args[0].startsWith("chrome-extension://")) {
|
||||
chatterino::Application::runNativeMessagingHost();
|
||||
runNativeMessagingHost();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -47,14 +57,16 @@ int runGui(int argc, char *argv[])
|
||||
// QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL, true);
|
||||
QApplication a(argc, argv);
|
||||
|
||||
// Install native event handler for hidpi on windows
|
||||
#ifdef USEWINSDK
|
||||
a.installNativeEventFilter(new chatterino::util::DpiNativeEventFilter);
|
||||
#endif
|
||||
QApplication::setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
installCustomPalette();
|
||||
|
||||
// Initialize NetworkManager
|
||||
chatterino::util::NetworkManager::init();
|
||||
|
||||
// Check for upates
|
||||
chatterino::singletons::UpdateManager::getInstance().checkForUpdates();
|
||||
|
||||
// Initialize application
|
||||
chatterino::Application::instantiate(argc, argv);
|
||||
auto app = chatterino::getApp();
|
||||
@@ -68,7 +80,14 @@ int runGui(int argc, char *argv[])
|
||||
if (QFile::exists(runningPath)) {
|
||||
#ifndef DISABLE_CRASH_DIALOG
|
||||
chatterino::widgets::LastRunCrashDialog dialog;
|
||||
dialog.exec();
|
||||
|
||||
switch (dialog.exec()) {
|
||||
case QDialog::Accepted: {
|
||||
}; break;
|
||||
default: {
|
||||
_exit(0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
QFile runningFile(runningPath);
|
||||
@@ -98,3 +117,86 @@ int runGui(int argc, char *argv[])
|
||||
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
void runNativeMessagingHost()
|
||||
{
|
||||
auto *nm = new chatterino::singletons::NativeMessagingManager;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
_setmode(_fileno(stdin), _O_BINARY);
|
||||
_setmode(_fileno(stdout), _O_BINARY);
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
bool bigEndian = isBigEndian();
|
||||
#endif
|
||||
|
||||
std::atomic<bool> ping(false);
|
||||
|
||||
QTimer timer;
|
||||
QObject::connect(&timer, &QTimer::timeout, [&ping] {
|
||||
if (!ping.exchange(false)) {
|
||||
_exit(0);
|
||||
}
|
||||
});
|
||||
timer.setInterval(11000);
|
||||
timer.start();
|
||||
|
||||
while (true) {
|
||||
char size_c[4];
|
||||
std::cin.read(size_c, 4);
|
||||
|
||||
if (std::cin.eof()) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t size = *reinterpret_cast<uint32_t *>(size_c);
|
||||
#if 0
|
||||
// To avoid breaking strict-aliasing rules and potentially inducing undefined behaviour, the following code can be run instead
|
||||
uint32_t size = 0;
|
||||
if (bigEndian) {
|
||||
size = size_c[3] | static_cast<uint32_t>(size_c[2]) << 8 |
|
||||
static_cast<uint32_t>(size_c[1]) << 16 | static_cast<uint32_t>(size_c[0]) << 24;
|
||||
} else {
|
||||
size = size_c[0] | static_cast<uint32_t>(size_c[1]) << 8 |
|
||||
static_cast<uint32_t>(size_c[2]) << 16 | static_cast<uint32_t>(size_c[3]) << 24;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unique_ptr<char[]> b(new char[size + 1]);
|
||||
std::cin.read(b.get(), size);
|
||||
*(b.get() + size) = '\0';
|
||||
|
||||
nm->sendToGuiProcess(QByteArray::fromRawData(b.get(), static_cast<int32_t>(size)));
|
||||
}
|
||||
}
|
||||
|
||||
void installCustomPalette()
|
||||
{
|
||||
// borrowed from
|
||||
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
|
||||
QPalette darkPalette = qApp->palette();
|
||||
|
||||
darkPalette.setColor(QPalette::Window, QColor(22, 22, 22));
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127));
|
||||
darkPalette.setColor(QPalette::Base, QColor("#333"));
|
||||
darkPalette.setColor(QPalette::AlternateBase, QColor("#444"));
|
||||
darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127));
|
||||
darkPalette.setColor(QPalette::Dark, QColor(35, 35, 35));
|
||||
darkPalette.setColor(QPalette::Shadow, QColor(20, 20, 20));
|
||||
darkPalette.setColor(QPalette::Button, QColor(70, 70, 70));
|
||||
darkPalette.setColor(QPalette::ButtonText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127));
|
||||
darkPalette.setColor(QPalette::BrightText, Qt::red);
|
||||
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
|
||||
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80));
|
||||
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127));
|
||||
|
||||
qApp->setPalette(darkPalette);
|
||||
}
|
||||
|
||||
@@ -147,10 +147,10 @@ void Image::loadImage()
|
||||
loadedEventQueued = true;
|
||||
|
||||
QTimer::singleShot(500, [] {
|
||||
getApp()->emotes->incGeneration();
|
||||
getApp()->windows->incGeneration();
|
||||
|
||||
auto app = getApp();
|
||||
app->windows->layoutVisibleChatWidgets();
|
||||
app->windows->layoutChannelViews();
|
||||
loadedEventQueued = false;
|
||||
});
|
||||
}
|
||||
@@ -240,7 +240,8 @@ int Image::getWidth() const
|
||||
|
||||
int Image::getScaledWidth() const
|
||||
{
|
||||
return static_cast<int>(this->getWidth() * this->scale);
|
||||
return static_cast<int>((float)this->getWidth() * this->scale *
|
||||
getApp()->settings->emoteScale.getValue());
|
||||
}
|
||||
|
||||
int Image::getHeight() const
|
||||
@@ -253,7 +254,8 @@ int Image::getHeight() const
|
||||
|
||||
int Image::getScaledHeight() const
|
||||
{
|
||||
return static_cast<int>(this->getHeight() * this->scale);
|
||||
return static_cast<int>((float)this->getHeight() * this->scale *
|
||||
getApp()->settings->emoteScale.getValue());
|
||||
}
|
||||
|
||||
} // namespace messages
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "application.hpp"
|
||||
#include "singletons/emotemanager.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
#include "singletons/windowmanager.hpp"
|
||||
#include "util/benchmark.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
@@ -20,9 +22,9 @@ namespace chatterino {
|
||||
namespace messages {
|
||||
namespace layouts {
|
||||
|
||||
MessageLayout::MessageLayout(MessagePtr _message)
|
||||
: message(_message)
|
||||
, buffer(nullptr)
|
||||
MessageLayout::MessageLayout(MessagePtr message)
|
||||
: message_(message)
|
||||
, buffer_(nullptr)
|
||||
{
|
||||
util::DebugCount::increase("message layout");
|
||||
}
|
||||
@@ -34,119 +36,116 @@ MessageLayout::~MessageLayout()
|
||||
|
||||
Message *MessageLayout::getMessage()
|
||||
{
|
||||
return this->message.get();
|
||||
return this->message_.get();
|
||||
}
|
||||
|
||||
// Height
|
||||
int MessageLayout::getHeight() const
|
||||
{
|
||||
return container.getHeight();
|
||||
return container_.getHeight();
|
||||
}
|
||||
|
||||
// Layout
|
||||
// return true if redraw is required
|
||||
bool MessageLayout::layout(int width, float scale, MessageElement::Flags flags)
|
||||
{
|
||||
// BenchmarkGuard benchmark("MessageLayout::layout()");
|
||||
|
||||
auto app = getApp();
|
||||
|
||||
bool layoutRequired = false;
|
||||
|
||||
// check if width changed
|
||||
bool widthChanged = width != this->currentLayoutWidth;
|
||||
bool widthChanged = width != this->currentLayoutWidth_;
|
||||
layoutRequired |= widthChanged;
|
||||
this->currentLayoutWidth = width;
|
||||
this->currentLayoutWidth_ = width;
|
||||
|
||||
// check if emotes changed
|
||||
bool imagesChanged = this->emoteGeneration != app->emotes->getGeneration();
|
||||
layoutRequired |= imagesChanged;
|
||||
this->emoteGeneration = app->emotes->getGeneration();
|
||||
|
||||
// check if text changed
|
||||
bool textChanged = this->fontGeneration != app->fonts->getGeneration();
|
||||
layoutRequired |= textChanged;
|
||||
this->fontGeneration = app->fonts->getGeneration();
|
||||
// check if layout state changed
|
||||
if (this->layoutState_ != app->windows->getGeneration()) {
|
||||
layoutRequired = true;
|
||||
this->flags |= RequiresBufferUpdate;
|
||||
this->layoutState_ = app->windows->getGeneration();
|
||||
}
|
||||
|
||||
// check if work mask changed
|
||||
bool wordMaskChanged = this->currentWordFlags != flags; // app->settings->getWordTypeMask();
|
||||
layoutRequired |= wordMaskChanged;
|
||||
this->currentWordFlags = flags; // app->settings->getWordTypeMask();
|
||||
layoutRequired |= this->currentWordFlags_ != flags;
|
||||
this->currentWordFlags_ = flags; // app->settings->getWordTypeMask();
|
||||
|
||||
// check if timestamp format changed
|
||||
bool timestampFormatChanged = this->timestampFormat != app->settings->timestampFormat;
|
||||
|
||||
layoutRequired |= timestampFormatChanged;
|
||||
// check if layout was requested manually
|
||||
layoutRequired |= bool(this->flags & RequiresLayout);
|
||||
this->flags &= decltype(RequiresLayout)(~RequiresLayout);
|
||||
|
||||
// check if dpi changed
|
||||
bool scaleChanged = this->scale != scale;
|
||||
layoutRequired |= scaleChanged;
|
||||
this->scale = scale;
|
||||
imagesChanged |= scaleChanged;
|
||||
textChanged |= scaleChanged;
|
||||
layoutRequired |= this->scale_ != scale;
|
||||
this->scale_ = scale;
|
||||
|
||||
// update word sizes if needed
|
||||
if (imagesChanged) {
|
||||
// this->container.updateImages();
|
||||
this->flags |= MessageLayout::RequiresBufferUpdate;
|
||||
}
|
||||
if (textChanged) {
|
||||
// this->container.updateText();
|
||||
this->flags |= MessageLayout::RequiresBufferUpdate;
|
||||
}
|
||||
if (widthChanged || wordMaskChanged) {
|
||||
this->deleteBuffer();
|
||||
}
|
||||
|
||||
// return if no layout is required
|
||||
if (!layoutRequired) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int oldHeight = this->container_.getHeight();
|
||||
this->actuallyLayout(width, flags);
|
||||
if (widthChanged || this->container_.getHeight() != oldHeight) {
|
||||
this->deleteBuffer();
|
||||
}
|
||||
this->invalidateBuffer();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MessageLayout::actuallyLayout(int width, MessageElement::Flags flags)
|
||||
void MessageLayout::actuallyLayout(int width, MessageElement::Flags _flags)
|
||||
{
|
||||
this->container.begin(width, this->scale, this->message->flags.value);
|
||||
auto messageFlags = this->message_->flags.value;
|
||||
|
||||
for (const std::unique_ptr<MessageElement> &element : this->message->getElements()) {
|
||||
element->addToContainer(this->container, flags);
|
||||
if (this->flags & MessageLayout::Expanded ||
|
||||
(_flags & MessageElement::ModeratorTools &&
|
||||
!(this->message_->flags & Message::MessageFlags::Disabled))) {
|
||||
messageFlags = Message::MessageFlags(messageFlags & ~Message::MessageFlags::Collapsed);
|
||||
}
|
||||
|
||||
if (this->height != this->container.getHeight()) {
|
||||
this->container_.begin(width, this->scale_, messageFlags);
|
||||
|
||||
for (const std::unique_ptr<MessageElement> &element : this->message_->getElements()) {
|
||||
element->addToContainer(this->container_, _flags);
|
||||
}
|
||||
|
||||
if (this->height_ != this->container_.getHeight()) {
|
||||
this->deleteBuffer();
|
||||
}
|
||||
|
||||
this->container.end();
|
||||
this->height = this->container.getHeight();
|
||||
this->container_.end();
|
||||
this->height_ = this->container_.getHeight();
|
||||
|
||||
// collapsed state
|
||||
this->flags &= ~Flags::Collapsed;
|
||||
if (this->container_.isCollapsed()) {
|
||||
this->flags |= Flags::Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
// Painting
|
||||
void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection &selection,
|
||||
bool isLastReadMessage, bool isWindowFocused)
|
||||
void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
|
||||
Selection &selection, bool isLastReadMessage, bool isWindowFocused)
|
||||
{
|
||||
auto app = getApp();
|
||||
QPixmap *pixmap = this->buffer.get();
|
||||
QPixmap *pixmap = this->buffer_.get();
|
||||
|
||||
// create new buffer if required
|
||||
if (!pixmap) {
|
||||
#ifdef Q_OS_MACOS
|
||||
pixmap =
|
||||
new QPixmap((int)(this->container.getWidth() * painter.device()->devicePixelRatioF()),
|
||||
(int)(this->container.getHeight() * painter.device()->devicePixelRatioF()));
|
||||
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
|
||||
int(container_.getHeight() * painter.device()->devicePixelRatioF()));
|
||||
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
|
||||
#else
|
||||
pixmap = new QPixmap(this->container.getWidth(), std::max(16, this->container.getHeight()));
|
||||
pixmap = new QPixmap(width, std::max(16, this->container_.getHeight()));
|
||||
#endif
|
||||
|
||||
this->buffer = std::shared_ptr<QPixmap>(pixmap);
|
||||
this->bufferValid = false;
|
||||
this->buffer_ = std::shared_ptr<QPixmap>(pixmap);
|
||||
this->bufferValid_ = false;
|
||||
util::DebugCount::increase("message drawing buffers");
|
||||
}
|
||||
|
||||
if (!this->bufferValid || !selection.isEmpty()) {
|
||||
if (!this->bufferValid_ || !selection.isEmpty()) {
|
||||
this->updateBuffer(pixmap, messageIndex, selection);
|
||||
}
|
||||
|
||||
@@ -155,21 +154,21 @@ void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection
|
||||
// painter.drawPixmap(0, y, this->container.width, this->container.getHeight(), *pixmap);
|
||||
|
||||
// draw gif emotes
|
||||
this->container.paintAnimatedElements(painter, y);
|
||||
this->container_.paintAnimatedElements(painter, y);
|
||||
|
||||
// draw disabled
|
||||
if (this->message->flags.HasFlag(Message::Disabled)) {
|
||||
if (this->message_->flags.HasFlag(Message::Disabled)) {
|
||||
painter.fillRect(0, y, pixmap->width(), pixmap->height(), app->themes->messages.disabled);
|
||||
}
|
||||
|
||||
// draw selection
|
||||
if (!selection.isEmpty()) {
|
||||
this->container.paintSelection(painter, messageIndex, selection, y);
|
||||
this->container_.paintSelection(painter, messageIndex, selection, y);
|
||||
}
|
||||
|
||||
// draw message seperation line
|
||||
if (app->settings->seperateMessages.getValue()) {
|
||||
painter.fillRect(0, y + this->container.getHeight() - 1, this->container.getWidth(), 1,
|
||||
painter.fillRect(0, y, this->container_.getWidth(), 1,
|
||||
app->themes->splits.messageSeperator);
|
||||
}
|
||||
|
||||
@@ -180,14 +179,13 @@ void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection
|
||||
|
||||
QBrush brush(color, Qt::VerPattern);
|
||||
|
||||
painter.fillRect(0, y + this->container.getHeight() - 1, this->container.getWidth(), 1,
|
||||
brush);
|
||||
painter.fillRect(0, y + this->container_.getHeight() - 1, pixmap->width(), 1, brush);
|
||||
}
|
||||
|
||||
this->bufferValid = true;
|
||||
this->bufferValid_ = true;
|
||||
}
|
||||
|
||||
void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &selection)
|
||||
void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, Selection & /*selection*/)
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
@@ -197,8 +195,10 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &s
|
||||
|
||||
// draw background
|
||||
QColor backgroundColor;
|
||||
if (this->message->flags & Message::Highlighted) {
|
||||
if (this->message_->flags & Message::Highlighted) {
|
||||
backgroundColor = app->themes->messages.backgrounds.highlighted;
|
||||
} else if (this->message_->flags & Message::Subscription) {
|
||||
backgroundColor = app->themes->messages.backgrounds.subscription;
|
||||
} else if (app->settings->alternateMessageBackground.getValue() &&
|
||||
this->flags & MessageLayout::AlternateBackground) {
|
||||
backgroundColor = app->themes->messages.backgrounds.alternate;
|
||||
@@ -208,7 +208,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &s
|
||||
painter.fillRect(buffer->rect(), backgroundColor);
|
||||
|
||||
// draw message
|
||||
this->container.paintElements(painter);
|
||||
this->container_.paintElements(painter);
|
||||
|
||||
#ifdef FOURTF
|
||||
// debug
|
||||
@@ -226,15 +226,15 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &s
|
||||
|
||||
void MessageLayout::invalidateBuffer()
|
||||
{
|
||||
this->bufferValid = false;
|
||||
this->bufferValid_ = false;
|
||||
}
|
||||
|
||||
void MessageLayout::deleteBuffer()
|
||||
{
|
||||
if (this->buffer != nullptr) {
|
||||
if (this->buffer_ != nullptr) {
|
||||
util::DebugCount::decrease("message drawing buffers");
|
||||
|
||||
this->buffer = nullptr;
|
||||
this->buffer_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ void MessageLayout::deleteCache()
|
||||
this->deleteBuffer();
|
||||
|
||||
#ifdef XD
|
||||
this->container.clear();
|
||||
this->container_.clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -256,22 +256,22 @@ void MessageLayout::deleteCache()
|
||||
const MessageLayoutElement *MessageLayout::getElementAt(QPoint point)
|
||||
{
|
||||
// go through all words and return the first one that contains the point.
|
||||
return this->container.getElementAt(point);
|
||||
return this->container_.getElementAt(point);
|
||||
}
|
||||
|
||||
int MessageLayout::getLastCharacterIndex() const
|
||||
{
|
||||
return this->container.getLastCharacterIndex();
|
||||
return this->container_.getLastCharacterIndex();
|
||||
}
|
||||
|
||||
int MessageLayout::getSelectionIndex(QPoint position)
|
||||
{
|
||||
return this->container.getSelectionIndex(position);
|
||||
return this->container_.getSelectionIndex(position);
|
||||
}
|
||||
|
||||
void MessageLayout::addSelectionText(QString &str, int from, int to)
|
||||
{
|
||||
this->container.addSelectionText(str, from, to);
|
||||
this->container_.addSelectionText(str, from, to);
|
||||
}
|
||||
|
||||
} // namespace layouts
|
||||
|
||||
@@ -22,10 +22,12 @@ public:
|
||||
enum Flags : uint8_t {
|
||||
RequiresBufferUpdate = 1 << 1,
|
||||
RequiresLayout = 1 << 2,
|
||||
AlternateBackground = 1 << 3
|
||||
AlternateBackground = 1 << 3,
|
||||
Collapsed = 1 << 4,
|
||||
Expanded = 1 << 5,
|
||||
};
|
||||
|
||||
MessageLayout(MessagePtr message);
|
||||
MessageLayout(MessagePtr message_);
|
||||
~MessageLayout();
|
||||
|
||||
Message *getMessage();
|
||||
@@ -37,10 +39,10 @@ public:
|
||||
util::FlagsEnum<Flags> flags;
|
||||
|
||||
// Layout
|
||||
bool layout(int width, float scale, MessageElement::Flags flags);
|
||||
bool layout(int width, float scale_, MessageElement::Flags flags);
|
||||
|
||||
// Painting
|
||||
void paint(QPainter &painter, int y, int messageIndex, Selection &selection,
|
||||
void paint(QPainter &painter, int width, int y, int messageIndex, Selection &selection,
|
||||
bool isLastReadMessage, bool isWindowFocused);
|
||||
void invalidateBuffer();
|
||||
void deleteBuffer();
|
||||
@@ -50,30 +52,28 @@ public:
|
||||
const MessageLayoutElement *getElementAt(QPoint point);
|
||||
int getLastCharacterIndex() const;
|
||||
int getSelectionIndex(QPoint position);
|
||||
void addSelectionText(QString &str, int from, int to);
|
||||
void addSelectionText(QString &str, int from = 0, int to = INT_MAX);
|
||||
|
||||
// Misc
|
||||
bool isDisabled() const;
|
||||
|
||||
private:
|
||||
// variables
|
||||
MessagePtr message;
|
||||
MessageLayoutContainer container;
|
||||
std::shared_ptr<QPixmap> buffer = nullptr;
|
||||
bool bufferValid = false;
|
||||
MessagePtr message_;
|
||||
MessageLayoutContainer container_;
|
||||
std::shared_ptr<QPixmap> buffer_ = nullptr;
|
||||
bool bufferValid_ = false;
|
||||
|
||||
int height = 0;
|
||||
int height_ = 0;
|
||||
|
||||
int currentLayoutWidth = -1;
|
||||
int fontGeneration = -1;
|
||||
int emoteGeneration = -1;
|
||||
QString timestampFormat;
|
||||
float scale = -1;
|
||||
unsigned int bufferUpdatedCount = 0;
|
||||
int currentLayoutWidth_ = -1;
|
||||
int layoutState_ = -1;
|
||||
float scale_ = -1;
|
||||
unsigned int bufferUpdatedCount_ = 0;
|
||||
|
||||
MessageElement::Flags currentWordFlags = MessageElement::None;
|
||||
MessageElement::Flags currentWordFlags_ = MessageElement::None;
|
||||
|
||||
int collapsedHeight = 32;
|
||||
int collapsedHeight_ = 32;
|
||||
|
||||
// methods
|
||||
void actuallyLayout(int width, MessageElement::Flags flags);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <QPainter>
|
||||
|
||||
#define COMPACT_EMOTES_OFFSET 6
|
||||
#define MAX_UNCOLLAPSED_LINES (getApp()->settings->collpseMessagesMinLines.getValue())
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
@@ -36,6 +37,12 @@ void MessageLayoutContainer::begin(int _width, float _scale, Message::MessageFla
|
||||
this->width = _width;
|
||||
this->scale = _scale;
|
||||
this->flags = _flags;
|
||||
auto mediumFontMetrics = getApp()->fonts->getFontMetrics(FontStyle::ChatMedium, _scale);
|
||||
this->textLineHeight = mediumFontMetrics.height();
|
||||
this->spaceWidth = mediumFontMetrics.width(' ');
|
||||
this->dotdotdotWidth = mediumFontMetrics.width("...");
|
||||
this->_canAddMessages = true;
|
||||
this->_isCollapsed = false;
|
||||
}
|
||||
|
||||
void MessageLayoutContainer::clear()
|
||||
@@ -68,12 +75,12 @@ void MessageLayoutContainer::addElementNoLineBreak(MessageLayoutElement *element
|
||||
|
||||
bool MessageLayoutContainer::canAddElements()
|
||||
{
|
||||
return !(this->flags & Message::MessageFlags::Collapsed && line >= 3);
|
||||
return this->_canAddMessages;
|
||||
}
|
||||
|
||||
void MessageLayoutContainer::_addElement(MessageLayoutElement *element)
|
||||
void MessageLayoutContainer::_addElement(MessageLayoutElement *element, bool forceAdd)
|
||||
{
|
||||
if (!this->canAddElements()) {
|
||||
if (!this->canAddElements() && !forceAdd) {
|
||||
delete element;
|
||||
return;
|
||||
}
|
||||
@@ -129,6 +136,11 @@ void MessageLayoutContainer::breakLine()
|
||||
yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale;
|
||||
}
|
||||
|
||||
// if (element->getCreator().getFlags() & MessageElement::Badges) {
|
||||
if (element->getRect().height() < this->textLineHeight) {
|
||||
yExtra -= (this->textLineHeight - element->getRect().height()) / 2;
|
||||
}
|
||||
|
||||
element->setPosition(QPoint(element->getRect().x() + xOffset + this->margin.left,
|
||||
element->getRect().y() + this->lineHeight + yExtra));
|
||||
}
|
||||
@@ -146,6 +158,12 @@ void MessageLayoutContainer::breakLine()
|
||||
|
||||
this->lineStart = this->elements.size();
|
||||
// this->currentX = (int)(this->scale * 8);
|
||||
|
||||
if (this->canCollapse() && line + 1 >= MAX_UNCOLLAPSED_LINES) {
|
||||
this->_canAddMessages = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this->currentX = 0;
|
||||
this->currentY += this->lineHeight;
|
||||
this->height = this->currentY + (this->margin.bottom * this->scale);
|
||||
@@ -160,15 +178,32 @@ bool MessageLayoutContainer::atStartOfLine()
|
||||
|
||||
bool MessageLayoutContainer::fitsInLine(int _width)
|
||||
{
|
||||
return this->currentX + _width <= this->width - this->margin.left - this->margin.right;
|
||||
return this->currentX + _width <=
|
||||
(this->width - this->margin.left - this->margin.right -
|
||||
(this->line + 1 == MAX_UNCOLLAPSED_LINES ? this->dotdotdotWidth : 0));
|
||||
}
|
||||
|
||||
void MessageLayoutContainer::end()
|
||||
{
|
||||
if (!this->canAddElements()) {
|
||||
static TextElement dotdotdot("...", MessageElement::Collapsed, MessageColor::Link);
|
||||
static QString dotdotdotText("...");
|
||||
|
||||
auto *element = new TextLayoutElement(
|
||||
dotdotdot, dotdotdotText, QSize(this->dotdotdotWidth, this->textLineHeight),
|
||||
QColor("#00D80A"), FontStyle::ChatMediumBold, this->scale);
|
||||
|
||||
// getApp()->themes->messages.textColors.system
|
||||
this->_addElement(element, true);
|
||||
this->_isCollapsed = true;
|
||||
}
|
||||
|
||||
if (!this->atStartOfLine()) {
|
||||
this->breakLine();
|
||||
}
|
||||
|
||||
this->height += this->lineHeight;
|
||||
|
||||
if (this->lines.size() != 0) {
|
||||
this->lines[0].rect.setTop(-100000);
|
||||
this->lines.back().rect.setBottom(100000);
|
||||
@@ -177,6 +212,17 @@ void MessageLayoutContainer::end()
|
||||
}
|
||||
}
|
||||
|
||||
bool MessageLayoutContainer::canCollapse()
|
||||
{
|
||||
return getApp()->settings->collpseMessagesMinLines.getValue() > 0 &&
|
||||
this->flags & Message::MessageFlags::Collapsed;
|
||||
}
|
||||
|
||||
bool MessageLayoutContainer::isCollapsed()
|
||||
{
|
||||
return this->_isCollapsed;
|
||||
}
|
||||
|
||||
MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point)
|
||||
{
|
||||
for (std::unique_ptr<MessageLayoutElement> &element : this->elements) {
|
||||
@@ -429,8 +475,6 @@ void MessageLayoutContainer::addSelectionText(QString &str, int from, int to)
|
||||
for (std::unique_ptr<MessageLayoutElement> &ele : this->elements) {
|
||||
int c = ele->getSelectionIndexCount();
|
||||
|
||||
qDebug() << c;
|
||||
|
||||
if (first) {
|
||||
if (index + c > from) {
|
||||
ele->addCopyTextToString(str, from - index, to - index);
|
||||
|
||||
@@ -76,6 +76,8 @@ struct MessageLayoutContainer {
|
||||
int getLastCharacterIndex() const;
|
||||
void addSelectionText(QString &str, int from, int to);
|
||||
|
||||
bool isCollapsed();
|
||||
|
||||
private:
|
||||
struct Line {
|
||||
int startIndex;
|
||||
@@ -86,7 +88,7 @@ private:
|
||||
};
|
||||
|
||||
// helpers
|
||||
void _addElement(MessageLayoutElement *element);
|
||||
void _addElement(MessageLayoutElement *element, bool forceAdd = false);
|
||||
|
||||
// variables
|
||||
float scale = 1.f;
|
||||
@@ -100,6 +102,12 @@ private:
|
||||
size_t lineStart = 0;
|
||||
int lineHeight = 0;
|
||||
int spaceWidth = 4;
|
||||
int textLineHeight = 0;
|
||||
int dotdotdotWidth = 0;
|
||||
bool _canAddMessages = true;
|
||||
bool _isCollapsed = false;
|
||||
|
||||
bool canCollapse();
|
||||
|
||||
std::vector<std::unique_ptr<MessageLayoutElement>> elements;
|
||||
std::vector<Line> lines;
|
||||
|
||||
@@ -188,7 +188,7 @@ int TextLayoutElement::getMouseOverIndex(const QPoint &abs)
|
||||
|
||||
auto app = getApp();
|
||||
|
||||
QFontMetrics &metrics = app->fonts->getFontMetrics(this->style, this->scale);
|
||||
QFontMetrics metrics = app->fonts->getFontMetrics(this->style, this->scale);
|
||||
|
||||
int x = this->getRect().left();
|
||||
|
||||
@@ -209,7 +209,7 @@ int TextLayoutElement::getXFromIndex(int index)
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
QFontMetrics &metrics = app->fonts->getFontMetrics(this->style, this->scale);
|
||||
QFontMetrics metrics = app->fonts->getFontMetrics(this->style, this->scale);
|
||||
|
||||
if (index <= 0) {
|
||||
return this->getRect().left();
|
||||
|
||||
@@ -21,6 +21,8 @@ SBHighlight Message::getScrollBarHighlight() const
|
||||
{
|
||||
if (this->flags & Message::Highlighted) {
|
||||
return SBHighlight(SBHighlight::Highlight);
|
||||
} else if (this->flags & Message::Subscription) {
|
||||
return SBHighlight(SBHighlight::Subscription);
|
||||
}
|
||||
return SBHighlight();
|
||||
}
|
||||
@@ -32,7 +34,19 @@ MessagePtr Message::createSystemMessage(const QString &text)
|
||||
|
||||
message->addElement(new TimestampElement(QTime::currentTime()));
|
||||
message->addElement(new TextElement(text, MessageElement::Text, MessageColor::System));
|
||||
message->flags.EnableFlag(MessageFlags::System);
|
||||
message->flags |= MessageFlags::System;
|
||||
message->flags |= MessageFlags::DoNotTriggerNotification;
|
||||
message->searchText = text;
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
MessagePtr Message::createMessage(const QString &text)
|
||||
{
|
||||
MessagePtr message(new Message);
|
||||
|
||||
message->addElement(new TimestampElement(QTime::currentTime()));
|
||||
message->addElement(new TextElement(text, MessageElement::Text, MessageColor::Text));
|
||||
message->searchText = text;
|
||||
|
||||
return message;
|
||||
@@ -63,7 +77,7 @@ MessagePtr Message::createTimeoutMessage(const QString &username, const QString
|
||||
|
||||
if (reason.length() > 0) {
|
||||
text.append(": \"");
|
||||
text.append(util::ParseTagString(reason));
|
||||
text.append(util::parseTagString(reason));
|
||||
text.append("\"");
|
||||
}
|
||||
text.append(".");
|
||||
@@ -89,7 +103,6 @@ MessagePtr Message::createTimeoutMessage(const providers::twitch::BanAction &act
|
||||
|
||||
msg->timeoutUser = action.target.name;
|
||||
msg->count = count;
|
||||
msg->banAction.reset(new providers::twitch::BanAction(action));
|
||||
|
||||
QString text;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace messages {
|
||||
|
||||
struct Message {
|
||||
Message()
|
||||
: parseTime(QTime::currentTime())
|
||||
{
|
||||
util::DebugCount::increase("messages");
|
||||
}
|
||||
@@ -39,6 +40,8 @@ struct Message {
|
||||
Collapsed = (1 << 7),
|
||||
DisconnectedMessage = (1 << 8),
|
||||
Untimeout = (1 << 9),
|
||||
PubSub = (1 << 10),
|
||||
Subscription = (1 << 11),
|
||||
};
|
||||
|
||||
util::FlagsEnum<MessageFlags> flags;
|
||||
@@ -50,7 +53,6 @@ struct Message {
|
||||
QString localizedName;
|
||||
QString timeoutUser;
|
||||
|
||||
std::unique_ptr<providers::twitch::BanAction> banAction;
|
||||
uint32_t count = 1;
|
||||
|
||||
// Messages should not be added after the message is done initializing.
|
||||
@@ -65,6 +67,7 @@ private:
|
||||
|
||||
public:
|
||||
static std::shared_ptr<Message> createSystemMessage(const QString &text);
|
||||
static std::shared_ptr<Message> createMessage(const QString &text);
|
||||
|
||||
static std::shared_ptr<Message> createTimeoutMessage(const QString &username,
|
||||
const QString &durationInSeconds,
|
||||
|
||||
@@ -70,8 +70,8 @@ ImageElement::ImageElement(Image *_image, MessageElement::Flags flags)
|
||||
void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
|
||||
{
|
||||
if (_flags & this->getFlags()) {
|
||||
QSize size(this->image->getWidth() * this->image->getScale() * container.getScale(),
|
||||
this->image->getHeight() * this->image->getScale() * container.getScale());
|
||||
QSize size(this->image->getScaledWidth() * container.getScale(),
|
||||
this->image->getScaledHeight() * container.getScale());
|
||||
|
||||
container.addElement(
|
||||
(new ImageLayoutElement(*this, this->image, size))->setLink(this->getLink()));
|
||||
@@ -97,19 +97,10 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElem
|
||||
return;
|
||||
}
|
||||
|
||||
int quality = getApp()->settings->preferredEmoteQuality;
|
||||
Image *_image = this->data.getImage(container.getScale());
|
||||
|
||||
Image *_image;
|
||||
if (quality == 3 && this->data.image3x != nullptr) {
|
||||
_image = this->data.image3x;
|
||||
} else if (quality >= 2 && this->data.image2x != nullptr) {
|
||||
_image = this->data.image2x;
|
||||
} else {
|
||||
_image = this->data.image1x;
|
||||
}
|
||||
|
||||
QSize size((int)(container.getScale() * _image->getScaledWidth()),
|
||||
(int)(container.getScale() * _image->getScaledHeight()));
|
||||
QSize size(int(container.getScale() * _image->getScaledWidth()),
|
||||
int(container.getScale() * _image->getScaledHeight()));
|
||||
|
||||
container.addElement(
|
||||
(new ImageLayoutElement(*this, _image, size))->setLink(this->getLink()));
|
||||
@@ -139,7 +130,7 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
|
||||
auto app = getApp();
|
||||
|
||||
if (_flags & this->getFlags()) {
|
||||
QFontMetrics &metrics = app->fonts->getFontMetrics(this->style, container.getScale());
|
||||
QFontMetrics metrics = app->fonts->getFontMetrics(this->style, container.getScale());
|
||||
|
||||
for (Word &word : this->words) {
|
||||
auto getTextLayoutElement = [&](QString text, int width, bool trailingSpace) {
|
||||
@@ -242,7 +233,7 @@ TextElement *TimestampElement::formatTime(const QTime &time)
|
||||
|
||||
QString format = locale.toString(time, getApp()->settings->timestampFormat);
|
||||
|
||||
return new TextElement(format, Flags::Timestamp, MessageColor::System, FontStyle::Medium);
|
||||
return new TextElement(format, Flags::Timestamp, MessageColor::System, FontStyle::ChatMedium);
|
||||
}
|
||||
|
||||
// TWITCH MODERATION
|
||||
|
||||
@@ -158,7 +158,7 @@ class TextElement : public MessageElement
|
||||
public:
|
||||
TextElement(const QString &text, MessageElement::Flags flags,
|
||||
const MessageColor &color = MessageColor::Text,
|
||||
FontStyle style = FontStyle::Medium);
|
||||
FontStyle style = FontStyle::ChatMedium);
|
||||
~TextElement() override = default;
|
||||
|
||||
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
|
||||
@@ -169,7 +169,6 @@ public:
|
||||
// b) which size it wants
|
||||
class EmoteElement : public MessageElement
|
||||
{
|
||||
const util::EmoteData data;
|
||||
std::unique_ptr<TextElement> textElement;
|
||||
|
||||
public:
|
||||
@@ -177,6 +176,8 @@ public:
|
||||
~EmoteElement() override = default;
|
||||
|
||||
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
|
||||
|
||||
const util::EmoteData data;
|
||||
};
|
||||
|
||||
// contains a text, formated depending on the preferences
|
||||
|
||||
@@ -7,6 +7,7 @@ struct MessageParseArgs {
|
||||
bool disablePingSounds = false;
|
||||
bool isReceivedWhisper = false;
|
||||
bool isSentWhisper = false;
|
||||
bool trimSubscriberUsername = false;
|
||||
};
|
||||
|
||||
} // namespace messages
|
||||
|
||||
@@ -42,6 +42,11 @@ struct SelectionItem {
|
||||
{
|
||||
return this->messageIndex == b.messageIndex && this->charIndex == b.charIndex;
|
||||
}
|
||||
|
||||
bool operator!=(const SelectionItem &b) const
|
||||
{
|
||||
return this->operator==(b);
|
||||
}
|
||||
};
|
||||
|
||||
struct Selection {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace chatterino {
|
||||
enum class ProviderId { Twitch };
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "messages/limitedqueuesnapshot.hpp"
|
||||
#include "messages/message.hpp"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
using namespace chatterino::messages;
|
||||
|
||||
namespace chatterino {
|
||||
@@ -13,14 +15,14 @@ namespace irc {
|
||||
AbstractIrcServer::AbstractIrcServer()
|
||||
{
|
||||
// Initialize the connections
|
||||
this->writeConnection.reset(new Communi::IrcConnection);
|
||||
this->writeConnection.reset(new IrcConnection);
|
||||
this->writeConnection->moveToThread(QCoreApplication::instance()->thread());
|
||||
|
||||
QObject::connect(this->writeConnection.get(), &Communi::IrcConnection::messageReceived,
|
||||
[this](auto msg) { this->writeConnectionMessageReceived(msg); });
|
||||
|
||||
// Listen to read connection message signals
|
||||
this->readConnection.reset(new Communi::IrcConnection);
|
||||
this->readConnection.reset(new IrcConnection);
|
||||
this->readConnection->moveToThread(QCoreApplication::instance()->thread());
|
||||
|
||||
QObject::connect(this->readConnection.get(), &Communi::IrcConnection::messageReceived,
|
||||
@@ -31,9 +33,13 @@ AbstractIrcServer::AbstractIrcServer()
|
||||
[this] { this->onConnected(); });
|
||||
QObject::connect(this->readConnection.get(), &Communi::IrcConnection::disconnected,
|
||||
[this] { this->onDisconnected(); });
|
||||
|
||||
// listen to reconnect request
|
||||
this->readConnection->reconnectRequested.connect([this] { this->connect(); });
|
||||
// this->writeConnection->reconnectRequested.connect([this] { this->connect(); });
|
||||
}
|
||||
|
||||
Communi::IrcConnection *AbstractIrcServer::getReadConnection() const
|
||||
IrcConnection *AbstractIrcServer::getReadConnection() const
|
||||
{
|
||||
return this->readConnection.get();
|
||||
}
|
||||
@@ -231,7 +237,7 @@ void AbstractIrcServer::addFakeMessage(const QString &data)
|
||||
{
|
||||
auto fakeMessage = Communi::IrcMessage::fromData(data.toUtf8(), this->readConnection.get());
|
||||
|
||||
this->privateMessageReceived(qobject_cast<Communi::IrcPrivateMessage *>(fakeMessage));
|
||||
this->messageReceived(fakeMessage);
|
||||
}
|
||||
|
||||
void AbstractIrcServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
#include "channel.hpp"
|
||||
|
||||
#include <IrcConnection>
|
||||
#include <IrcMessage>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
#include <providers/irc/ircconnection2.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
@@ -19,7 +19,7 @@ public:
|
||||
virtual ~AbstractIrcServer() = default;
|
||||
|
||||
// connection
|
||||
Communi::IrcConnection *getReadConnection() const;
|
||||
IrcConnection *getReadConnection() const;
|
||||
|
||||
void connect();
|
||||
void disconnect();
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
// signals
|
||||
pajlada::Signals::NoArgSignal connected;
|
||||
pajlada::Signals::NoArgSignal disconnected;
|
||||
pajlada::Signals::Signal<Communi::IrcPrivateMessage *> onPrivateMessage;
|
||||
// pajlada::Signals::Signal<Communi::IrcPrivateMessage *> onPrivateMessage;
|
||||
|
||||
void addFakeMessage(const QString &data);
|
||||
|
||||
@@ -43,8 +43,7 @@ public:
|
||||
protected:
|
||||
AbstractIrcServer();
|
||||
|
||||
virtual void initializeConnection(Communi::IrcConnection *connection, bool isRead,
|
||||
bool isWrite) = 0;
|
||||
virtual void initializeConnection(IrcConnection *connection, bool isRead, bool isWrite) = 0;
|
||||
virtual std::shared_ptr<Channel> createChannel(const QString &channelName) = 0;
|
||||
|
||||
virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
|
||||
@@ -64,10 +63,12 @@ protected:
|
||||
private:
|
||||
void initConnection();
|
||||
|
||||
std::unique_ptr<Communi::IrcConnection> writeConnection = nullptr;
|
||||
std::unique_ptr<Communi::IrcConnection> readConnection = nullptr;
|
||||
std::unique_ptr<IrcConnection> writeConnection = nullptr;
|
||||
std::unique_ptr<IrcConnection> readConnection = nullptr;
|
||||
|
||||
std::mutex connectionMutex;
|
||||
|
||||
QTimer pingTimer;
|
||||
};
|
||||
|
||||
} // namespace irc
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "ircconnection2.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace providers {
|
||||
namespace irc {
|
||||
|
||||
IrcConnection::IrcConnection(QObject *parent)
|
||||
: Communi::IrcConnection(parent)
|
||||
{
|
||||
this->pingTimer_.setInterval(5000);
|
||||
this->pingTimer_.start();
|
||||
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
|
||||
if (!this->recentlyReceivedMessage_.load()) {
|
||||
this->sendRaw("PING");
|
||||
this->reconnectTimer_.start();
|
||||
}
|
||||
this->recentlyReceivedMessage_ = false;
|
||||
});
|
||||
|
||||
this->reconnectTimer_.setInterval(5000);
|
||||
this->reconnectTimer_.setSingleShot(true);
|
||||
QObject::connect(&this->reconnectTimer_, &QTimer::timeout,
|
||||
[this] { reconnectRequested.invoke(); });
|
||||
|
||||
QObject::connect(this, &Communi::IrcConnection::messageReceived, [this](Communi::IrcMessage *) {
|
||||
this->recentlyReceivedMessage_ = true;
|
||||
|
||||
if (this->reconnectTimer_.isActive()) {
|
||||
this->reconnectTimer_.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace irc
|
||||
} // namespace providers
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
|
||||
#include <IrcConnection>
|
||||
#include <QTimer>
|
||||
|
||||
namespace chatterino {
|
||||
namespace providers {
|
||||
namespace irc {
|
||||
|
||||
class IrcConnection : public Communi::IrcConnection
|
||||
{
|
||||
public:
|
||||
IrcConnection(QObject *parent = nullptr);
|
||||
|
||||
pajlada::Signals::NoArgSignal reconnectRequested;
|
||||
|
||||
private:
|
||||
QTimer pingTimer_;
|
||||
QTimer reconnectTimer_;
|
||||
std::atomic<bool> recentlyReceivedMessage_{true};
|
||||
};
|
||||
|
||||
} // namespace irc
|
||||
} // namespace providers
|
||||
} // namespace chatterino
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "ircmessagehandler.hpp"
|
||||
|
||||
#include "application.hpp"
|
||||
#include "controllers/highlights/highlightcontroller.hpp"
|
||||
#include "debug/log.hpp"
|
||||
#include "messages/limitedqueue.hpp"
|
||||
#include "messages/message.hpp"
|
||||
@@ -10,6 +11,9 @@
|
||||
#include "providers/twitch/twitchserver.hpp"
|
||||
#include "singletons/resourcemanager.hpp"
|
||||
#include "singletons/windowmanager.hpp"
|
||||
#include "util/irchelpers.hpp"
|
||||
|
||||
#include <IrcMessage>
|
||||
|
||||
using namespace chatterino::singletons;
|
||||
using namespace chatterino::messages;
|
||||
@@ -24,116 +28,145 @@ IrcMessageHandler &IrcMessageHandler::getInstance()
|
||||
return instance;
|
||||
}
|
||||
|
||||
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchServer &server)
|
||||
{
|
||||
this->addMessage(message, message->target(), message->content(), server, false);
|
||||
}
|
||||
|
||||
void IrcMessageHandler::addMessage(Communi::IrcMessage *message, const QString &target,
|
||||
const QString &content, TwitchServer &server, bool isSub)
|
||||
{
|
||||
QString channelName;
|
||||
if (!trimChannelName(target, channelName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto chan = server.getChannelOrEmpty(channelName);
|
||||
|
||||
if (chan->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
messages::MessageParseArgs args;
|
||||
if (isSub) {
|
||||
args.trimSubscriberUsername = true;
|
||||
}
|
||||
|
||||
TwitchMessageBuilder builder(chan.get(), message, content, args);
|
||||
|
||||
if (isSub || !builder.isIgnored()) {
|
||||
messages::MessagePtr msg = builder.build();
|
||||
|
||||
if (isSub) {
|
||||
msg->flags |= messages::Message::Subscription;
|
||||
msg->flags &= ~messages::Message::Highlighted;
|
||||
} else {
|
||||
if (msg->flags & messages::Message::Highlighted) {
|
||||
server.mentionsChannel->addMessage(msg);
|
||||
getApp()->highlights->addHighlight(msg);
|
||||
}
|
||||
}
|
||||
|
||||
chan->addMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message)
|
||||
{
|
||||
const auto &tags = message->tags();
|
||||
auto iterator = tags.find("room-id");
|
||||
auto app = getApp();
|
||||
|
||||
if (iterator != tags.end()) {
|
||||
auto roomID = iterator.value().toString();
|
||||
// get twitch channel
|
||||
QString chanName;
|
||||
if (!trimChannelName(message->parameter(0), chanName)) {
|
||||
return;
|
||||
}
|
||||
auto chan = app->twitch.server->getChannelOrEmpty(chanName);
|
||||
TwitchChannel *twitchChannel = dynamic_cast<twitch::TwitchChannel *>(chan.get());
|
||||
|
||||
QStringList words = QString(message->toData()).split("#");
|
||||
if (twitchChannel) {
|
||||
// room-id
|
||||
decltype(tags.find("xD")) it;
|
||||
|
||||
// ensure the format is valid
|
||||
if (words.length() < 2) {
|
||||
return;
|
||||
}
|
||||
if ((it = tags.find("room-id")) != tags.end()) {
|
||||
auto roomID = it.value().toString();
|
||||
|
||||
auto app = getApp();
|
||||
|
||||
QString channelName = words.at(1);
|
||||
|
||||
auto channel = app->twitch.server->getChannelOrEmpty(channelName);
|
||||
|
||||
if (channel->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto twitchChannel = dynamic_cast<twitch::TwitchChannel *>(channel.get())) {
|
||||
// set the room id of the channel
|
||||
twitchChannel->setRoomID(roomID);
|
||||
|
||||
app->resources->loadChannelData(roomID);
|
||||
}
|
||||
|
||||
app->resources->loadChannelData(roomID);
|
||||
// Room modes
|
||||
TwitchChannel::RoomModes roomModes = twitchChannel->getRoomModes();
|
||||
|
||||
if ((it = tags.find("emote-only")) != tags.end()) {
|
||||
roomModes.emoteOnly = it.value() == "1";
|
||||
}
|
||||
if ((it = tags.find("subs-only")) != tags.end()) {
|
||||
roomModes.submode = it.value() == "1";
|
||||
}
|
||||
if ((it = tags.find("slow")) != tags.end()) {
|
||||
roomModes.slowMode = it.value().toInt();
|
||||
}
|
||||
if ((it = tags.find("r9k")) != tags.end()) {
|
||||
roomModes.r9k = it.value() == "1";
|
||||
}
|
||||
if ((it = tags.find("broadcaster-lang")) != tags.end()) {
|
||||
roomModes.broadcasterLang = it.value().toString();
|
||||
}
|
||||
|
||||
twitchChannel->setRoomModes(roomModes);
|
||||
}
|
||||
}
|
||||
|
||||
void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
|
||||
{
|
||||
return;
|
||||
// // check parameter count
|
||||
// if (message->parameters().length() < 1) {
|
||||
// return;
|
||||
// }
|
||||
// check parameter count
|
||||
if (message->parameters().length() < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// QString chanName;
|
||||
// if (!TrimChannelName(message->parameter(0), chanName)) {
|
||||
// return;
|
||||
// }
|
||||
QString chanName;
|
||||
if (!trimChannelName(message->parameter(0), chanName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// auto app = getApp();
|
||||
auto app = getApp();
|
||||
|
||||
// // get channel
|
||||
// auto chan = app->twitch.server->getChannelOrEmpty(chanName);
|
||||
// get channel
|
||||
auto chan = app->twitch.server->getChannelOrEmpty(chanName);
|
||||
|
||||
// if (chan->isEmpty()) {
|
||||
// debug::Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not found",
|
||||
// chanName);
|
||||
// return;
|
||||
// }
|
||||
if (chan->isEmpty()) {
|
||||
debug::Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not found",
|
||||
chanName);
|
||||
return;
|
||||
}
|
||||
|
||||
// // check if the chat has been cleared by a moderator
|
||||
// if (message->parameters().length() == 1) {
|
||||
// chan->addMessage(Message::createSystemMessage("Chat has been cleared by a
|
||||
// moderator."));
|
||||
// check if the chat has been cleared by a moderator
|
||||
if (message->parameters().length() == 1) {
|
||||
chan->addMessage(Message::createSystemMessage("Chat has been cleared by a moderator."));
|
||||
|
||||
// return;
|
||||
// }
|
||||
return;
|
||||
}
|
||||
|
||||
// // get username, duration and message of the timed out user
|
||||
// QString username = message->parameter(1);
|
||||
// QString durationInSeconds, reason;
|
||||
// QVariant v = message->tag("ban-duration");
|
||||
// if (v.isValid()) {
|
||||
// durationInSeconds = v.toString();
|
||||
// }
|
||||
// get username, duration and message of the timed out user
|
||||
QString username = message->parameter(1);
|
||||
QString durationInSeconds, reason;
|
||||
QVariant v = message->tag("ban-duration");
|
||||
if (v.isValid()) {
|
||||
durationInSeconds = v.toString();
|
||||
}
|
||||
|
||||
// v = message->tag("ban-reason");
|
||||
// if (v.isValid()) {
|
||||
// reason = v.toString();
|
||||
// }
|
||||
v = message->tag("ban-reason");
|
||||
if (v.isValid()) {
|
||||
reason = v.toString();
|
||||
}
|
||||
|
||||
// // add the notice that the user has been timed out
|
||||
// LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot();
|
||||
// bool addMessage = true;
|
||||
// int snapshotLength = snapshot.getLength();
|
||||
auto timeoutMsg = Message::createTimeoutMessage(username, durationInSeconds, reason, false);
|
||||
chan->addOrReplaceTimeout(timeoutMsg);
|
||||
|
||||
// for (int i = std::max(0, snapshotLength - 20); i < snapshotLength; i++) {
|
||||
// auto &s = snapshot[i];
|
||||
// if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == username) {
|
||||
// MessagePtr replacement(
|
||||
// Message::createTimeoutMessage(username, durationInSeconds, reason, true));
|
||||
// chan->replaceMessage(s, replacement);
|
||||
// addMessage = false;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (addMessage) {
|
||||
// chan->addMessage(Message::createTimeoutMessage(username, durationInSeconds, reason,
|
||||
// false));
|
||||
// }
|
||||
|
||||
// // disable the messages from the user
|
||||
// for (int i = 0; i < snapshotLength; i++) {
|
||||
// auto &s = snapshot[i];
|
||||
// if (!(s->flags & Message::Timeout) && s->loginName == username) {
|
||||
// s->flags.EnableFlag(Message::Disabled);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // refresh all
|
||||
// app->windows->repaintVisibleChatWidgets(chan.get());
|
||||
// refresh all
|
||||
app->windows->repaintVisibleChatWidgets(chan.get());
|
||||
}
|
||||
|
||||
void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
|
||||
@@ -144,7 +177,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
|
||||
auto app = getApp();
|
||||
|
||||
QString channelName;
|
||||
if (!TrimChannelName(message->parameter(0), channelName)) {
|
||||
if (!trimChannelName(message->parameter(0), channelName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -180,6 +213,8 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
|
||||
app->twitch.server->mentionsChannel->addMessage(_message);
|
||||
}
|
||||
|
||||
app->twitch.server->lastUserThatWhisperedMe.set(builder.userName);
|
||||
|
||||
c->addMessage(_message);
|
||||
|
||||
if (app->settings->inlineWhispers) {
|
||||
@@ -190,9 +225,51 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
|
||||
}
|
||||
}
|
||||
|
||||
void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message)
|
||||
void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message, TwitchServer &server)
|
||||
{
|
||||
// do nothing
|
||||
auto data = message->toData();
|
||||
|
||||
auto tags = message->tags();
|
||||
auto parameters = message->parameters();
|
||||
|
||||
auto target = parameters[0];
|
||||
QString msgType = tags.value("msg-id", "").toString();
|
||||
QString content;
|
||||
if (parameters.size() >= 2) {
|
||||
content = parameters[1];
|
||||
}
|
||||
|
||||
if (msgType == "sub" || msgType == "resub" || msgType == "subgift") {
|
||||
// Sub-specific message. I think it's only allowed for "resub" messages atm
|
||||
if (!content.isEmpty()) {
|
||||
this->addMessage(message, target, content, server, true);
|
||||
}
|
||||
}
|
||||
|
||||
auto it = tags.find("system-msg");
|
||||
|
||||
if (it != tags.end()) {
|
||||
auto newMessage =
|
||||
messages::Message::createSystemMessage(util::parseTagString(it.value().toString()));
|
||||
|
||||
newMessage->flags |= messages::Message::Subscription;
|
||||
|
||||
QString channelName;
|
||||
|
||||
if (message->parameters().size() < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!trimChannelName(message->parameter(0), channelName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto chan = server.getChannelOrEmpty(channelName);
|
||||
|
||||
if (!chan->isEmpty()) {
|
||||
chan->addMessage(newMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IrcMessageHandler::handleModeMessage(Communi::IrcMessage *message)
|
||||
@@ -231,7 +308,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
|
||||
// auto channel = app->twitch.server->getChannelOrEmpty(channelName);
|
||||
|
||||
// if (channel->isEmpty()) {
|
||||
// debug::Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel manager",
|
||||
// debug::Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel
|
||||
// manager",
|
||||
// channelName);
|
||||
// return;
|
||||
// }
|
||||
@@ -257,6 +335,26 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(Communi::IrcNoticeMes
|
||||
this->handleNoticeMessage(message);
|
||||
}
|
||||
|
||||
void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
|
||||
{
|
||||
auto app = getApp();
|
||||
auto channel = app->twitch.server->getChannelOrEmpty(message->parameter(0).remove(0, 1));
|
||||
|
||||
if (TwitchChannel *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get())) {
|
||||
twitchChannel->addJoinedUser(message->nick());
|
||||
}
|
||||
}
|
||||
|
||||
void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message)
|
||||
{
|
||||
auto app = getApp();
|
||||
auto channel = app->twitch.server->getChannelOrEmpty(message->parameter(0).remove(0, 1));
|
||||
|
||||
if (TwitchChannel *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get())) {
|
||||
twitchChannel->addPartedUser(message->nick());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace twitch
|
||||
} // namespace providers
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace chatterino {
|
||||
namespace providers {
|
||||
namespace twitch {
|
||||
|
||||
class TwitchServer;
|
||||
|
||||
class IrcMessageHandler
|
||||
{
|
||||
IrcMessageHandler() = default;
|
||||
@@ -13,14 +15,23 @@ class IrcMessageHandler
|
||||
public:
|
||||
static IrcMessageHandler &getInstance();
|
||||
|
||||
void handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchServer &server);
|
||||
|
||||
void handleRoomStateMessage(Communi::IrcMessage *message);
|
||||
void handleClearChatMessage(Communi::IrcMessage *message);
|
||||
void handleUserStateMessage(Communi::IrcMessage *message);
|
||||
void handleWhisperMessage(Communi::IrcMessage *message);
|
||||
void handleUserNoticeMessage(Communi::IrcMessage *message);
|
||||
void handleUserNoticeMessage(Communi::IrcMessage *message, TwitchServer &server);
|
||||
void handleModeMessage(Communi::IrcMessage *message);
|
||||
void handleNoticeMessage(Communi::IrcNoticeMessage *message);
|
||||
void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message);
|
||||
|
||||
void handleJoinMessage(Communi::IrcMessage *message);
|
||||
void handlePartMessage(Communi::IrcMessage *message);
|
||||
|
||||
private:
|
||||
void addMessage(Communi::IrcMessage *message, const QString &target, const QString &content,
|
||||
TwitchServer &server, bool isResub);
|
||||
};
|
||||
|
||||
} // namespace twitch
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "debug/log.hpp"
|
||||
#include "providers/twitch/pubsubactions.hpp"
|
||||
#include "providers/twitch/pubsubhelpers.hpp"
|
||||
#include "singletons/accountmanager.hpp"
|
||||
#include "util/rapidjson-helpers.hpp"
|
||||
|
||||
#include <rapidjson/error/en.h>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "providers/twitch/pubsubhelpers.hpp"
|
||||
|
||||
#include "providers/twitch/pubsubactions.hpp"
|
||||
#include "singletons/accountmanager.hpp"
|
||||
#include "util/rapidjson-helpers.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace twitch {
|
||||
|
||||
TwitchAccount::TwitchAccount(const QString &_username, const QString &_oauthToken,
|
||||
const QString &_oauthClient, const QString &_userID)
|
||||
: controllers::accounts::Account("Twitch")
|
||||
: controllers::accounts::Account(ProviderId::Twitch)
|
||||
, oauthClient(_oauthClient)
|
||||
, oauthToken(_oauthToken)
|
||||
, userName(_username)
|
||||
|
||||
@@ -55,7 +55,6 @@ public:
|
||||
bool isAnon() const;
|
||||
|
||||
void loadIgnores();
|
||||
|
||||
void ignore(const QString &targetName,
|
||||
std::function<void(IgnoreResult, const QString &)> onFinished);
|
||||
void ignoreByID(const QString &targetUserID, const QString &targetName,
|
||||
|
||||
@@ -15,6 +15,9 @@ TwitchAccountManager::TwitchAccountManager()
|
||||
auto currentUser = this->getCurrent();
|
||||
currentUser->loadIgnores();
|
||||
});
|
||||
|
||||
this->accounts.itemRemoved.connect(
|
||||
[this](const auto &acc) { this->removeUser(acc.item.get()); });
|
||||
}
|
||||
|
||||
std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent()
|
||||
@@ -32,7 +35,7 @@ std::vector<QString> TwitchAccountManager::getUsernames() const
|
||||
|
||||
std::lock_guard<std::mutex> lock(this->mutex);
|
||||
|
||||
for (const auto &user : this->users) {
|
||||
for (const auto &user : this->accounts.getVector()) {
|
||||
userNames.push_back(user->getUserName());
|
||||
}
|
||||
|
||||
@@ -44,7 +47,7 @@ std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->mutex);
|
||||
|
||||
for (const auto &user : this->users) {
|
||||
for (const auto &user : this->accounts.getVector()) {
|
||||
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0) {
|
||||
return user;
|
||||
}
|
||||
@@ -134,25 +137,16 @@ void TwitchAccountManager::load()
|
||||
});
|
||||
}
|
||||
|
||||
bool TwitchAccountManager::removeUser(const QString &username)
|
||||
bool TwitchAccountManager::removeUser(TwitchAccount *account)
|
||||
{
|
||||
if (!this->userExists(username)) {
|
||||
return false;
|
||||
const auto &accs = this->accounts.getVector();
|
||||
|
||||
std::string userID(account->getUserId().toStdString());
|
||||
if (!userID.empty()) {
|
||||
pajlada::Settings::SettingManager::removeSetting("/accounts/uid" + userID);
|
||||
}
|
||||
|
||||
this->mutex.lock();
|
||||
this->users.erase(std::remove_if(this->users.begin(), this->users.end(), [username](auto user) {
|
||||
if (user->getUserName() == username) {
|
||||
std::string userID(user->getUserId().toStdString());
|
||||
assert(!userID.empty());
|
||||
pajlada::Settings::SettingManager::removeSetting("/accounts/uid" + userID);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
this->mutex.unlock();
|
||||
|
||||
if (username == qS(this->currentUsername.getValue())) {
|
||||
if (account->getUserName() == qS(this->currentUsername.getValue())) {
|
||||
// The user that was removed is the current user, log into the anonymous user
|
||||
this->currentUsername = "";
|
||||
}
|
||||
@@ -187,9 +181,9 @@ TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
|
||||
auto newUser = std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
|
||||
userData.clientID, userData.userID);
|
||||
|
||||
std::lock_guard<std::mutex> lock(this->mutex);
|
||||
// std::lock_guard<std::mutex> lock(this->mutex);
|
||||
|
||||
this->users.push_back(newUser);
|
||||
this->accounts.insertItem(newUser);
|
||||
|
||||
return AddUserResponse::UserAdded;
|
||||
}
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
//
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
namespace singletons {
|
||||
class AccountManager;
|
||||
} // namespace singletons
|
||||
namespace controllers {
|
||||
namespace accounts {
|
||||
class AccountController;
|
||||
}
|
||||
} // namespace controllers
|
||||
|
||||
namespace providers {
|
||||
namespace twitch {
|
||||
@@ -46,8 +47,6 @@ public:
|
||||
void reloadUsers();
|
||||
void load();
|
||||
|
||||
bool removeUser(const QString &username);
|
||||
|
||||
pajlada::Settings::Setting<std::string> currentUsername = {"/accounts/current", ""};
|
||||
pajlada::Signals::NoArgSignal currentUserChanged;
|
||||
pajlada::Signals::NoArgSignal userListUpdated;
|
||||
@@ -63,14 +62,14 @@ private:
|
||||
UserAdded,
|
||||
};
|
||||
AddUserResponse addUser(const UserData &data);
|
||||
bool removeUser(TwitchAccount *account);
|
||||
|
||||
std::shared_ptr<TwitchAccount> currentUser;
|
||||
|
||||
std::shared_ptr<TwitchAccount> anonymousUser;
|
||||
std::vector<std::shared_ptr<TwitchAccount>> users;
|
||||
mutable std::mutex mutex;
|
||||
|
||||
friend class chatterino::singletons::AccountManager;
|
||||
friend class chatterino::controllers::accounts::AccountController;
|
||||
};
|
||||
|
||||
} // namespace twitch
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include "messages/message.hpp"
|
||||
#include "providers/twitch/pubsub.hpp"
|
||||
#include "providers/twitch/twitchmessagebuilder.hpp"
|
||||
#include "singletons/accountmanager.hpp"
|
||||
#include "singletons/emotemanager.hpp"
|
||||
#include "singletons/ircmanager.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
@@ -45,7 +44,8 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
|
||||
this->refreshLiveStatus(); //
|
||||
});
|
||||
|
||||
this->managedConnect(app->accounts->Twitch.currentUserChanged, [this]() { this->setMod(false); });
|
||||
this->managedConnect(app->accounts->twitch.currentUserChanged,
|
||||
[this]() { this->setMod(false); });
|
||||
|
||||
auto refreshPubSubState = [=]() {
|
||||
if (!this->hasModRights()) {
|
||||
@@ -56,7 +56,7 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
|
||||
return;
|
||||
}
|
||||
|
||||
auto account = app->accounts->Twitch.getCurrent();
|
||||
auto account = app->accounts->twitch.getCurrent();
|
||||
if (account && !account->getUserId().isEmpty()) {
|
||||
app->twitch.pubsub->listenToChannelModerationActions(this->roomID, account);
|
||||
}
|
||||
@@ -64,7 +64,7 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
|
||||
|
||||
this->userStateChanged.connect(refreshPubSubState);
|
||||
this->roomIDchanged.connect(refreshPubSubState);
|
||||
this->managedConnect(app->accounts->Twitch.currentUserChanged, refreshPubSubState);
|
||||
this->managedConnect(app->accounts->twitch.currentUserChanged, refreshPubSubState);
|
||||
refreshPubSubState();
|
||||
|
||||
this->fetchMessages.connect([this] {
|
||||
@@ -85,7 +85,7 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
|
||||
};
|
||||
|
||||
auto doRefreshChatters = [=]() {
|
||||
const auto streamStatus = this->GetStreamStatus();
|
||||
const auto streamStatus = this->getStreamStatus();
|
||||
|
||||
if (app->settings->onlyFetchChattersForSmallerStreamers) {
|
||||
if (streamStatus.live && streamStatus.viewerCount > app->settings->smallStreamerLimit) {
|
||||
@@ -102,6 +102,10 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
|
||||
this->chattersListTimer = new QTimer;
|
||||
QObject::connect(this->chattersListTimer, &QTimer::timeout, doRefreshChatters);
|
||||
this->chattersListTimer->start(5 * 60 * 1000);
|
||||
|
||||
// for (int i = 0; i < 1000; i++) {
|
||||
// this->addMessage(messages::Message::createSystemMessage("asdf"));
|
||||
// }
|
||||
}
|
||||
|
||||
TwitchChannel::~TwitchChannel()
|
||||
@@ -186,7 +190,7 @@ bool TwitchChannel::isBroadcaster()
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
return this->name == app->accounts->Twitch.getCurrent()->getUserName();
|
||||
return this->name == app->accounts->twitch.getCurrent()->getUserName();
|
||||
}
|
||||
|
||||
bool TwitchChannel::hasModRights()
|
||||
@@ -206,6 +210,93 @@ void TwitchChannel::addRecentChatter(const std::shared_ptr<messages::Message> &m
|
||||
this->completionModel.addUser(message->displayName);
|
||||
}
|
||||
|
||||
void TwitchChannel::addJoinedUser(const QString &user)
|
||||
{
|
||||
auto *app = getApp();
|
||||
if (user == app->accounts->twitch.getCurrent()->getUserName() ||
|
||||
!app->settings->showJoins.getValue()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> guard(this->joinedUserMutex);
|
||||
|
||||
joinedUsers << user;
|
||||
|
||||
if (!this->joinedUsersMergeQueued) {
|
||||
this->joinedUsersMergeQueued = true;
|
||||
|
||||
QTimer::singleShot(500, &this->object, [this] {
|
||||
std::lock_guard<std::mutex> guard(this->joinedUserMutex);
|
||||
|
||||
auto message = messages::Message::createSystemMessage("Users joined: " +
|
||||
this->joinedUsers.join(", "));
|
||||
message->flags |= messages::Message::Collapsed;
|
||||
this->addMessage(message);
|
||||
this->joinedUsers.clear();
|
||||
this->joinedUsersMergeQueued = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchChannel::addPartedUser(const QString &user)
|
||||
{
|
||||
auto *app = getApp();
|
||||
|
||||
if (user == app->accounts->twitch.getCurrent()->getUserName() ||
|
||||
!app->settings->showJoins.getValue()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> guard(this->partedUserMutex);
|
||||
|
||||
partedUsers << user;
|
||||
|
||||
if (!this->partedUsersMergeQueued) {
|
||||
this->partedUsersMergeQueued = true;
|
||||
|
||||
QTimer::singleShot(500, &this->object, [this] {
|
||||
std::lock_guard<std::mutex> guard(this->partedUserMutex);
|
||||
|
||||
auto message = messages::Message::createSystemMessage("Users parted: " +
|
||||
this->partedUsers.join(", "));
|
||||
message->flags |= messages::Message::Collapsed;
|
||||
this->addMessage(message);
|
||||
this->partedUsers.clear();
|
||||
|
||||
this->partedUsersMergeQueued = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
TwitchChannel::RoomModes TwitchChannel::getRoomModes()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->roomModeMutex);
|
||||
|
||||
return this->roomModes;
|
||||
}
|
||||
|
||||
void TwitchChannel::setRoomModes(const RoomModes &_roomModes)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->roomModeMutex);
|
||||
this->roomModes = _roomModes;
|
||||
}
|
||||
|
||||
this->roomModesChanged.invoke();
|
||||
}
|
||||
|
||||
bool TwitchChannel::isLive() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
|
||||
return this->streamStatus.live;
|
||||
}
|
||||
|
||||
TwitchChannel::StreamStatus TwitchChannel::getStreamStatus() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
|
||||
return this->streamStatus;
|
||||
}
|
||||
|
||||
void TwitchChannel::setLive(bool newLiveStatus)
|
||||
{
|
||||
bool gotNewLiveStatus = false;
|
||||
@@ -289,6 +380,11 @@ void TwitchChannel::refreshLiveStatus()
|
||||
QString::number(diff / 3600) + "h " + QString::number(diff % 3600 / 60) + "m";
|
||||
|
||||
channel->streamStatus.rerun = false;
|
||||
if (stream.HasMember("stream_type")) {
|
||||
channel->streamStatus.streamType = stream["stream_type"].GetString();
|
||||
} else {
|
||||
channel->streamStatus.streamType = QString();
|
||||
}
|
||||
|
||||
if (stream.HasMember("broadcast_platform")) {
|
||||
const auto &broadcastPlatformValue = stream["broadcast_platform"];
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "singletons/emotemanager.hpp"
|
||||
#include "singletons/ircmanager.hpp"
|
||||
#include "util/concurrentmap.hpp"
|
||||
#include "util/mutexvalue.hpp"
|
||||
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
|
||||
@@ -31,6 +32,7 @@ public:
|
||||
QString title;
|
||||
QString game;
|
||||
QString uptime;
|
||||
QString streamType;
|
||||
};
|
||||
|
||||
struct UserState {
|
||||
@@ -38,6 +40,15 @@ public:
|
||||
bool broadcaster;
|
||||
};
|
||||
|
||||
struct RoomModes {
|
||||
bool submode = false;
|
||||
bool r9k = false;
|
||||
bool emoteOnly = false;
|
||||
// int folowerOnly = 0;
|
||||
int slowMode = 0;
|
||||
QString broadcasterLang;
|
||||
};
|
||||
|
||||
~TwitchChannel() final;
|
||||
|
||||
void reloadChannelEmotes();
|
||||
@@ -52,6 +63,8 @@ public:
|
||||
bool hasModRights();
|
||||
|
||||
void addRecentChatter(const std::shared_ptr<messages::Message> &message) final;
|
||||
void addJoinedUser(const QString &user);
|
||||
void addPartedUser(const QString &user);
|
||||
|
||||
const std::shared_ptr<chatterino::util::EmoteMap> bttvChannelEmotes;
|
||||
const std::shared_ptr<chatterino::util::EmoteMap> ffzChannelEmotes;
|
||||
@@ -66,25 +79,21 @@ public:
|
||||
|
||||
pajlada::Signals::NoArgBoltSignal fetchMessages;
|
||||
pajlada::Signals::NoArgSignal userStateChanged;
|
||||
pajlada::Signals::NoArgSignal roomModesChanged;
|
||||
|
||||
QString roomID;
|
||||
|
||||
StreamStatus GetStreamStatus() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
|
||||
return this->streamStatus;
|
||||
}
|
||||
RoomModes getRoomModes();
|
||||
void setRoomModes(const RoomModes &roomModes);
|
||||
|
||||
StreamStatus getStreamStatus() const;
|
||||
|
||||
struct NameOptions {
|
||||
QString displayName;
|
||||
QString localizedName;
|
||||
};
|
||||
|
||||
bool IsLive() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
|
||||
return this->streamStatus.live;
|
||||
}
|
||||
bool isLive() const;
|
||||
|
||||
private:
|
||||
explicit TwitchChannel(const QString &channelName, Communi::IrcConnection *readConnection);
|
||||
@@ -103,6 +112,16 @@ private:
|
||||
bool mod;
|
||||
QByteArray messageSuffix;
|
||||
QString lastSentMessage;
|
||||
RoomModes roomModes;
|
||||
std::mutex roomModeMutex;
|
||||
|
||||
QObject object;
|
||||
std::mutex joinedUserMutex;
|
||||
QStringList joinedUsers;
|
||||
bool joinedUsersMergeQueued = false;
|
||||
std::mutex partedUserMutex;
|
||||
QStringList partedUsers;
|
||||
bool partedUsersMergeQueued = false;
|
||||
|
||||
Communi::IrcConnection *readConnection;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace chatterino {
|
||||
namespace providers {
|
||||
namespace twitch {
|
||||
|
||||
bool TrimChannelName(const QString &channelName, QString &outChannelName)
|
||||
bool trimChannelName(const QString &channelName, QString &outChannelName)
|
||||
{
|
||||
if (channelName.length() < 3) {
|
||||
debug::Log("channel name length below 3");
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace chatterino {
|
||||
namespace providers {
|
||||
namespace twitch {
|
||||
|
||||
bool TrimChannelName(const QString &channelName, QString &outChannelName);
|
||||
bool trimChannelName(const QString &channelName, QString &outChannelName);
|
||||
|
||||
} // namespace twitch
|
||||
} // namespace providers
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "providers/twitch/twitchmessagebuilder.hpp"
|
||||
|
||||
#include "application.hpp"
|
||||
#include "controllers/accounts/accountcontroller.hpp"
|
||||
#include "controllers/highlights/highlightcontroller.hpp"
|
||||
#include "controllers/ignores/ignorecontroller.hpp"
|
||||
#include "debug/log.hpp"
|
||||
#include "providers/twitch/twitchchannel.hpp"
|
||||
#include "singletons/accountmanager.hpp"
|
||||
#include "singletons/emotemanager.hpp"
|
||||
#include "singletons/ircmanager.hpp"
|
||||
#include "singletons/resourcemanager.hpp"
|
||||
@@ -68,7 +68,18 @@ bool TwitchMessageBuilder::isIgnored() const
|
||||
if (app->settings->enableTwitchIgnoredUsers && this->tags.contains("user-id")) {
|
||||
auto sourceUserID = this->tags.value("user-id").toString();
|
||||
|
||||
for (const auto &user : app->accounts->Twitch.getCurrent()->getIgnores()) {
|
||||
for (const auto &user : app->accounts->twitch.getCurrent()->getIgnores()) {
|
||||
if (sourceUserID == user.id) {
|
||||
debug::Log("Blocking message because it's from blocked user {}", user.name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (app->settings->enableTwitchIgnoredUsers && this->tags.contains("user-id")) {
|
||||
auto sourceUserID = this->tags.value("user-id").toString();
|
||||
|
||||
for (const auto &user : app->accounts->twitch.getCurrent()->getIgnores()) {
|
||||
if (sourceUserID == user.id) {
|
||||
debug::Log("Blocking message because it's from blocked user {}", user.name);
|
||||
return true;
|
||||
@@ -86,12 +97,14 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
// PARSING
|
||||
this->parseUsername();
|
||||
|
||||
#ifdef XD
|
||||
if (this->originalMessage.length() > 100) {
|
||||
this->message->flags |= Message::Collapsed;
|
||||
this->emplace<EmoteElement>(getApp()->resources->badgeCollapsed, MessageElement::Collapsed);
|
||||
}
|
||||
#endif
|
||||
//#ifdef XD
|
||||
// if (this->originalMessage.length() > 100) {
|
||||
// this->message->flags |= Message::Collapsed;
|
||||
// this->emplace<EmoteElement>(getApp()->resources->badgeCollapsed,
|
||||
// MessageElement::Collapsed);
|
||||
// }
|
||||
//#endif
|
||||
this->message->flags |= Message::Collapsed;
|
||||
|
||||
// PARSING
|
||||
this->parseMessageID();
|
||||
@@ -141,15 +154,8 @@ MessagePtr TwitchMessageBuilder::build()
|
||||
this->appendTwitchEmote(ircMessage, emote, twitchEmotes);
|
||||
}
|
||||
|
||||
struct {
|
||||
bool operator()(const std::pair<long, util::EmoteData> &lhs,
|
||||
const std::pair<long, util::EmoteData> &rhs)
|
||||
{
|
||||
return lhs.first < rhs.first;
|
||||
}
|
||||
} customLess;
|
||||
|
||||
std::sort(twitchEmotes.begin(), twitchEmotes.end(), customLess);
|
||||
std::sort(twitchEmotes.begin(), twitchEmotes.end(),
|
||||
[](const auto &a, const auto &b) { return a.first < b.first; });
|
||||
}
|
||||
|
||||
auto currentTwitchEmote = twitchEmotes.begin();
|
||||
@@ -282,10 +288,16 @@ void TwitchMessageBuilder::parseUsername()
|
||||
// username
|
||||
this->userName = this->ircMessage->nick();
|
||||
|
||||
if (this->userName.isEmpty()) {
|
||||
if (this->userName.isEmpty() || this->args.trimSubscriberUsername) {
|
||||
this->userName = this->tags.value(QLatin1String("login")).toString();
|
||||
}
|
||||
|
||||
// display name
|
||||
// auto displayNameVariant = this->tags.value("display-name");
|
||||
// if (displayNameVariant.isValid()) {
|
||||
// this->userName = displayNameVariant.toString() + " (" + this->userName + ")";
|
||||
// }
|
||||
|
||||
this->message->loginName = this->userName;
|
||||
}
|
||||
|
||||
@@ -350,14 +362,14 @@ void TwitchMessageBuilder::appendUsername()
|
||||
} else if (this->args.isReceivedWhisper) {
|
||||
// Sender username
|
||||
this->emplace<TextElement>(usernameText, MessageElement::Text, this->usernameColor,
|
||||
FontStyle::MediumBold)
|
||||
FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserInfo, this->userName});
|
||||
|
||||
auto currentUser = app->accounts->Twitch.getCurrent();
|
||||
auto currentUser = app->accounts->twitch.getCurrent();
|
||||
|
||||
// Separator
|
||||
this->emplace<TextElement>("->", MessageElement::Text,
|
||||
app->themes->messages.textColors.system, FontStyle::Medium);
|
||||
app->themes->messages.textColors.system, FontStyle::ChatMedium);
|
||||
|
||||
QColor selfColor = currentUser->color;
|
||||
if (!selfColor.isValid()) {
|
||||
@@ -366,14 +378,14 @@ void TwitchMessageBuilder::appendUsername()
|
||||
|
||||
// Your own username
|
||||
this->emplace<TextElement>(currentUser->getUserName() + ":", MessageElement::Text,
|
||||
selfColor, FontStyle::MediumBold);
|
||||
selfColor, FontStyle::ChatMediumBold);
|
||||
} else {
|
||||
if (!this->action) {
|
||||
usernameText += ":";
|
||||
}
|
||||
|
||||
this->emplace<TextElement>(usernameText, MessageElement::Text, this->usernameColor,
|
||||
FontStyle::MediumBold)
|
||||
FontStyle::ChatMediumBold)
|
||||
->setLink({Link::UserInfo, this->userName});
|
||||
}
|
||||
}
|
||||
@@ -385,7 +397,7 @@ void TwitchMessageBuilder::parseHighlights()
|
||||
|
||||
auto app = getApp();
|
||||
|
||||
auto currentUser = app->accounts->Twitch.getCurrent();
|
||||
auto currentUser = app->accounts->twitch.getCurrent();
|
||||
|
||||
QString currentUsername = currentUser->getUserName();
|
||||
|
||||
@@ -571,11 +583,13 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||
|
||||
QString cheerAmountQS = badge.mid(5);
|
||||
std::string versionKey = cheerAmountQS.toStdString();
|
||||
QString tooltip = QString("Twitch cheer ") + cheerAmountQS;
|
||||
|
||||
// Try to fetch channel-specific bit badge
|
||||
try {
|
||||
const auto &badge = channelResources.badgeSets.at("bits").versions.at(versionKey);
|
||||
this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity);
|
||||
this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity)
|
||||
->setTooltip(tooltip);
|
||||
continue;
|
||||
} catch (const std::out_of_range &) {
|
||||
// Channel does not contain a special bit badge for this version
|
||||
@@ -584,7 +598,8 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||
// Use default bit badge
|
||||
try {
|
||||
const auto &badge = app->resources->badgeSets.at("bits").versions.at(versionKey);
|
||||
this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity);
|
||||
this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity)
|
||||
->setTooltip(tooltip);
|
||||
} catch (const std::out_of_range &) {
|
||||
debug::Log("No default bit badge for version {} found", versionKey);
|
||||
continue;
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
#include "twitchserver.hpp"
|
||||
|
||||
#include "application.hpp"
|
||||
#include "controllers/accounts/accountcontroller.hpp"
|
||||
#include "controllers/highlights/highlightcontroller.hpp"
|
||||
#include "providers/twitch/ircmessagehandler.hpp"
|
||||
#include "providers/twitch/twitchaccount.hpp"
|
||||
#include "providers/twitch/twitchhelpers.hpp"
|
||||
#include "providers/twitch/twitchmessagebuilder.hpp"
|
||||
#include "singletons/accountmanager.hpp"
|
||||
#include "util/posttothread.hpp"
|
||||
|
||||
#include <IrcCommand>
|
||||
#include <cassert>
|
||||
|
||||
using namespace Communi;
|
||||
// using namespace Communi;
|
||||
using namespace chatterino::singletons;
|
||||
|
||||
namespace chatterino {
|
||||
@@ -27,13 +29,14 @@ TwitchServer::TwitchServer()
|
||||
|
||||
void TwitchServer::initialize()
|
||||
{
|
||||
getApp()->accounts->Twitch.currentUserChanged.connect(
|
||||
getApp()->accounts->twitch.currentUserChanged.connect(
|
||||
[this]() { util::postToThread([this] { this->connect(); }); });
|
||||
}
|
||||
|
||||
void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead, bool isWrite)
|
||||
void TwitchServer::initializeConnection(providers::irc::IrcConnection *connection, bool isRead,
|
||||
bool isWrite)
|
||||
{
|
||||
std::shared_ptr<TwitchAccount> account = getApp()->accounts->Twitch.getCurrent();
|
||||
std::shared_ptr<TwitchAccount> account = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
qDebug() << "logging in as" << account->getUserName();
|
||||
|
||||
@@ -56,9 +59,9 @@ void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
|
||||
// this->refreshIgnoredUsers(username, oauthClient, oauthToken);
|
||||
}
|
||||
|
||||
connection->sendCommand(IrcCommand::createCapability("REQ", "twitch.tv/membership"));
|
||||
connection->sendCommand(IrcCommand::createCapability("REQ", "twitch.tv/commands"));
|
||||
connection->sendCommand(IrcCommand::createCapability("REQ", "twitch.tv/tags"));
|
||||
connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/membership"));
|
||||
connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/commands"));
|
||||
connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/tags"));
|
||||
|
||||
connection->setHost("irc.chat.twitch.tv");
|
||||
connection->setPort(6667);
|
||||
@@ -74,69 +77,53 @@ std::shared_ptr<Channel> TwitchServer::createChannel(const QString &channelName)
|
||||
return std::shared_ptr<Channel>(channel);
|
||||
}
|
||||
|
||||
void TwitchServer::privateMessageReceived(IrcPrivateMessage *message)
|
||||
void TwitchServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
|
||||
{
|
||||
QString channelName;
|
||||
if (!TrimChannelName(message->target(), channelName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->onPrivateMessage.invoke(message);
|
||||
auto chan = this->getChannelOrEmpty(channelName);
|
||||
|
||||
if (chan->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
messages::MessageParseArgs args;
|
||||
|
||||
TwitchMessageBuilder builder(chan.get(), message, args);
|
||||
|
||||
if (!builder.isIgnored()) {
|
||||
messages::MessagePtr _message = builder.build();
|
||||
if (_message->flags & messages::Message::Highlighted) {
|
||||
this->mentionsChannel->addMessage(_message);
|
||||
}
|
||||
|
||||
chan->addMessage(_message);
|
||||
}
|
||||
IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
|
||||
}
|
||||
|
||||
void TwitchServer::messageReceived(IrcMessage *message)
|
||||
void TwitchServer::messageReceived(Communi::IrcMessage *message)
|
||||
{
|
||||
// this->readConnection
|
||||
if (message->type() == IrcMessage::Type::Private) {
|
||||
if (message->type() == Communi::IrcMessage::Type::Private) {
|
||||
// We already have a handler for private messages
|
||||
return;
|
||||
}
|
||||
|
||||
const QString &command = message->command();
|
||||
|
||||
auto &handler = IrcMessageHandler::getInstance();
|
||||
|
||||
if (command == "ROOMSTATE") {
|
||||
IrcMessageHandler::getInstance().handleRoomStateMessage(message);
|
||||
handler.handleRoomStateMessage(message);
|
||||
} else if (command == "CLEARCHAT") {
|
||||
IrcMessageHandler::getInstance().handleClearChatMessage(message);
|
||||
handler.handleClearChatMessage(message);
|
||||
} else if (command == "USERSTATE") {
|
||||
IrcMessageHandler::getInstance().handleUserStateMessage(message);
|
||||
handler.handleUserStateMessage(message);
|
||||
} else if (command == "WHISPER") {
|
||||
IrcMessageHandler::getInstance().handleWhisperMessage(message);
|
||||
handler.handleWhisperMessage(message);
|
||||
} else if (command == "USERNOTICE") {
|
||||
IrcMessageHandler::getInstance().handleUserNoticeMessage(message);
|
||||
handler.handleUserNoticeMessage(message, *this);
|
||||
} else if (command == "MODE") {
|
||||
IrcMessageHandler::getInstance().handleModeMessage(message);
|
||||
handler.handleModeMessage(message);
|
||||
} else if (command == "NOTICE") {
|
||||
IrcMessageHandler::getInstance().handleNoticeMessage(
|
||||
static_cast<IrcNoticeMessage *>(message));
|
||||
handler.handleNoticeMessage(static_cast<Communi::IrcNoticeMessage *>(message));
|
||||
} else if (command == "JOIN") {
|
||||
handler.handleJoinMessage(message);
|
||||
} else if (command == "PART") {
|
||||
handler.handlePartMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchServer::writeConnectionMessageReceived(IrcMessage *message)
|
||||
void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
|
||||
{
|
||||
switch (message->type()) {
|
||||
case IrcMessage::Type::Notice: {
|
||||
case Communi::IrcMessage::Type::Notice: {
|
||||
IrcMessageHandler::getInstance().handleWriteConnectionNoticeMessage(
|
||||
static_cast<IrcNoticeMessage *>(message));
|
||||
static_cast<Communi::IrcNoticeMessage *>(message));
|
||||
} break;
|
||||
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "providers/irc/abstractircserver.hpp"
|
||||
#include "providers/twitch/twitchaccount.hpp"
|
||||
#include "providers/twitch/twitchchannel.hpp"
|
||||
#include "util/mutexvalue.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -22,12 +23,17 @@ public:
|
||||
|
||||
std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID);
|
||||
|
||||
util::MutexValue<QString> lastUserThatWhisperedMe;
|
||||
|
||||
// QString getLastWhisperedPerson() const;
|
||||
// void setLastWhisperedPerson(const QString &person);
|
||||
|
||||
const ChannelPtr whispersChannel;
|
||||
const ChannelPtr mentionsChannel;
|
||||
IndirectChannel watchingChannel;
|
||||
|
||||
protected:
|
||||
void initializeConnection(Communi::IrcConnection *connection, bool isRead,
|
||||
void initializeConnection(providers::irc::IrcConnection *connection, bool isRead,
|
||||
bool isWrite) override;
|
||||
std::shared_ptr<Channel> createChannel(const QString &channelName) override;
|
||||
|
||||
@@ -38,6 +44,10 @@ protected:
|
||||
std::shared_ptr<Channel> getCustomChannel(const QString &channelname) override;
|
||||
|
||||
QString cleanChannelName(const QString &dirtyChannelName) override;
|
||||
|
||||
private:
|
||||
// mutable std::mutex lastWhisperedPersonMutex;
|
||||
// QString lastWhisperedPerson;
|
||||
};
|
||||
|
||||
} // namespace twitch
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#include "singletons/accountmanager.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace singletons {
|
||||
|
||||
void AccountManager::load()
|
||||
{
|
||||
this->Twitch.load();
|
||||
}
|
||||
|
||||
} // namespace singletons
|
||||
} // namespace chatterino
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "providers/twitch/twitchaccountmanager.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace singletons {
|
||||
|
||||
class AccountManager
|
||||
{
|
||||
public:
|
||||
AccountManager() = default;
|
||||
|
||||
~AccountManager() = delete;
|
||||
|
||||
void load();
|
||||
|
||||
providers::twitch::TwitchAccountManager Twitch;
|
||||
};
|
||||
|
||||
} // namespace singletons
|
||||
} // namespace chatterino
|
||||
@@ -86,8 +86,8 @@ EmoteManager::EmoteManager()
|
||||
|
||||
void EmoteManager::initialize()
|
||||
{
|
||||
getApp()->accounts->Twitch.currentUserChanged.connect([this] {
|
||||
auto currentUser = getApp()->accounts->Twitch.getCurrent();
|
||||
getApp()->accounts->twitch.currentUserChanged.connect([this] {
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
assert(currentUser);
|
||||
this->refreshTwitchEmotes(currentUser);
|
||||
});
|
||||
@@ -130,13 +130,23 @@ void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName,
|
||||
QString code = emoteObject.value("code").toString();
|
||||
// emoteObject.value("imageType").toString();
|
||||
|
||||
QString link = linkTemplate;
|
||||
link.detach();
|
||||
auto emote = this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [&] {
|
||||
util::EmoteData emoteData;
|
||||
QString link = linkTemplate;
|
||||
link.detach();
|
||||
emoteData.image1x = new Image(link.replace("{{id}}", id).replace("{{image}}", "1x"),
|
||||
1, code, code + "<br />Channel BTTV Emote");
|
||||
link = linkTemplate;
|
||||
link.detach();
|
||||
emoteData.image2x = new Image(link.replace("{{id}}", id).replace("{{image}}", "2x"),
|
||||
0.5, code, code + "<br />Channel BTTV Emote");
|
||||
link = linkTemplate;
|
||||
link.detach();
|
||||
emoteData.image3x = new Image(link.replace("{{id}}", id).replace("{{image}}", "3x"),
|
||||
0.25, code, code + "<br />Channel BTTV Emote");
|
||||
emoteData.pageLink = "https://manage.betterttv.net/emotes/" + id;
|
||||
|
||||
link = link.replace("{{id}}", id).replace("{{image}}", "1x");
|
||||
|
||||
auto emote = this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [&code, &link] {
|
||||
return util::EmoteData(new Image(link, 1, code, code + "<br/>Channel BTTV Emote"));
|
||||
return emoteData;
|
||||
});
|
||||
|
||||
this->bttvChannelEmotes.insert(code, emote);
|
||||
@@ -182,9 +192,11 @@ void EmoteManager::reloadFFZChannelEmotes(const QString &channelName,
|
||||
|
||||
QJsonObject urls = emoteObject.value("urls").toObject();
|
||||
|
||||
auto emote = this->getFFZChannelEmoteFromCaches().getOrAdd(id, [&code, &urls] {
|
||||
auto emote = this->getFFZChannelEmoteFromCaches().getOrAdd(id, [id, &code, &urls] {
|
||||
util::EmoteData emoteData;
|
||||
FillInFFZEmoteData(urls, code, code + "<br/>Channel FFZ Emote", emoteData);
|
||||
emoteData.pageLink =
|
||||
QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
|
||||
|
||||
return emoteData;
|
||||
});
|
||||
@@ -436,6 +448,7 @@ void EmoteManager::refreshTwitchEmotes(const std::shared_ptr<TwitchAccount> &use
|
||||
[=, &emoteData](const QJsonObject &root) {
|
||||
emoteData.emoteSets.clear();
|
||||
emoteData.emoteCodes.clear();
|
||||
|
||||
auto emoticonSets = root.value("emoticon_sets").toObject();
|
||||
for (QJsonObject::iterator it = emoticonSets.begin(); it != emoticonSets.end(); ++it) {
|
||||
std::string emoteSetString = it.key().toStdString();
|
||||
@@ -443,7 +456,7 @@ void EmoteManager::refreshTwitchEmotes(const std::shared_ptr<TwitchAccount> &use
|
||||
|
||||
for (QJsonValue emoteValue : emoteSetList) {
|
||||
QJsonObject emoticon = emoteValue.toObject();
|
||||
std::string id = emoticon["id"].toString().toStdString();
|
||||
std::string id = QString::number(emoticon["id"].toInt()).toStdString();
|
||||
std::string code = emoticon["code"].toString().toStdString();
|
||||
emoteData.emoteSets[emoteSetString].push_back({id, code});
|
||||
emoteData.emoteCodes.push_back(code);
|
||||
@@ -483,6 +496,7 @@ void EmoteManager::loadBTTVEmotes()
|
||||
code + "<br />Global BTTV Emote");
|
||||
emoteData.image3x = new Image(GetBTTVEmoteLink(urlTemplate, id, "3x"), 0.25, code,
|
||||
code + "<br />Global BTTV Emote");
|
||||
emoteData.pageLink = "https://manage.betterttv.net/emotes/" + id;
|
||||
|
||||
this->bttvGlobalEmotes.insert(code, emoteData);
|
||||
codes.push_back(code.toStdString());
|
||||
@@ -510,10 +524,13 @@ void EmoteManager::loadFFZEmotes()
|
||||
QJsonObject object = emote.toObject();
|
||||
|
||||
QString code = object.value("name").toString();
|
||||
int id = object.value("id").toInt();
|
||||
QJsonObject urls = object.value("urls").toObject();
|
||||
|
||||
util::EmoteData emoteData;
|
||||
FillInFFZEmoteData(urls, code, code + "<br/>Global FFZ Emote", emoteData);
|
||||
emoteData.pageLink =
|
||||
QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
|
||||
|
||||
this->ffzGlobalEmotes.insert(code, emoteData);
|
||||
codes.push_back(code.toStdString());
|
||||
@@ -530,6 +547,20 @@ util::EmoteData EmoteManager::getTwitchEmoteById(long id, const QString &emoteNa
|
||||
{
|
||||
QString _emoteName = emoteName;
|
||||
_emoteName.replace("<", "<");
|
||||
_emoteName.replace(">", ">");
|
||||
|
||||
static QMap<QString, QString> emoteNameReplacements{
|
||||
{"[oO](_|\\.)[oO]", "O_o"}, {"\\>\\;\\(", ">("}, {"\\<\\;3", "<3"},
|
||||
{"\\:-?(o|O)", ":O"}, {"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"},
|
||||
{"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("}, {"\\:-?\\)", ":)"},
|
||||
{"\\:-?D", ":D"}, {"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"},
|
||||
{"R-?\\)", "R)"}, {"B-?\\)", "B)"},
|
||||
};
|
||||
|
||||
auto it = emoteNameReplacements.find(_emoteName);
|
||||
if (it != emoteNameReplacements.end()) {
|
||||
_emoteName = it.value();
|
||||
}
|
||||
|
||||
return _twitchEmoteFromCache.getOrAdd(id, [&emoteName, &_emoteName, &id] {
|
||||
util::EmoteData newEmoteData;
|
||||
|
||||
@@ -46,16 +46,6 @@ public:
|
||||
|
||||
util::EmoteData getTwitchEmoteById(long int id, const QString &emoteName);
|
||||
|
||||
int getGeneration()
|
||||
{
|
||||
return _generation;
|
||||
}
|
||||
|
||||
void incGeneration()
|
||||
{
|
||||
_generation++;
|
||||
}
|
||||
|
||||
pajlada::Signals::NoArgSignal &getGifUpdateSignal();
|
||||
|
||||
// Bit badge/emotes?
|
||||
@@ -145,8 +135,6 @@ private:
|
||||
pajlada::Signals::NoArgSignal gifUpdateTimerSignal;
|
||||
QTimer gifUpdateTimer;
|
||||
bool gifUpdateTimerInitiated = false;
|
||||
|
||||
int _generation = 0;
|
||||
};
|
||||
|
||||
} // namespace singletons
|
||||
|
||||
+103
-74
@@ -3,6 +3,10 @@
|
||||
#include <QDebug>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "application.hpp"
|
||||
#include "util/assertinguithread.hpp"
|
||||
#include "windowmanager.hpp"
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#define DEFAULT_FONT_FAMILY "Segoe UI"
|
||||
#define DEFAULT_FONT_SIZE 10
|
||||
@@ -20,86 +24,111 @@ namespace chatterino {
|
||||
namespace singletons {
|
||||
|
||||
FontManager::FontManager()
|
||||
: currentFontFamily("/appearance/currentFontFamily", DEFAULT_FONT_FAMILY)
|
||||
, currentFontSize("/appearance/currentFontSize", DEFAULT_FONT_SIZE)
|
||||
// , currentFont(this->currentFontFamily.getValue().c_str(), currentFontSize.getValue())
|
||||
: chatFontFamily("/appearance/currentFontFamily", DEFAULT_FONT_FAMILY)
|
||||
, chatFontSize("/appearance/currentFontSize", DEFAULT_FONT_SIZE)
|
||||
{
|
||||
qDebug() << "init FontManager";
|
||||
|
||||
this->currentFontFamily.connect([this](const std::string &newValue, auto) {
|
||||
this->incGeneration();
|
||||
// this->currentFont.setFamily(newValue.c_str());
|
||||
this->currentFontByScale.clear();
|
||||
this->fontChanged.invoke();
|
||||
});
|
||||
this->chatFontFamily.connect([this](const std::string &, auto) {
|
||||
util::assertInGuiThread();
|
||||
|
||||
this->currentFontSize.connect([this](const int &newValue, auto) {
|
||||
this->incGeneration();
|
||||
// this->currentFont.setSize(newValue);
|
||||
this->currentFontByScale.clear();
|
||||
this->fontChanged.invoke();
|
||||
});
|
||||
}
|
||||
|
||||
QFont &FontManager::getFont(FontManager::Type type, float scale)
|
||||
{
|
||||
// return this->currentFont.getFont(type);
|
||||
return this->getCurrentFont(scale).getFont(type);
|
||||
}
|
||||
|
||||
QFontMetrics &FontManager::getFontMetrics(FontManager::Type type, float scale)
|
||||
{
|
||||
// return this->currentFont.getFontMetrics(type);
|
||||
return this->getCurrentFont(scale).getFontMetrics(type);
|
||||
}
|
||||
|
||||
FontManager::FontData &FontManager::Font::getFontData(FontManager::Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case Tiny:
|
||||
return this->tiny;
|
||||
case Small:
|
||||
return this->small;
|
||||
case MediumSmall:
|
||||
return this->mediumSmall;
|
||||
case Medium:
|
||||
return this->medium;
|
||||
case MediumBold:
|
||||
return this->mediumBold;
|
||||
case MediumItalic:
|
||||
return this->mediumItalic;
|
||||
case Large:
|
||||
return this->large;
|
||||
case VeryLarge:
|
||||
return this->veryLarge;
|
||||
default:
|
||||
qDebug() << "Unknown font type:" << type << ", defaulting to medium";
|
||||
return this->medium;
|
||||
}
|
||||
}
|
||||
|
||||
QFont &FontManager::Font::getFont(Type type)
|
||||
{
|
||||
return this->getFontData(type).font;
|
||||
}
|
||||
|
||||
QFontMetrics &FontManager::Font::getFontMetrics(Type type)
|
||||
{
|
||||
return this->getFontData(type).metrics;
|
||||
}
|
||||
|
||||
FontManager::Font &FontManager::getCurrentFont(float scale)
|
||||
{
|
||||
for (auto it = this->currentFontByScale.begin(); it != this->currentFontByScale.end(); it++) {
|
||||
if (it->first == scale) {
|
||||
return it->second;
|
||||
if (getApp()->windows) {
|
||||
getApp()->windows->incGeneration();
|
||||
}
|
||||
}
|
||||
this->currentFontByScale.push_back(
|
||||
std::make_pair(scale, Font(this->currentFontFamily.getValue().c_str(),
|
||||
this->currentFontSize.getValue() * scale)));
|
||||
|
||||
return this->currentFontByScale.back().second;
|
||||
for (auto &map : this->fontsByType) {
|
||||
map.clear();
|
||||
}
|
||||
this->fontChanged.invoke();
|
||||
});
|
||||
|
||||
this->chatFontSize.connect([this](const int &, auto) {
|
||||
util::assertInGuiThread();
|
||||
|
||||
if (getApp()->windows) {
|
||||
getApp()->windows->incGeneration();
|
||||
}
|
||||
|
||||
for (auto &map : this->fontsByType) {
|
||||
map.clear();
|
||||
}
|
||||
this->fontChanged.invoke();
|
||||
});
|
||||
|
||||
this->fontsByType.resize(size_t(EndType));
|
||||
}
|
||||
|
||||
QFont FontManager::getFont(FontManager::Type type, float scale)
|
||||
{
|
||||
return this->getOrCreateFontData(type, scale).font;
|
||||
}
|
||||
|
||||
QFontMetrics FontManager::getFontMetrics(FontManager::Type type, float scale)
|
||||
{
|
||||
return this->getOrCreateFontData(type, scale).metrics;
|
||||
}
|
||||
|
||||
FontManager::FontData &FontManager::getOrCreateFontData(Type type, float scale)
|
||||
{
|
||||
util::assertInGuiThread();
|
||||
|
||||
assert(type >= 0 && type < EndType);
|
||||
|
||||
auto &map = this->fontsByType[size_t(type)];
|
||||
|
||||
// find element
|
||||
auto it = map.find(scale);
|
||||
if (it != map.end()) {
|
||||
// return if found
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// emplace new element
|
||||
auto result = map.emplace(scale, this->createFontData(type, scale));
|
||||
assert(result.second);
|
||||
|
||||
return result.first->second;
|
||||
}
|
||||
|
||||
FontManager::FontData FontManager::createFontData(Type type, float scale)
|
||||
{
|
||||
// check if it's a chat (scale the setting)
|
||||
if (type >= ChatStart && type <= ChatEnd) {
|
||||
static std::unordered_map<Type, ChatFontData> sizeScale{
|
||||
{ChatSmall, {0.6f, false, QFont::Normal}},
|
||||
{ChatMediumSmall, {0.8f, false, QFont::Normal}},
|
||||
{ChatMedium, {1, false, QFont::Normal}},
|
||||
{ChatMediumBold, {1, false, QFont::Medium}},
|
||||
{ChatMediumItalic, {1, true, QFont::Normal}},
|
||||
{ChatLarge, {1.2f, false, QFont::Normal}},
|
||||
{ChatVeryLarge, {1.4f, false, QFont::Normal}},
|
||||
};
|
||||
|
||||
auto data = sizeScale[type];
|
||||
return FontData(QFont(QString::fromStdString(this->chatFontFamily.getValue()),
|
||||
int(this->chatFontSize.getValue() * data.scale * scale), data.weight,
|
||||
data.italic));
|
||||
}
|
||||
|
||||
// normal Ui font (use pt size)
|
||||
{
|
||||
#ifdef Q_OS_MAC
|
||||
constexpr float multiplier = 0.8f;
|
||||
#else
|
||||
constexpr float multiplier = 1.f;
|
||||
#endif
|
||||
|
||||
static std::unordered_map<Type, UiFontData> defaultSize{
|
||||
{Tiny, {8, "Monospace", false, QFont::Normal}},
|
||||
{UiMedium, {int(12 * multiplier), DEFAULT_FONT_FAMILY, false, QFont::Normal}},
|
||||
{UiTabs, {int(9 * multiplier), DEFAULT_FONT_FAMILY, false, QFont::Normal}},
|
||||
};
|
||||
|
||||
UiFontData &data = defaultSize[type];
|
||||
QFont font(data.name, int(data.size * scale), data.weight, data.italic);
|
||||
return FontData(font);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace singletons
|
||||
|
||||
+43
-102
@@ -3,138 +3,79 @@
|
||||
#include <QFont>
|
||||
#include <QFontDatabase>
|
||||
#include <QFontMetrics>
|
||||
#include <array>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <pajlada/settings/setting.hpp>
|
||||
#include <pajlada/signals/signal.hpp>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace chatterino {
|
||||
namespace singletons {
|
||||
|
||||
class FontManager
|
||||
class FontManager : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
FontManager();
|
||||
|
||||
FontManager(const FontManager &) = delete;
|
||||
FontManager(FontManager &&) = delete;
|
||||
~FontManager() = delete;
|
||||
|
||||
// font data gets set in createFontData(...)
|
||||
enum Type : uint8_t {
|
||||
Tiny,
|
||||
Small,
|
||||
MediumSmall,
|
||||
Medium,
|
||||
MediumBold,
|
||||
MediumItalic,
|
||||
Large,
|
||||
VeryLarge,
|
||||
ChatSmall,
|
||||
ChatMediumSmall,
|
||||
ChatMedium,
|
||||
ChatMediumBold,
|
||||
ChatMediumItalic,
|
||||
ChatLarge,
|
||||
ChatVeryLarge,
|
||||
|
||||
UiMedium,
|
||||
UiTabs,
|
||||
|
||||
// don't remove this value
|
||||
EndType,
|
||||
|
||||
// make sure to update these values accordingly!
|
||||
ChatStart = ChatSmall,
|
||||
ChatEnd = ChatVeryLarge,
|
||||
};
|
||||
|
||||
QFont &getFont(Type type, float scale);
|
||||
QFontMetrics &getFontMetrics(Type type, float scale);
|
||||
QFont getFont(Type type, float scale);
|
||||
QFontMetrics getFontMetrics(Type type, float scale);
|
||||
|
||||
int getGeneration() const
|
||||
{
|
||||
return this->generation;
|
||||
}
|
||||
|
||||
void incGeneration()
|
||||
{
|
||||
this->generation++;
|
||||
}
|
||||
|
||||
pajlada::Settings::Setting<std::string> currentFontFamily;
|
||||
pajlada::Settings::Setting<int> currentFontSize;
|
||||
pajlada::Settings::Setting<std::string> chatFontFamily;
|
||||
pajlada::Settings::Setting<int> chatFontSize;
|
||||
|
||||
pajlada::Signals::NoArgSignal fontChanged;
|
||||
|
||||
private:
|
||||
struct FontData {
|
||||
FontData(QFont &&_font)
|
||||
FontData(const QFont &_font)
|
||||
: font(_font)
|
||||
, metrics(this->font)
|
||||
, metrics(_font)
|
||||
{
|
||||
}
|
||||
|
||||
QFont font;
|
||||
QFontMetrics metrics;
|
||||
const QFont font;
|
||||
const QFontMetrics metrics;
|
||||
};
|
||||
|
||||
struct Font {
|
||||
Font() = delete;
|
||||
|
||||
Font(const char *fontFamilyName, int mediumSize)
|
||||
: tiny(QFont("Monospace", 8))
|
||||
, small(QFont(fontFamilyName, mediumSize - 4))
|
||||
, mediumSmall(QFont(fontFamilyName, mediumSize - 2))
|
||||
, medium(QFont(fontFamilyName, mediumSize))
|
||||
, mediumBold(QFont(fontFamilyName, mediumSize, QFont::DemiBold))
|
||||
, mediumItalic(QFont(fontFamilyName, mediumSize, -1, true))
|
||||
, large(QFont(fontFamilyName, mediumSize))
|
||||
, veryLarge(QFont(fontFamilyName, mediumSize))
|
||||
{
|
||||
tiny.font.setStyleHint(QFont::TypeWriter);
|
||||
}
|
||||
|
||||
void setFamily(const char *newFamily)
|
||||
{
|
||||
this->small.font.setFamily(newFamily);
|
||||
this->mediumSmall.font.setFamily(newFamily);
|
||||
this->medium.font.setFamily(newFamily);
|
||||
this->mediumBold.font.setFamily(newFamily);
|
||||
this->mediumItalic.font.setFamily(newFamily);
|
||||
this->large.font.setFamily(newFamily);
|
||||
this->veryLarge.font.setFamily(newFamily);
|
||||
|
||||
this->updateMetrics();
|
||||
}
|
||||
|
||||
void setSize(int newMediumSize)
|
||||
{
|
||||
this->small.font.setPointSize(newMediumSize - 4);
|
||||
this->mediumSmall.font.setPointSize(newMediumSize - 2);
|
||||
this->medium.font.setPointSize(newMediumSize);
|
||||
this->mediumBold.font.setPointSize(newMediumSize);
|
||||
this->mediumItalic.font.setPointSize(newMediumSize);
|
||||
this->large.font.setPointSize(newMediumSize + 2);
|
||||
this->veryLarge.font.setPointSize(newMediumSize + 4);
|
||||
|
||||
this->updateMetrics();
|
||||
}
|
||||
|
||||
void updateMetrics()
|
||||
{
|
||||
this->small.metrics = QFontMetrics(this->small.font);
|
||||
this->mediumSmall.metrics = QFontMetrics(this->mediumSmall.font);
|
||||
this->medium.metrics = QFontMetrics(this->medium.font);
|
||||
this->mediumBold.metrics = QFontMetrics(this->mediumBold.font);
|
||||
this->mediumItalic.metrics = QFontMetrics(this->mediumItalic.font);
|
||||
this->large.metrics = QFontMetrics(this->large.font);
|
||||
this->veryLarge.metrics = QFontMetrics(this->veryLarge.font);
|
||||
}
|
||||
|
||||
FontData &getFontData(Type type);
|
||||
|
||||
QFont &getFont(Type type);
|
||||
QFontMetrics &getFontMetrics(Type type);
|
||||
|
||||
FontData tiny;
|
||||
FontData small;
|
||||
FontData mediumSmall;
|
||||
FontData medium;
|
||||
FontData mediumBold;
|
||||
FontData mediumItalic;
|
||||
FontData large;
|
||||
FontData veryLarge;
|
||||
struct ChatFontData {
|
||||
float scale;
|
||||
bool italic;
|
||||
QFont::Weight weight;
|
||||
};
|
||||
|
||||
Font &getCurrentFont(float scale);
|
||||
struct UiFontData {
|
||||
float size;
|
||||
const char *name;
|
||||
bool italic;
|
||||
QFont::Weight weight;
|
||||
};
|
||||
|
||||
// Future plans:
|
||||
// Could have multiple fonts in here, such as "Menu font", "Application font", "Chat font"
|
||||
FontData &getOrCreateFontData(Type type, float scale);
|
||||
FontData createFontData(Type type, float scale);
|
||||
|
||||
std::list<std::pair<float, Font>> currentFontByScale;
|
||||
|
||||
int generation = 0;
|
||||
std::vector<std::unordered_map<float, FontData>> fontsByType;
|
||||
};
|
||||
|
||||
} // namespace singletons
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace ipc = boost::interprocess;
|
||||
namespace chatterino {
|
||||
namespace singletons {
|
||||
|
||||
// fourtf: don't add this class to the application class
|
||||
NativeMessagingManager::NativeMessagingManager()
|
||||
{
|
||||
qDebug() << "init NativeMessagingManager";
|
||||
@@ -50,6 +51,10 @@ void NativeMessagingManager::registerHost()
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
if (app->paths->isPortable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create manifest
|
||||
QJsonDocument document;
|
||||
QJsonObject root_obj;
|
||||
@@ -113,13 +118,14 @@ void NativeMessagingManager::ReceiverThread::run()
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
char *buf = (char *)malloc(MESSAGE_SIZE);
|
||||
std::unique_ptr<char> buf(static_cast<char *>(malloc(MESSAGE_SIZE)));
|
||||
ipc::message_queue::size_type retSize;
|
||||
unsigned int priority;
|
||||
|
||||
messageQueue.receive(buf, MESSAGE_SIZE, retSize, priority);
|
||||
messageQueue.receive(buf.get(), MESSAGE_SIZE, retSize, priority);
|
||||
|
||||
QJsonDocument document = QJsonDocument::fromJson(QByteArray(buf, retSize));
|
||||
QJsonDocument document =
|
||||
QJsonDocument::fromJson(QByteArray::fromRawData(buf.get(), retSize));
|
||||
|
||||
this->handleMessage(document.object());
|
||||
} catch (ipc::interprocess_exception &ex) {
|
||||
@@ -143,26 +149,37 @@ void NativeMessagingManager::ReceiverThread::handleMessage(const QJsonObject &ro
|
||||
QString _type = root.value("type").toString();
|
||||
bool attach = root.value("attach").toBool();
|
||||
QString name = root.value("name").toString();
|
||||
QString winId = root.value("winId").toString();
|
||||
int yOffset = root.value("yOffset").toInt(-1);
|
||||
|
||||
if (_type.isNull() || name.isNull() || winId.isNull()) {
|
||||
#ifdef USEWINSDK
|
||||
widgets::AttachedWindow::GetArgs args;
|
||||
args.winId = root.value("winId").toString();
|
||||
args.yOffset = root.value("yOffset").toInt(-1);
|
||||
args.width = root.value("size").toObject().value("width").toInt(-1);
|
||||
args.height = root.value("size").toObject().value("height").toInt(-1);
|
||||
|
||||
if (_type.isNull() || args.winId.isNull()) {
|
||||
qDebug() << "NM type, name or winId missing";
|
||||
attach = false;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_type == "twitch") {
|
||||
util::postToThread([name, attach, winId, yOffset, app] {
|
||||
app->twitch.server->watchingChannel.update(
|
||||
app->twitch.server->getOrAddChannel(name));
|
||||
util::postToThread([=] {
|
||||
if (!name.isEmpty()) {
|
||||
app->twitch.server->watchingChannel.update(
|
||||
app->twitch.server->getOrAddChannel(name));
|
||||
}
|
||||
|
||||
if (attach) {
|
||||
#ifdef USEWINSDK
|
||||
auto *window =
|
||||
widgets::AttachedWindow::get(::GetForegroundWindow(), winId, yOffset);
|
||||
window->setChannel(app->twitch.server->getOrAddChannel(name));
|
||||
window->show();
|
||||
if (args.height != -1) {
|
||||
auto *window = widgets::AttachedWindow::get(::GetForegroundWindow(), args);
|
||||
if (!name.isEmpty()) {
|
||||
window->setChannel(app->twitch.server->getOrAddChannel(name));
|
||||
}
|
||||
}
|
||||
// window->show();
|
||||
#endif
|
||||
}
|
||||
});
|
||||
@@ -179,12 +196,15 @@ void NativeMessagingManager::ReceiverThread::handleMessage(const QJsonObject &ro
|
||||
}
|
||||
|
||||
#ifdef USEWINSDK
|
||||
util::postToThread([winId] { widgets::AttachedWindow::detach(winId); });
|
||||
util::postToThread([winId] {
|
||||
qDebug() << "NW detach";
|
||||
widgets::AttachedWindow::detach(winId);
|
||||
});
|
||||
#endif
|
||||
} else {
|
||||
qDebug() << "NM unknown action " + action;
|
||||
}
|
||||
}
|
||||
} // namespace singletons
|
||||
|
||||
} // namespace singletons
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace singletons {
|
||||
class NativeMessagingManager
|
||||
{
|
||||
public:
|
||||
// fourtf: don't add this class to the application class
|
||||
NativeMessagingManager();
|
||||
|
||||
~NativeMessagingManager() = delete;
|
||||
|
||||
@@ -19,21 +19,21 @@ PathManager::PathManager(int argc, char **argv)
|
||||
.replace("/", "x");
|
||||
|
||||
// Options
|
||||
bool portable = false;
|
||||
this->portable = false;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (strcmp(argv[i], "portable") == 0) {
|
||||
portable = true;
|
||||
this->portable = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (QFileInfo::exists(QCoreApplication::applicationDirPath() + "/portable")) {
|
||||
portable = true;
|
||||
if (QFileInfo::exists(QCoreApplication::applicationDirPath() + "/this->portable")) {
|
||||
this->portable = true;
|
||||
}
|
||||
|
||||
// Root path = %APPDATA%/Chatterino or the folder that the executable resides in
|
||||
QString rootPath;
|
||||
if (portable) {
|
||||
if (this->portable) {
|
||||
rootPath.append(QCoreApplication::applicationDirPath());
|
||||
} else {
|
||||
// Get settings path
|
||||
@@ -91,5 +91,10 @@ bool PathManager::createFolder(const QString &folderPath)
|
||||
return QDir().mkpath(folderPath);
|
||||
}
|
||||
|
||||
bool PathManager::isPortable()
|
||||
{
|
||||
return this->portable;
|
||||
}
|
||||
|
||||
} // namespace singletons
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -28,6 +28,10 @@ public:
|
||||
QString appPathHash;
|
||||
|
||||
bool createFolder(const QString &folderPath);
|
||||
bool isPortable();
|
||||
|
||||
private:
|
||||
bool portable;
|
||||
};
|
||||
|
||||
} // namespace singletons
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "util/emotemap.hpp"
|
||||
|
||||
#include <QIcon>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <map>
|
||||
|
||||
@@ -39,8 +39,18 @@ void SettingManager::initialize()
|
||||
|
||||
this->timestampFormat.connect([](auto, auto) {
|
||||
auto app = getApp();
|
||||
app->windows->layoutVisibleChatWidgets();
|
||||
app->windows->layoutChannelViews();
|
||||
});
|
||||
|
||||
this->emoteScale.connect([](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
|
||||
|
||||
this->timestampFormat.connect([](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
|
||||
this->alternateMessageBackground.connect(
|
||||
[](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
|
||||
this->seperateMessages.connect(
|
||||
[](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
|
||||
this->collpseMessagesMinLines.connect(
|
||||
[](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
|
||||
}
|
||||
|
||||
MessageElement::Flags SettingManager::getWordFlags()
|
||||
|
||||
@@ -40,6 +40,8 @@ public:
|
||||
BoolSetting hideEmptyInput = {"/appearance/hideEmptyInputBox", false};
|
||||
BoolSetting showMessageLength = {"/appearance/messages/showMessageLength", false};
|
||||
BoolSetting seperateMessages = {"/appearance/messages/separateMessages", false};
|
||||
// BoolSetting collapseLongMessages = {"/appearance/messages/collapseLongMessages", false};
|
||||
IntSetting collpseMessagesMinLines = {"/appearance/messages/collapseMessagesMinLines", 0};
|
||||
BoolSetting alternateMessageBackground = {"/appearance/messages/alternateMessageBackground",
|
||||
false};
|
||||
BoolSetting windowTopMost = {"/appearance/windowAlwaysOnTop", false};
|
||||
@@ -54,6 +56,8 @@ public:
|
||||
/// Behaviour
|
||||
BoolSetting allowDuplicateMessages = {"/behaviour/allowDuplicateMessages", true};
|
||||
BoolSetting mentionUsersWithAt = {"/behaviour/mentionUsersWithAt", false};
|
||||
BoolSetting showJoins = {"/behaviour/showJoins", false};
|
||||
BoolSetting showParts = {"/behaviour/showParts", false};
|
||||
FloatSetting mouseScrollMultiplier = {"/behaviour/mouseScrollMultiplier", 1.0};
|
||||
|
||||
// Auto-completion
|
||||
@@ -75,10 +79,10 @@ public:
|
||||
BoolSetting enableGifAnimations = {"/emotes/enableGifAnimations", true};
|
||||
FloatSetting emoteScale = {"/emotes/scale", 1.f};
|
||||
|
||||
// 0 = Smallest size
|
||||
// 1 = One size above 0 (usually size of 0 * 2)
|
||||
// 2 = One size above 1 (usually size of 1 * 2)
|
||||
// etc...
|
||||
// 0 = No preference
|
||||
// 1 = 1x
|
||||
// 2 = 2x
|
||||
// 3 = 3x
|
||||
IntSetting preferredEmoteQuality = {"/emotes/preferredEmoteQuality", 0};
|
||||
|
||||
/// Links
|
||||
|
||||
@@ -52,7 +52,6 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
|
||||
isLight = multiplier > 0;
|
||||
bool lightWin = isLight;
|
||||
|
||||
QColor none(0, 0, 0, 0);
|
||||
QColor themeColor = QColor::fromHslF(hue, 0.43, 0.5);
|
||||
QColor themeColorNoSat = QColor::fromHslF(hue, 0, 0.5);
|
||||
|
||||
@@ -69,7 +68,7 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
|
||||
#ifdef Q_OS_LINUX
|
||||
this->window.background = lightWin ? "#fff" : QColor(61, 60, 56);
|
||||
#else
|
||||
this->window.background = lightWin ? "#fff" : "#444";
|
||||
this->window.background = lightWin ? "#fff" : "#111";
|
||||
#endif
|
||||
|
||||
QColor fg = this->window.text = lightWin ? "#000" : "#eee";
|
||||
@@ -89,27 +88,52 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
|
||||
|
||||
/// TABS
|
||||
if (lightWin) {
|
||||
this->tabs.regular = {fg, {bg, QColor("#ccc"), bg}};
|
||||
this->tabs.regular = {QColor("#444"),
|
||||
{QColor("#fff"), QColor("#fff"), QColor("#fff")},
|
||||
{QColor("#fff"), QColor("#fff"), QColor("#fff")}};
|
||||
this->tabs.newMessage = {
|
||||
fg,
|
||||
{QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
|
||||
QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
|
||||
QBrush(blendColors(themeColorNoSat, "#ccc", 0.9), Qt::FDiagPattern)}};
|
||||
this->tabs.highlighted = {fg, {QColor("#ccc"), QColor("#ccc"), QColor("#bbb")}};
|
||||
this->tabs.selected = {QColor("#fff"),
|
||||
{QColor("#777"), QColor("#777"), QColor("#888")}};
|
||||
} else {
|
||||
this->tabs.regular = {fg, {bg, QColor("#555"), bg}};
|
||||
this->tabs.newMessage = {
|
||||
fg,
|
||||
{QBrush(blendColors(themeColor, "#666", 0.7), Qt::FDiagPattern),
|
||||
QBrush(blendColors(themeColor, "#666", 0.5), Qt::FDiagPattern),
|
||||
QBrush(blendColors(themeColorNoSat, "#666", 0.7), Qt::FDiagPattern)}};
|
||||
this->tabs.highlighted = {fg, {QColor("#777"), QColor("#777"), QColor("#666")}};
|
||||
fg, {bg, QColor("#ccc"), bg}, {QColor("#aaa"), QColor("#aaa"), QColor("#aaa")}};
|
||||
this->tabs.highlighted = {fg,
|
||||
{bg, QColor("#ccc"), bg},
|
||||
{QColor("#b60505"), QColor("#b60505"), QColor("#b60505")}};
|
||||
this->tabs.selected = {QColor("#000"),
|
||||
{QColor("#999"), QColor("#999"), QColor("#888")}};
|
||||
{QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")},
|
||||
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
|
||||
} else {
|
||||
this->tabs.regular = {QColor("#aaa"),
|
||||
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
|
||||
{QColor("#444"), QColor("#444"), QColor("#444")}};
|
||||
this->tabs.newMessage = {fg,
|
||||
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
|
||||
{QColor("#888"), QColor("#888"), QColor("#888")}};
|
||||
this->tabs.highlighted = {fg,
|
||||
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
|
||||
{QColor("#ee6166"), QColor("#ee6166"), QColor("#ee6166")}};
|
||||
|
||||
this->tabs.selected = {QColor("#fff"),
|
||||
{QColor("#555555"), QColor("#555555"), QColor("#555555")},
|
||||
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
|
||||
}
|
||||
|
||||
// scrollbar
|
||||
this->scrollbars.highlights.highlight = QColor("#ee6166");
|
||||
this->scrollbars.highlights.subscription = QColor("#C466FF");
|
||||
|
||||
// this->tabs.newMessage = {
|
||||
// fg,
|
||||
// {QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
|
||||
// QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
|
||||
// QBrush(blendColors(themeColorNoSat, "#ccc", 0.9), Qt::FDiagPattern)}};
|
||||
|
||||
// this->tabs.newMessage = {
|
||||
// fg,
|
||||
// {QBrush(blendColors(themeColor, "#666", 0.7), Qt::FDiagPattern),
|
||||
// QBrush(blendColors(themeColor, "#666", 0.5), Qt::FDiagPattern),
|
||||
// QBrush(blendColors(themeColorNoSat, "#666", 0.7),
|
||||
// Qt::FDiagPattern)}};
|
||||
// this->tabs.highlighted = {fg, {QColor("#777"), QColor("#777"),
|
||||
// QColor("#666")}};
|
||||
|
||||
this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color();
|
||||
}
|
||||
|
||||
@@ -119,7 +143,12 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
|
||||
this->splits.messageSeperator = isLight ? QColor(127, 127, 127) : QColor(60, 60, 60);
|
||||
this->splits.background = getColor(0, sat, 1);
|
||||
this->splits.dropPreview = QColor(0, 148, 255, 0x30);
|
||||
this->splits.dropPreviewBorder = QColor(0, 148, 255, 0x70);
|
||||
this->splits.dropPreviewBorder = QColor(0, 148, 255, 0xff);
|
||||
this->splits.dropTargetRect = QColor(0, 148, 255, 0x00);
|
||||
this->splits.dropTargetRectBorder = QColor(0, 148, 255, 0x00);
|
||||
this->splits.resizeHandle = QColor(0, 148, 255, 0x70);
|
||||
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x20);
|
||||
|
||||
// this->splits.border
|
||||
// this->splits.borderFocused
|
||||
|
||||
@@ -143,7 +172,10 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
|
||||
this->messages.backgrounds.regular = splits.background;
|
||||
this->messages.backgrounds.alternate = getColor(0, sat, 0.93);
|
||||
this->messages.backgrounds.highlighted =
|
||||
blendColors(themeColor, this->messages.backgrounds.regular, 0.8);
|
||||
blendColors(themeColor, this->messages.backgrounds.regular, 0.6);
|
||||
this->messages.backgrounds.subscription =
|
||||
blendColors(QColor("#C466FF"), this->messages.backgrounds.regular, 0.7);
|
||||
|
||||
// this->messages.backgrounds.resub
|
||||
// this->messages.backgrounds.whisper
|
||||
this->messages.disabled = getColor(0, sat, 1, 0.6);
|
||||
@@ -151,7 +183,9 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
|
||||
// this->messages.seperatorInner =
|
||||
|
||||
// Scrollbar
|
||||
this->scrollbars.background = getColor(0, sat, 0.94);
|
||||
this->scrollbars.background = QColor(0, 0, 0, 0);
|
||||
// this->scrollbars.background = splits.background;
|
||||
// this->scrollbars.background.setAlphaF(qreal(0.2));
|
||||
this->scrollbars.thumb = getColor(0, sat, 0.80);
|
||||
this->scrollbars.thumbSelected = getColor(0, sat, 0.7);
|
||||
|
||||
@@ -163,13 +197,13 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
|
||||
this->messages.selection = isLightTheme() ? QColor(0, 0, 0, 64) : QColor(255, 255, 255, 64);
|
||||
|
||||
this->updated.invoke();
|
||||
}
|
||||
} // namespace singletons
|
||||
|
||||
QColor ThemeManager::blendColors(const QColor &color1, const QColor &color2, qreal ratio)
|
||||
{
|
||||
int r = color1.red() * (1 - ratio) + color2.red() * ratio;
|
||||
int g = color1.green() * (1 - ratio) + color2.green() * ratio;
|
||||
int b = color1.blue() * (1 - ratio) + color2.blue() * ratio;
|
||||
int r = int(color1.red() * (1 - ratio) + color2.red() * ratio);
|
||||
int g = int(color1.green() * (1 - ratio) + color2.green() * ratio);
|
||||
int b = int(color1.blue() * (1 - ratio) + color2.blue() * ratio);
|
||||
|
||||
return QColor(r, g, b, 255);
|
||||
}
|
||||
@@ -177,22 +211,22 @@ QColor ThemeManager::blendColors(const QColor &color1, const QColor &color2, qre
|
||||
void ThemeManager::normalizeColor(QColor &color)
|
||||
{
|
||||
if (this->isLight) {
|
||||
if (color.lightnessF() > 0.5f) {
|
||||
color.setHslF(color.hueF(), color.saturationF(), 0.5f);
|
||||
if (color.lightnessF() > 0.5) {
|
||||
color.setHslF(color.hueF(), color.saturationF(), 0.5);
|
||||
}
|
||||
|
||||
if (color.lightnessF() > 0.4f && color.hueF() > 0.1 && color.hueF() < 0.33333) {
|
||||
if (color.lightnessF() > 0.4 && color.hueF() > 0.1 && color.hueF() < 0.33333) {
|
||||
color.setHslF(
|
||||
color.hueF(), color.saturationF(),
|
||||
color.lightnessF() - sin((color.hueF() - 0.1) / (0.3333 - 0.1) * 3.14159) *
|
||||
color.saturationF() * 0.2);
|
||||
}
|
||||
} else {
|
||||
if (color.lightnessF() < 0.5f) {
|
||||
color.setHslF(color.hueF(), color.saturationF(), 0.5f);
|
||||
if (color.lightnessF() < 0.5) {
|
||||
color.setHslF(color.hueF(), color.saturationF(), 0.5);
|
||||
}
|
||||
|
||||
if (color.lightnessF() < 0.6f && color.hueF() > 0.54444 && color.hueF() < 0.83333) {
|
||||
if (color.lightnessF() < 0.6 && color.hueF() > 0.54444 && color.hueF() < 0.83333) {
|
||||
color.setHslF(
|
||||
color.hueF(), color.saturationF(),
|
||||
color.lightnessF() + sin((color.hueF() - 0.54444) / (0.8333 - 0.54444) * 3.14159) *
|
||||
|
||||
@@ -25,11 +25,16 @@ public:
|
||||
|
||||
struct TabColors {
|
||||
QColor text;
|
||||
struct Backgrounds {
|
||||
struct {
|
||||
QBrush regular;
|
||||
QBrush hover;
|
||||
QBrush unfocused;
|
||||
} backgrounds;
|
||||
struct {
|
||||
QColor regular;
|
||||
QColor hover;
|
||||
QColor unfocused;
|
||||
} line;
|
||||
};
|
||||
|
||||
/// WINDOW
|
||||
@@ -43,9 +48,9 @@ public:
|
||||
/// TABS
|
||||
struct {
|
||||
TabColors regular;
|
||||
TabColors selected;
|
||||
TabColors highlighted;
|
||||
TabColors newMessage;
|
||||
TabColors highlighted;
|
||||
TabColors selected;
|
||||
QColor border;
|
||||
QColor bottomLine;
|
||||
} tabs;
|
||||
@@ -58,6 +63,10 @@ public:
|
||||
QColor borderFocused;
|
||||
QColor dropPreview;
|
||||
QColor dropPreviewBorder;
|
||||
QColor dropTargetRect;
|
||||
QColor dropTargetRectBorder;
|
||||
QColor resizeHandle;
|
||||
QColor resizeHandleBackground;
|
||||
|
||||
struct {
|
||||
QColor border;
|
||||
@@ -89,7 +98,7 @@ public:
|
||||
QColor regular;
|
||||
QColor alternate;
|
||||
QColor highlighted;
|
||||
// QColor resub;
|
||||
QColor subscription;
|
||||
// QColor whisper;
|
||||
} backgrounds;
|
||||
|
||||
@@ -104,8 +113,10 @@ public:
|
||||
QColor background;
|
||||
QColor thumb;
|
||||
QColor thumbSelected;
|
||||
// const int highlightsCount = 3;
|
||||
// QColor highlights[3];
|
||||
struct {
|
||||
QColor highlight;
|
||||
QColor subscription;
|
||||
} highlights;
|
||||
} scrollbars;
|
||||
|
||||
/// TOOLTIP
|
||||
|
||||
@@ -7,25 +7,31 @@ namespace chatterino {
|
||||
namespace singletons {
|
||||
|
||||
UpdateManager::UpdateManager()
|
||||
: currentVersion(CHATTERINO_VERSION)
|
||||
: currentVersion_(CHATTERINO_VERSION)
|
||||
{
|
||||
qDebug() << "init UpdateManager";
|
||||
}
|
||||
|
||||
UpdateManager &UpdateManager::getInstance()
|
||||
{
|
||||
// fourtf: don't add this class to the application class
|
||||
static UpdateManager instance;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
const QString &UpdateManager::getCurrentVersion() const
|
||||
{
|
||||
return this->getCurrentVersion();
|
||||
return currentVersion_;
|
||||
}
|
||||
|
||||
const QString &UpdateManager::getOnlineVersion() const
|
||||
{
|
||||
return this->getOnlineVersion();
|
||||
return onlineVersion_;
|
||||
}
|
||||
|
||||
void UpdateManager::installUpdates()
|
||||
{
|
||||
}
|
||||
|
||||
void UpdateManager::checkForUpdates()
|
||||
@@ -33,17 +39,38 @@ void UpdateManager::checkForUpdates()
|
||||
QString url = "https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS "/stable";
|
||||
|
||||
util::NetworkRequest req(url);
|
||||
req.setTimeout(20000);
|
||||
req.setTimeout(30000);
|
||||
req.getJSON([this](QJsonObject &object) {
|
||||
QJsonValue version_val = object.value("version");
|
||||
if (!version_val.isString()) {
|
||||
this->setStatus_(Error);
|
||||
qDebug() << "error updating";
|
||||
return;
|
||||
}
|
||||
|
||||
this->onlineVersion = version_val.toString();
|
||||
this->onlineVersionUpdated.invoke();
|
||||
this->onlineVersion_ = version_val.toString();
|
||||
|
||||
if (this->currentVersion_ != this->onlineVersion_) {
|
||||
this->setStatus_(UpdateAvailable);
|
||||
} else {
|
||||
this->setStatus_(NoUpdateAvailable);
|
||||
}
|
||||
});
|
||||
this->setStatus_(Searching);
|
||||
req.execute();
|
||||
}
|
||||
|
||||
UpdateManager::UpdateStatus UpdateManager::getStatus() const
|
||||
{
|
||||
return this->status_;
|
||||
}
|
||||
|
||||
void UpdateManager::setStatus_(UpdateStatus status)
|
||||
{
|
||||
if (this->status_ != status) {
|
||||
this->status_ = status;
|
||||
this->statusUpdated.invoke(status);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace singletons
|
||||
|
||||
@@ -11,17 +11,25 @@ class UpdateManager
|
||||
UpdateManager();
|
||||
|
||||
public:
|
||||
enum UpdateStatus { None, Searching, UpdateAvailable, NoUpdateAvailable, Error };
|
||||
|
||||
// fourtf: don't add this class to the application class
|
||||
static UpdateManager &getInstance();
|
||||
|
||||
void checkForUpdates();
|
||||
const QString &getCurrentVersion() const;
|
||||
const QString &getOnlineVersion() const;
|
||||
void installUpdates();
|
||||
UpdateStatus getStatus() const;
|
||||
|
||||
pajlada::Signals::NoArgSignal onlineVersionUpdated;
|
||||
pajlada::Signals::Signal<UpdateStatus> statusUpdated;
|
||||
|
||||
private:
|
||||
QString currentVersion;
|
||||
QString onlineVersion;
|
||||
QString currentVersion_;
|
||||
QString onlineVersion_;
|
||||
UpdateStatus status_ = None;
|
||||
|
||||
void setStatus_(UpdateStatus status);
|
||||
};
|
||||
|
||||
} // namespace singletons
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "widgets/accountswitchpopupwidget.hpp"
|
||||
#include "widgets/settingsdialog.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
@@ -20,6 +21,9 @@
|
||||
namespace chatterino {
|
||||
namespace singletons {
|
||||
|
||||
using SplitNode = widgets::SplitContainer::Node;
|
||||
using SplitDirection = widgets::SplitContainer::Direction;
|
||||
|
||||
void WindowManager::showSettingsDialog()
|
||||
{
|
||||
QTimer::singleShot(80, [] { widgets::SettingsDialog::showDialog(); });
|
||||
@@ -54,11 +58,17 @@ WindowManager::WindowManager()
|
||||
qDebug() << "init WindowManager";
|
||||
}
|
||||
|
||||
void WindowManager::layoutVisibleChatWidgets(Channel *channel)
|
||||
void WindowManager::layoutChannelViews(Channel *channel)
|
||||
{
|
||||
this->layout.invoke(channel);
|
||||
}
|
||||
|
||||
void WindowManager::forceLayoutChannelViews()
|
||||
{
|
||||
this->incGeneration();
|
||||
this->layoutChannelViews(nullptr);
|
||||
}
|
||||
|
||||
void WindowManager::repaintVisibleChatWidgets(Channel *channel)
|
||||
{
|
||||
if (this->mainWindow != nullptr) {
|
||||
@@ -184,32 +194,40 @@ void WindowManager::initialize()
|
||||
// load tabs
|
||||
QJsonArray tabs = window_obj.value("tabs").toArray();
|
||||
for (QJsonValue tab_val : tabs) {
|
||||
widgets::SplitContainer *tab = window.getNotebook().addNewPage();
|
||||
widgets::SplitContainer *page = window.getNotebook().addPage(false);
|
||||
|
||||
QJsonObject tab_obj = tab_val.toObject();
|
||||
|
||||
// set custom title
|
||||
QJsonValue title_val = tab_obj.value("title");
|
||||
if (title_val.isString()) {
|
||||
tab->getTab()->setTitle(title_val.toString());
|
||||
tab->getTab()->useDefaultTitle = false;
|
||||
page->getTab()->setCustomTitle(title_val.toString());
|
||||
}
|
||||
|
||||
// selected
|
||||
if (tab_obj.value("selected").toBool(false)) {
|
||||
window.getNotebook().select(tab);
|
||||
window.getNotebook().select(page);
|
||||
}
|
||||
|
||||
// load splits
|
||||
QJsonObject splitRoot = tab_obj.value("splits2").toObject();
|
||||
|
||||
if (!splitRoot.isEmpty()) {
|
||||
page->decodeFromJson(splitRoot);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// fallback load splits (old)
|
||||
int colNr = 0;
|
||||
for (QJsonValue column_val : tab_obj.value("splits").toArray()) {
|
||||
for (QJsonValue split_val : column_val.toArray()) {
|
||||
widgets::Split *split = new widgets::Split(tab);
|
||||
widgets::Split *split = new widgets::Split(page);
|
||||
|
||||
QJsonObject split_obj = split_val.toObject();
|
||||
split->setChannel(this->decodeChannel(split_obj));
|
||||
split->setChannel(decodeChannel(split_obj));
|
||||
|
||||
tab->addToLayout(split, std::make_pair(colNr, 10000000));
|
||||
page->appendSplit(split);
|
||||
}
|
||||
colNr++;
|
||||
}
|
||||
@@ -218,7 +236,7 @@ void WindowManager::initialize()
|
||||
|
||||
if (mainWindow == nullptr) {
|
||||
mainWindow = &createWindow(widgets::Window::Main);
|
||||
mainWindow->getNotebook().addNewPage(true);
|
||||
mainWindow->getNotebook().addPage(true);
|
||||
}
|
||||
|
||||
this->initialized = true;
|
||||
@@ -255,13 +273,15 @@ void WindowManager::save()
|
||||
// window tabs
|
||||
QJsonArray tabs_arr;
|
||||
|
||||
for (int tab_i = 0; tab_i < window->getNotebook().tabCount(); tab_i++) {
|
||||
for (int tab_i = 0; tab_i < window->getNotebook().getPageCount(); tab_i++) {
|
||||
QJsonObject tab_obj;
|
||||
widgets::SplitContainer *tab = window->getNotebook().tabAt(tab_i);
|
||||
widgets::SplitContainer *tab =
|
||||
dynamic_cast<widgets::SplitContainer *>(window->getNotebook().getPageAt(tab_i));
|
||||
assert(tab != nullptr);
|
||||
|
||||
// custom tab title
|
||||
if (!tab->getTab()->useDefaultTitle) {
|
||||
tab_obj.insert("title", tab->getTab()->getTitle());
|
||||
if (tab->getTab()->hasCustomTitle()) {
|
||||
tab_obj.insert("title", tab->getTab()->getCustomTitle());
|
||||
}
|
||||
|
||||
// selected
|
||||
@@ -270,23 +290,11 @@ void WindowManager::save()
|
||||
}
|
||||
|
||||
// splits
|
||||
QJsonArray columns_arr;
|
||||
std::vector<std::vector<widgets::Split *>> columns = tab->getColumns();
|
||||
QJsonObject splits;
|
||||
|
||||
for (std::vector<widgets::Split *> &cells : columns) {
|
||||
QJsonArray cells_arr;
|
||||
this->encodeNodeRecusively(tab->getBaseNode(), splits);
|
||||
|
||||
for (widgets::Split *cell : cells) {
|
||||
QJsonObject cell_obj;
|
||||
|
||||
this->encodeChannel(cell->getIndirectChannel(), cell_obj);
|
||||
|
||||
cells_arr.append(cell_obj);
|
||||
}
|
||||
columns_arr.append(cells_arr);
|
||||
}
|
||||
|
||||
tab_obj.insert("splits", columns_arr);
|
||||
tab_obj.insert("splits2", splits);
|
||||
tabs_arr.append(tab_obj);
|
||||
}
|
||||
|
||||
@@ -302,10 +310,46 @@ void WindowManager::save()
|
||||
QString settingsPath = app->paths->settingsFolderPath + SETTINGS_FILENAME;
|
||||
QFile file(settingsPath);
|
||||
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
file.write(document.toJson());
|
||||
|
||||
QJsonDocument::JsonFormat format =
|
||||
#ifdef _DEBUG
|
||||
QJsonDocument::JsonFormat::Compact
|
||||
#else
|
||||
(QJsonDocument::JsonFormat)0
|
||||
#endif
|
||||
;
|
||||
|
||||
file.write(document.toJson(format));
|
||||
file.flush();
|
||||
}
|
||||
|
||||
void WindowManager::encodeNodeRecusively(SplitNode *node, QJsonObject &obj)
|
||||
{
|
||||
switch (node->getType()) {
|
||||
case SplitNode::_Split: {
|
||||
obj.insert("type", "split");
|
||||
QJsonObject split;
|
||||
encodeChannel(node->getSplit()->getIndirectChannel(), split);
|
||||
obj.insert("data", split);
|
||||
obj.insert("flexh", node->getHorizontalFlex());
|
||||
obj.insert("flexv", node->getVerticalFlex());
|
||||
} break;
|
||||
case SplitNode::HorizontalContainer:
|
||||
case SplitNode::VerticalContainer: {
|
||||
obj.insert("type", node->getType() == SplitNode::HorizontalContainer ? "horizontal"
|
||||
: "vertical");
|
||||
|
||||
QJsonArray items_arr;
|
||||
for (const std::unique_ptr<SplitNode> &n : node->getChildren()) {
|
||||
QJsonObject subObj;
|
||||
this->encodeNodeRecusively(n.get(), subObj);
|
||||
items_arr.append(subObj);
|
||||
}
|
||||
obj.insert("items", items_arr);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::encodeChannel(IndirectChannel channel, QJsonObject &obj)
|
||||
{
|
||||
util::assertInGuiThread();
|
||||
@@ -356,5 +400,15 @@ void WindowManager::closeAll()
|
||||
}
|
||||
}
|
||||
|
||||
int WindowManager::getGeneration() const
|
||||
{
|
||||
return this->generation;
|
||||
}
|
||||
|
||||
void WindowManager::incGeneration()
|
||||
{
|
||||
this->generation++;
|
||||
}
|
||||
|
||||
} // namespace singletons
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/splitcontainer.hpp"
|
||||
#include "widgets/window.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
// namespace widgets {
|
||||
// struct SplitContainer::Node;
|
||||
//}
|
||||
namespace singletons {
|
||||
|
||||
class WindowManager
|
||||
@@ -15,7 +19,8 @@ public:
|
||||
void showSettingsDialog();
|
||||
void showAccountSelectPopup(QPoint point);
|
||||
|
||||
void layoutVisibleChatWidgets(Channel *channel = nullptr);
|
||||
void layoutChannelViews(Channel *channel = nullptr);
|
||||
void forceLayoutChannelViews();
|
||||
void repaintVisibleChatWidgets(Channel *channel = nullptr);
|
||||
void repaintGifEmotes();
|
||||
// void updateAll();
|
||||
@@ -31,19 +36,27 @@ public:
|
||||
void initialize();
|
||||
void closeAll();
|
||||
|
||||
int getGeneration() const;
|
||||
void incGeneration();
|
||||
|
||||
pajlada::Signals::NoArgSignal repaintGifs;
|
||||
pajlada::Signals::Signal<Channel *> layout;
|
||||
|
||||
private:
|
||||
bool initialized = false;
|
||||
|
||||
std::atomic<int> generation{0};
|
||||
|
||||
std::vector<widgets::Window *> windows;
|
||||
|
||||
widgets::Window *mainWindow = nullptr;
|
||||
widgets::Window *selectedWindow = nullptr;
|
||||
|
||||
void encodeChannel(IndirectChannel channel, QJsonObject &obj);
|
||||
IndirectChannel decodeChannel(const QJsonObject &obj);
|
||||
void encodeNodeRecusively(widgets::SplitContainer::Node *node, QJsonObject &obj);
|
||||
|
||||
public:
|
||||
static void encodeChannel(IndirectChannel channel, QJsonObject &obj);
|
||||
static IndirectChannel decodeChannel(const QJsonObject &obj);
|
||||
};
|
||||
|
||||
} // namespace singletons
|
||||
|
||||
+25
-1
@@ -3,6 +3,7 @@
|
||||
#include <QDebug>
|
||||
#include <QElapsedTimer>
|
||||
#include <boost/current_function.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#define BENCH(x) \
|
||||
QElapsedTimer x; \
|
||||
@@ -10,4 +11,27 @@
|
||||
|
||||
#define MARK(x) \
|
||||
qDebug() << BOOST_CURRENT_FUNCTION << __LINE__ \
|
||||
<< static_cast<float>(x.nsecsElapsed()) / 100000.0 << "ms";
|
||||
<< static_cast<float>(x.nsecsElapsed()) / 1000000.0 << "ms";
|
||||
|
||||
class BenchmarkGuard : boost::noncopyable
|
||||
{
|
||||
QElapsedTimer timer;
|
||||
QString name;
|
||||
|
||||
public:
|
||||
BenchmarkGuard(const QString &_name)
|
||||
: name(_name)
|
||||
{
|
||||
timer.start();
|
||||
}
|
||||
|
||||
~BenchmarkGuard()
|
||||
{
|
||||
qDebug() << this->name << float(timer.nsecsElapsed()) / 1000000.0f << "ms";
|
||||
}
|
||||
|
||||
qreal getElapsedMs()
|
||||
{
|
||||
return qreal(timer.nsecsElapsed()) / 1000000.0;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "emotemap.hpp"
|
||||
|
||||
#include "application.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace util {
|
||||
|
||||
EmoteData::EmoteData(messages::Image *_image)
|
||||
: image1x(_image)
|
||||
{
|
||||
}
|
||||
|
||||
// Emotes must have a 1x image to be valid
|
||||
bool EmoteData::isValid() const
|
||||
{
|
||||
return this->image1x != nullptr;
|
||||
}
|
||||
|
||||
messages::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;
|
||||
}();
|
||||
}
|
||||
|
||||
messages::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 util
|
||||
} // namespace chatterino
|
||||
@@ -9,20 +9,18 @@ namespace util {
|
||||
struct EmoteData {
|
||||
EmoteData() = default;
|
||||
|
||||
EmoteData(messages::Image *_image)
|
||||
: image1x(_image)
|
||||
{
|
||||
}
|
||||
EmoteData(messages::Image *_image);
|
||||
|
||||
// Emotes must have a 1x image to be valid
|
||||
bool isValid() const
|
||||
{
|
||||
return this->image1x != nullptr;
|
||||
}
|
||||
bool isValid() const;
|
||||
messages::Image *getImage(float scale) const;
|
||||
|
||||
messages::Image *image1x = nullptr;
|
||||
messages::Image *image2x = nullptr;
|
||||
messages::Image *image3x = nullptr;
|
||||
|
||||
// Link to the emote page i.e. https://www.frankerfacez.com/emoticon/144722-pajaCringe
|
||||
QString pageLink;
|
||||
};
|
||||
|
||||
using EmoteMap = ConcurrentMap<QString, EmoteData>;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
namespace chatterino {
|
||||
namespace util {
|
||||
|
||||
inline QString ParseTagString(const QString &input)
|
||||
inline QString parseTagString(const QString &input)
|
||||
{
|
||||
QString output = input;
|
||||
output.detach();
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace chatterino {
|
||||
namespace util {
|
||||
|
||||
template <typename T>
|
||||
class MutexValue
|
||||
{
|
||||
mutable std::mutex mutex;
|
||||
T value;
|
||||
|
||||
public:
|
||||
MutexValue()
|
||||
{
|
||||
}
|
||||
|
||||
MutexValue(T &&val)
|
||||
: value(val)
|
||||
{
|
||||
}
|
||||
|
||||
T get() const
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex);
|
||||
|
||||
return this->value;
|
||||
}
|
||||
|
||||
void set(const T &val)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(this->mutex);
|
||||
|
||||
this->value = val;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
} // namespace chatterino
|
||||
+29
-25
@@ -216,7 +216,7 @@ public:
|
||||
|
||||
if (this->data.caller != nullptr) {
|
||||
QObject::connect(worker, &NetworkWorker::doneUrl, this->data.caller,
|
||||
[ onFinished, data = this->data ](auto reply) mutable {
|
||||
[onFinished, data = this->data](auto reply) mutable {
|
||||
if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||
// TODO: We might want to call an onError callback here
|
||||
return;
|
||||
@@ -238,7 +238,7 @@ public:
|
||||
|
||||
QObject::connect(
|
||||
&requester, &NetworkRequester::requestUrl, worker,
|
||||
[ timer, data = std::move(this->data), worker, onFinished{std::move(onFinished)} ]() {
|
||||
[timer, data = std::move(this->data), worker, onFinished{std::move(onFinished)}]() {
|
||||
QNetworkReply *reply = NetworkManager::NaM.get(data.request);
|
||||
|
||||
if (timer != nullptr) {
|
||||
@@ -253,21 +253,21 @@ public:
|
||||
data.onReplyCreated(reply);
|
||||
}
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, worker, [
|
||||
data = std::move(data), worker, reply, onFinished = std::move(onFinished)
|
||||
]() mutable {
|
||||
if (data.caller == nullptr) {
|
||||
QByteArray bytes = reply->readAll();
|
||||
data.writeToCache(bytes);
|
||||
onFinished(bytes);
|
||||
QObject::connect(reply, &QNetworkReply::finished, worker,
|
||||
[data = std::move(data), worker, reply,
|
||||
onFinished = std::move(onFinished)]() mutable {
|
||||
if (data.caller == nullptr) {
|
||||
QByteArray bytes = reply->readAll();
|
||||
data.writeToCache(bytes);
|
||||
onFinished(bytes);
|
||||
|
||||
reply->deleteLater();
|
||||
} else {
|
||||
emit worker->doneUrl(reply);
|
||||
}
|
||||
reply->deleteLater();
|
||||
} else {
|
||||
emit worker->doneUrl(reply);
|
||||
}
|
||||
|
||||
delete worker;
|
||||
});
|
||||
delete worker;
|
||||
});
|
||||
});
|
||||
|
||||
emit requester.requestUrl();
|
||||
@@ -276,7 +276,7 @@ public:
|
||||
template <typename FinishedCallback>
|
||||
void getJSON(FinishedCallback onFinished)
|
||||
{
|
||||
this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes)->bool {
|
||||
this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes) -> bool {
|
||||
auto object = parseJSONFromData(bytes);
|
||||
onFinished(object);
|
||||
|
||||
@@ -289,7 +289,7 @@ public:
|
||||
template <typename FinishedCallback>
|
||||
void getJSON2(FinishedCallback onFinished)
|
||||
{
|
||||
this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes)->bool {
|
||||
this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes) -> bool {
|
||||
auto object = parseJSONFromData2(bytes);
|
||||
onFinished(object);
|
||||
|
||||
@@ -303,6 +303,7 @@ public:
|
||||
{
|
||||
switch (this->data.requestType) {
|
||||
case GetRequest: {
|
||||
debug::Log("Call GET request!");
|
||||
this->executeGet();
|
||||
} break;
|
||||
|
||||
@@ -369,10 +370,8 @@ private:
|
||||
worker->moveToThread(&NetworkManager::workerThread);
|
||||
|
||||
if (this->data.caller != nullptr) {
|
||||
QObject::connect(worker, &NetworkWorker::doneUrl,
|
||||
this->data.caller, [data = this->data](auto reply) mutable {
|
||||
auto &dat = data;
|
||||
|
||||
QObject::connect(worker, &NetworkWorker::doneUrl, this->data.caller,
|
||||
[data = this->data](auto reply) mutable {
|
||||
if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||
if (data.onError) {
|
||||
data.onError(reply->error());
|
||||
@@ -395,8 +394,8 @@ private:
|
||||
}
|
||||
|
||||
QObject::connect(&requester, &NetworkRequester::requestUrl, worker,
|
||||
[ timer, data = std::move(this->data), worker ]() {
|
||||
QNetworkReply *reply;
|
||||
[timer, data = std::move(this->data), worker]() {
|
||||
QNetworkReply *reply = nullptr;
|
||||
switch (data.requestType) {
|
||||
case GetRequest: {
|
||||
reply = NetworkManager::NaM.get(data.request);
|
||||
@@ -431,11 +430,16 @@ private:
|
||||
}
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, worker,
|
||||
[ data = std::move(data), worker, reply ]() mutable {
|
||||
[data = std::move(data), worker, reply]() mutable {
|
||||
if (data.caller == nullptr) {
|
||||
QByteArray bytes = reply->readAll();
|
||||
data.writeToCache(bytes);
|
||||
data.onSuccess(parseJSONFromData2(bytes));
|
||||
|
||||
if (data.onSuccess) {
|
||||
data.onSuccess(parseJSONFromData2(bytes));
|
||||
} else {
|
||||
qWarning() << "data.onSuccess not found";
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<QString> {
|
||||
std::size_t operator()(const QString &s) const
|
||||
{
|
||||
return qHash(s);
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
+17
-17
@@ -11,6 +11,13 @@
|
||||
namespace chatterino {
|
||||
namespace util {
|
||||
|
||||
template <typename TVectorItem>
|
||||
struct SignalVectorItemArgs {
|
||||
const TVectorItem &item;
|
||||
int index;
|
||||
void *caller;
|
||||
};
|
||||
|
||||
template <typename TVectorItem>
|
||||
class ReadOnlySignalVector : boost::noncopyable
|
||||
{
|
||||
@@ -24,14 +31,8 @@ public:
|
||||
}
|
||||
virtual ~ReadOnlySignalVector() = default;
|
||||
|
||||
struct ItemArgs {
|
||||
const TVectorItem &item;
|
||||
int index;
|
||||
void *caller;
|
||||
};
|
||||
|
||||
pajlada::Signals::Signal<ItemArgs> itemInserted;
|
||||
pajlada::Signals::Signal<ItemArgs> itemRemoved;
|
||||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemInserted;
|
||||
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved;
|
||||
pajlada::Signals::NoArgSignal delayedItemsChanged;
|
||||
|
||||
const std::vector<TVectorItem> &getVector() const
|
||||
@@ -69,7 +70,7 @@ public:
|
||||
|
||||
TVectorItem item = this->vector[index];
|
||||
this->vector.erase(this->vector.begin() + index);
|
||||
typename ReadOnlySignalVector<TVectorItem>::ItemArgs args{item, index, caller};
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemRemoved.invoke(args);
|
||||
|
||||
this->invokeDelayedItemsChanged();
|
||||
@@ -96,7 +97,7 @@ public:
|
||||
|
||||
this->vector.insert(this->vector.begin() + index, item);
|
||||
|
||||
typename ReadOnlySignalVector<TVectorItem>::ItemArgs args{item, index, caller};
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemInserted.invoke(args);
|
||||
this->invokeDelayedItemsChanged();
|
||||
return index;
|
||||
@@ -107,16 +108,15 @@ template <typename TVectorItem, typename Compare>
|
||||
class SortedSignalVector : public BaseSignalVector<TVectorItem>
|
||||
{
|
||||
public:
|
||||
virtual int insertItem(const TVectorItem &item, int proposedIndex = -1,
|
||||
void *caller = 0) override
|
||||
virtual int insertItem(const TVectorItem &item, int = -1, void *caller = nullptr) override
|
||||
{
|
||||
util::assertInGuiThread();
|
||||
|
||||
int index =
|
||||
this->vector.insert(
|
||||
std::lower_bound(this->vector.begin(), this->vector.end(), item, Compare{}), item) -
|
||||
this->vector.begin();
|
||||
typename ReadOnlySignalVector<TVectorItem>::ItemArgs args{item, index, caller};
|
||||
auto it = std::lower_bound(this->vector.begin(), this->vector.end(), item, Compare{});
|
||||
int index = it - this->vector.begin();
|
||||
this->vector.insert(it, item);
|
||||
|
||||
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
|
||||
this->itemInserted.invoke(args);
|
||||
this->invokeDelayedItemsChanged();
|
||||
return index;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
#include "signalvectormodel.hpp"
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QStandardItem>
|
||||
#include <boost/optional.hpp>
|
||||
#include <util/signalvector2.hpp>
|
||||
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
@@ -26,11 +27,7 @@ public:
|
||||
{
|
||||
this->vector = vec;
|
||||
|
||||
auto insert = [this](const typename BaseSignalVector<TVectorItem>::ItemArgs &args) {
|
||||
if (args.caller == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto insert = [this](const SignalVectorItemArgs<TVectorItem> &args) {
|
||||
// get row index
|
||||
int index = this->getModelIndexFromVectorIndex(args.index);
|
||||
assert(index >= 0 && index <= this->rows.size());
|
||||
@@ -43,13 +40,13 @@ public:
|
||||
index = this->beforeInsert(args.item, row, index);
|
||||
|
||||
this->beginInsertRows(QModelIndex(), index, index);
|
||||
this->rows.insert(this->rows.begin() + index, Row(row));
|
||||
this->rows.insert(this->rows.begin() + index, Row(row, args.item));
|
||||
this->endInsertRows();
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
for (const TVectorItem &item : vec->getVector()) {
|
||||
typename BaseSignalVector<TVectorItem>::ItemArgs args{item, i++, 0};
|
||||
SignalVectorItemArgs<TVectorItem> args{item, i++, 0};
|
||||
|
||||
insert(args);
|
||||
}
|
||||
@@ -57,20 +54,21 @@ public:
|
||||
this->managedConnect(vec->itemInserted, insert);
|
||||
|
||||
this->managedConnect(vec->itemRemoved, [this](auto args) {
|
||||
if (args.caller == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
int row = this->getModelIndexFromVectorIndex(args.index);
|
||||
assert(row >= 0 && row <= this->rows.size());
|
||||
|
||||
// remove row
|
||||
std::vector<QStandardItem *> items = std::move(this->rows[row].items);
|
||||
|
||||
this->beginRemoveRows(QModelIndex(), row, row);
|
||||
for (QStandardItem *item : this->rows[row].items) {
|
||||
delete item;
|
||||
}
|
||||
this->rows.erase(this->rows.begin() + row);
|
||||
this->endRemoveRows();
|
||||
|
||||
this->afterRemoved(args.item, items, row);
|
||||
|
||||
for (QStandardItem *item : items) {
|
||||
delete item;
|
||||
}
|
||||
});
|
||||
|
||||
this->afterInit();
|
||||
@@ -117,7 +115,10 @@ public:
|
||||
} else {
|
||||
int vecRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector->removeItem(vecRow, this);
|
||||
TVectorItem item = this->getItemFromRow(this->rows[row].items);
|
||||
|
||||
assert(this->rows[row].original);
|
||||
TVectorItem item =
|
||||
this->getItemFromRow(this->rows[row].items, this->rows[row].original.get());
|
||||
this->vector->insertItem(item, vecRow, this);
|
||||
}
|
||||
|
||||
@@ -181,7 +182,7 @@ public:
|
||||
assert(row >= 0 && row < this->rows.size());
|
||||
|
||||
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
||||
this->vector->removeItem(signalVectorRow);
|
||||
this->vector->removeItem(signalVectorRow, this);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -192,7 +193,8 @@ protected:
|
||||
}
|
||||
|
||||
// turn a vector item into a model row
|
||||
virtual TVectorItem getItemFromRow(std::vector<QStandardItem *> &row) = 0;
|
||||
virtual TVectorItem getItemFromRow(std::vector<QStandardItem *> &row,
|
||||
const TVectorItem &original) = 0;
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
virtual void getRowFromItem(const TVectorItem &item, std::vector<QStandardItem *> &row) = 0;
|
||||
@@ -221,6 +223,16 @@ protected:
|
||||
this->endInsertRows();
|
||||
}
|
||||
|
||||
void removeCustomRow(int index)
|
||||
{
|
||||
assert(index >= 0 && index <= this->rows.size());
|
||||
assert(this->rows[index].isCustomRow);
|
||||
|
||||
this->beginRemoveRows(QModelIndex(), index, index);
|
||||
this->rows.erase(this->rows.begin() + index);
|
||||
this->endRemoveRows();
|
||||
}
|
||||
|
||||
std::vector<QStandardItem *> createRow()
|
||||
{
|
||||
std::vector<QStandardItem *> row;
|
||||
@@ -232,6 +244,7 @@ protected:
|
||||
|
||||
struct Row {
|
||||
std::vector<QStandardItem *> items;
|
||||
boost::optional<TVectorItem> original;
|
||||
bool isCustomRow;
|
||||
|
||||
Row(std::vector<QStandardItem *> _items, bool _isCustomRow = false)
|
||||
@@ -239,6 +252,14 @@ protected:
|
||||
, isCustomRow(_isCustomRow)
|
||||
{
|
||||
}
|
||||
|
||||
Row(std::vector<QStandardItem *> _items, const TVectorItem &_original,
|
||||
bool _isCustomRow = false)
|
||||
: items(std::move(_items))
|
||||
, original(_original)
|
||||
, isCustomRow(_isCustomRow)
|
||||
{
|
||||
}
|
||||
};
|
||||
std::vector<Row> rows;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "controllers/accounts/accountcontroller.hpp"
|
||||
#include "credentials.hpp"
|
||||
#include "debug/log.hpp"
|
||||
#include "singletons/accountmanager.hpp"
|
||||
#include "util/networkmanager.hpp"
|
||||
#include "util/networkrequest.hpp"
|
||||
|
||||
@@ -100,7 +100,7 @@ static void put(QUrl url, std::function<void(QJsonObject)> successCallback)
|
||||
{
|
||||
QNetworkRequest request(url);
|
||||
|
||||
auto currentTwitchUser = getApp()->accounts->Twitch.getCurrent();
|
||||
auto currentTwitchUser = getApp()->accounts->twitch.getCurrent();
|
||||
QByteArray oauthToken;
|
||||
if (currentTwitchUser) {
|
||||
oauthToken = currentTwitchUser->getOAuthToken().toUtf8();
|
||||
@@ -130,7 +130,7 @@ static void sendDelete(QUrl url, std::function<void()> successCallback)
|
||||
{
|
||||
QNetworkRequest request(url);
|
||||
|
||||
auto currentTwitchUser = getApp()->accounts->Twitch.getCurrent();
|
||||
auto currentTwitchUser = getApp()->accounts->twitch.getCurrent();
|
||||
QByteArray oauthToken;
|
||||
if (currentTwitchUser) {
|
||||
oauthToken = currentTwitchUser->getOAuthToken().toUtf8();
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "application.hpp"
|
||||
#include "channel.hpp"
|
||||
#include "credentials.hpp"
|
||||
#include "singletons/accountmanager.hpp"
|
||||
#include "singletons/settingsmanager.hpp"
|
||||
#include "ui_accountpopupform.h"
|
||||
#include "util/urlfetch.hpp"
|
||||
@@ -40,8 +39,8 @@ AccountPopupWidget::AccountPopupWidget(ChannelPtr _channel)
|
||||
connect(this, &AccountPopupWidget::refreshButtons, this,
|
||||
&AccountPopupWidget::actuallyRefreshButtons, Qt::QueuedConnection);
|
||||
|
||||
app->accounts->Twitch.currentUserChanged.connect([=] {
|
||||
auto currentTwitchUser = app->accounts->Twitch.getCurrent();
|
||||
app->accounts->twitch.currentUserChanged.connect([this] {
|
||||
auto currentTwitchUser = getApp()->accounts->twitch.getCurrent();
|
||||
if (!currentTwitchUser) {
|
||||
// No twitch user set (should never happen)
|
||||
return;
|
||||
@@ -113,7 +112,7 @@ AccountPopupWidget::AccountPopupWidget(ChannelPtr _channel)
|
||||
});
|
||||
|
||||
QObject::connect(this->ui->ignore, &QPushButton::clicked, this, [=]() {
|
||||
auto currentUser = getApp()->accounts->Twitch.getCurrent();
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
if (!this->relationship.isIgnoring()) {
|
||||
currentUser->ignoreByID(this->popupWidgetUser.userID, this->popupWidgetUser.username,
|
||||
@@ -121,11 +120,11 @@ AccountPopupWidget::AccountPopupWidget(ChannelPtr _channel)
|
||||
switch (result) {
|
||||
case IgnoreResult_Success: {
|
||||
this->relationship.setIgnoring(true);
|
||||
emit refreshButtons();
|
||||
emit this->refreshButtons();
|
||||
} break;
|
||||
case IgnoreResult_AlreadyIgnored: {
|
||||
this->relationship.setIgnoring(true);
|
||||
emit refreshButtons();
|
||||
emit this->refreshButtons();
|
||||
} break;
|
||||
case IgnoreResult_Failed: {
|
||||
} break;
|
||||
@@ -137,7 +136,7 @@ AccountPopupWidget::AccountPopupWidget(ChannelPtr _channel)
|
||||
switch (result) {
|
||||
case UnignoreResult_Success: {
|
||||
this->relationship.setIgnoring(false);
|
||||
emit refreshButtons();
|
||||
emit this->refreshButtons();
|
||||
} break;
|
||||
case UnignoreResult_Failed: {
|
||||
} break;
|
||||
@@ -225,12 +224,12 @@ void AccountPopupWidget::getUserData()
|
||||
});
|
||||
|
||||
auto app = getApp();
|
||||
auto currentUser = app->accounts->Twitch.getCurrent();
|
||||
auto currentUser = app->accounts->twitch.getCurrent();
|
||||
|
||||
currentUser->checkFollow(this->popupWidgetUser.userID, [=](auto result) {
|
||||
this->relationship.setFollowing(result == FollowResult_Following);
|
||||
|
||||
emit refreshButtons();
|
||||
emit this->refreshButtons();
|
||||
});
|
||||
|
||||
bool isIgnoring = false;
|
||||
@@ -273,9 +272,9 @@ void AccountPopupWidget::loadAvatar(const QUrl &avatarUrl)
|
||||
void AccountPopupWidget::scaleChangedEvent(float newDpi)
|
||||
{
|
||||
this->setStyleSheet(QString("* { font-size: <font-size>px; }")
|
||||
.replace("<font-size>", QString::number((int)(12 * newDpi))));
|
||||
.replace("<font-size>", QString::number(int(12 * newDpi))));
|
||||
|
||||
this->ui->lblAvatar->setFixedSize((int)(100 * newDpi), (int)(100 * newDpi));
|
||||
this->ui->lblAvatar->setFixedSize(int(100 * newDpi), int(100 * newDpi));
|
||||
}
|
||||
|
||||
void AccountPopupWidget::updateButtons(QWidget *layout, bool state)
|
||||
@@ -302,7 +301,7 @@ void AccountPopupWidget::sendCommand(QPushButton *button, QString command)
|
||||
|
||||
void AccountPopupWidget::refreshLayouts()
|
||||
{
|
||||
auto currentTwitchUser = getApp()->accounts->Twitch.getCurrent();
|
||||
auto currentTwitchUser = getApp()->accounts->twitch.getCurrent();
|
||||
if (!currentTwitchUser) {
|
||||
// No twitch user set (should never happen)
|
||||
return;
|
||||
|
||||
@@ -19,9 +19,10 @@ class Channel;
|
||||
|
||||
namespace widgets {
|
||||
|
||||
class AccountPopupWidget : public BaseWindow
|
||||
class AccountPopupWidget final : public BaseWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AccountPopupWidget(ChannelPtr _channel);
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ AccountSwitchPopupWidget::AccountSwitchPopupWidget(QWidget *parent)
|
||||
});
|
||||
|
||||
this->setLayout(vbox);
|
||||
|
||||
// this->setStyleSheet("background: #333");
|
||||
}
|
||||
|
||||
void AccountSwitchPopupWidget::refresh()
|
||||
@@ -48,11 +50,12 @@ void AccountSwitchPopupWidget::focusOutEvent(QFocusEvent *)
|
||||
this->hide();
|
||||
}
|
||||
|
||||
void AccountSwitchPopupWidget::paintEvent(QPaintEvent *event)
|
||||
void AccountSwitchPopupWidget::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
painter.fillRect(this->rect(), QColor(255, 255, 255));
|
||||
painter.setPen(QColor("#999"));
|
||||
painter.drawRect(0, 0, this->width() - 1, this->height() - 1);
|
||||
}
|
||||
|
||||
} // namespace widgets
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "application.hpp"
|
||||
#include "const.hpp"
|
||||
#include "singletons/accountmanager.hpp"
|
||||
#include "controllers/accounts/accountcontroller.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace widgets {
|
||||
@@ -14,18 +14,18 @@ AccountSwitchWidget::AccountSwitchWidget(QWidget *parent)
|
||||
|
||||
this->addItem(ANONYMOUS_USERNAME_LABEL);
|
||||
|
||||
for (const auto &userName : app->accounts->Twitch.getUsernames()) {
|
||||
for (const auto &userName : app->accounts->twitch.getUsernames()) {
|
||||
this->addItem(userName);
|
||||
}
|
||||
|
||||
app->accounts->Twitch.userListUpdated.connect([=]() {
|
||||
app->accounts->twitch.userListUpdated.connect([=]() {
|
||||
this->blockSignals(true);
|
||||
|
||||
this->clear();
|
||||
|
||||
this->addItem(ANONYMOUS_USERNAME_LABEL);
|
||||
|
||||
for (const auto &userName : app->accounts->Twitch.getUsernames()) {
|
||||
for (const auto &userName : app->accounts->twitch.getUsernames()) {
|
||||
this->addItem(userName);
|
||||
}
|
||||
|
||||
@@ -40,9 +40,9 @@ AccountSwitchWidget::AccountSwitchWidget(QWidget *parent)
|
||||
if (!this->selectedItems().isEmpty()) {
|
||||
QString newUsername = this->currentItem()->text();
|
||||
if (newUsername.compare(ANONYMOUS_USERNAME_LABEL, Qt::CaseInsensitive) == 0) {
|
||||
app->accounts->Twitch.currentUsername = "";
|
||||
app->accounts->twitch.currentUsername = "";
|
||||
} else {
|
||||
app->accounts->Twitch.currentUsername = newUsername.toStdString();
|
||||
app->accounts->twitch.currentUsername = newUsername.toStdString();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -61,7 +61,7 @@ void AccountSwitchWidget::refreshSelection()
|
||||
if (this->count() > 0) {
|
||||
auto app = getApp();
|
||||
|
||||
auto currentUser = app->accounts->Twitch.getCurrent();
|
||||
auto currentUser = app->accounts->twitch.getCurrent();
|
||||
|
||||
if (currentUser->isAnon()) {
|
||||
this->setCurrentRow(0);
|
||||
|
||||
+121
-35
@@ -1,14 +1,16 @@
|
||||
#include "attachedwindow.hpp"
|
||||
|
||||
#include "application.hpp"
|
||||
#include "util/debugcount.hpp"
|
||||
#include "widgets/split.hpp"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "widgets/split.hpp"
|
||||
|
||||
#ifdef USEWINSDK
|
||||
#include "Windows.h"
|
||||
// don't even think about reordering these
|
||||
#include "Psapi.h"
|
||||
#pragma comment(lib, "Dwmapi.lib")
|
||||
#endif
|
||||
|
||||
@@ -17,17 +19,19 @@ namespace widgets {
|
||||
|
||||
AttachedWindow::AttachedWindow(void *_target, int _yOffset)
|
||||
: QWidget(nullptr, Qt::FramelessWindowHint | Qt::Window)
|
||||
, target(_target)
|
||||
, yOffset(_yOffset)
|
||||
, target_(_target)
|
||||
, yOffset_(_yOffset)
|
||||
{
|
||||
QLayout *layout = new QVBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
this->setLayout(layout);
|
||||
|
||||
auto *split = new Split(this);
|
||||
this->ui.split = split;
|
||||
this->ui_.split = split;
|
||||
split->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::MinimumExpanding);
|
||||
layout->addWidget(split);
|
||||
|
||||
util::DebugCount::increase("attached window");
|
||||
}
|
||||
|
||||
AttachedWindow::~AttachedWindow()
|
||||
@@ -38,18 +42,51 @@ AttachedWindow::~AttachedWindow()
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
util::DebugCount::decrease("attached window");
|
||||
}
|
||||
|
||||
AttachedWindow *AttachedWindow::get(void *target, const QString &winId, int yOffset)
|
||||
AttachedWindow *AttachedWindow::get(void *target, const GetArgs &args)
|
||||
{
|
||||
for (Item &item : items) {
|
||||
if (item.hwnd == target) {
|
||||
return item.window;
|
||||
AttachedWindow *window = [&]() {
|
||||
for (Item &item : items) {
|
||||
if (item.hwnd == target) {
|
||||
return item.window;
|
||||
}
|
||||
}
|
||||
|
||||
auto *window = new AttachedWindow(target, args.yOffset);
|
||||
items.push_back(Item{target, window, args.winId});
|
||||
return window;
|
||||
}();
|
||||
|
||||
bool show = true;
|
||||
QSize size = window->size();
|
||||
|
||||
if (args.height != -1) {
|
||||
if (args.height == 0) {
|
||||
window->hide();
|
||||
show = false;
|
||||
} else {
|
||||
window->height_ = args.height;
|
||||
size.setHeight(args.height);
|
||||
}
|
||||
}
|
||||
if (args.width != -1) {
|
||||
if (args.width == 0) {
|
||||
window->hide();
|
||||
show = false;
|
||||
} else {
|
||||
window->width_ = args.width;
|
||||
size.setWidth(args.width);
|
||||
}
|
||||
}
|
||||
|
||||
auto *window = new AttachedWindow(target, yOffset);
|
||||
items.push_back(Item{target, window, winId});
|
||||
if (show) {
|
||||
window->updateWindowRect_(window->target_);
|
||||
window->show();
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
@@ -64,43 +101,92 @@ void AttachedWindow::detach(const QString &winId)
|
||||
|
||||
void AttachedWindow::setChannel(ChannelPtr channel)
|
||||
{
|
||||
this->ui.split->setChannel(channel);
|
||||
this->ui_.split->setChannel(channel);
|
||||
}
|
||||
|
||||
void AttachedWindow::showEvent(QShowEvent *)
|
||||
{
|
||||
attachToHwnd(this->target);
|
||||
attachToHwnd_(this->target_);
|
||||
}
|
||||
|
||||
void AttachedWindow::attachToHwnd(void *_hwnd)
|
||||
void AttachedWindow::attachToHwnd_(void *_attachedPtr)
|
||||
{
|
||||
#ifdef USEWINSDK
|
||||
QTimer *timer = new QTimer(this);
|
||||
timer->setInterval(1);
|
||||
if (this->attached_) {
|
||||
return;
|
||||
}
|
||||
|
||||
HWND hwnd = (HWND)this->winId();
|
||||
HWND attached = (HWND)_hwnd;
|
||||
QObject::connect(timer, &QTimer::timeout, [this, hwnd, attached, timer] {
|
||||
::SetLastError(0);
|
||||
RECT xD;
|
||||
::GetWindowRect(attached, &xD);
|
||||
this->attached_ = true;
|
||||
this->timer_.setInterval(1);
|
||||
|
||||
if (::GetLastError() != 0) {
|
||||
timer->stop();
|
||||
timer->deleteLater();
|
||||
this->deleteLater();
|
||||
auto hwnd = HWND(this->winId());
|
||||
auto attached = HWND(_attachedPtr);
|
||||
|
||||
QObject::connect(&this->timer_, &QTimer::timeout, [this, hwnd, attached] {
|
||||
// check process id
|
||||
if (!this->validProcessName_) {
|
||||
DWORD processId;
|
||||
::GetWindowThreadProcessId(attached, &processId);
|
||||
|
||||
HANDLE process =
|
||||
::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, processId);
|
||||
|
||||
std::unique_ptr<TCHAR[]> filename(new TCHAR[512]);
|
||||
DWORD filenameLength = ::GetModuleFileNameEx(process, nullptr, filename.get(), 512);
|
||||
QString qfilename = QString::fromWCharArray(filename.get(), filenameLength);
|
||||
|
||||
if (!qfilename.endsWith("chrome.exe")) {
|
||||
qDebug() << "NM Illegal caller" << qfilename;
|
||||
this->timer_.stop();
|
||||
this->deleteLater();
|
||||
return;
|
||||
}
|
||||
this->validProcessName_ = true;
|
||||
}
|
||||
|
||||
HWND next = ::GetNextWindow(attached, GW_HWNDPREV);
|
||||
|
||||
::SetWindowPos(hwnd, next ? next : HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
|
||||
::MoveWindow(hwnd, xD.right - 360, xD.top + this->yOffset - 8, 360 - 8,
|
||||
xD.bottom - xD.top - this->yOffset, false);
|
||||
// ::MoveWindow(hwnd, xD.right - 360, xD.top + 82, 360 - 8, xD.bottom - xD.top - 82 -
|
||||
// 8,
|
||||
// false);
|
||||
this->updateWindowRect_(attached);
|
||||
});
|
||||
timer->start();
|
||||
this->timer_.start();
|
||||
#endif
|
||||
}
|
||||
|
||||
void AttachedWindow::updateWindowRect_(void *_attachedPtr)
|
||||
{
|
||||
#ifdef USEWINSDK
|
||||
auto hwnd = HWND(this->winId());
|
||||
auto attached = HWND(_attachedPtr);
|
||||
|
||||
// We get the window rect first so we can close this window when it returns an error.
|
||||
// If we query the process first and check the filename then it will return and empty string
|
||||
// that doens't match.
|
||||
::SetLastError(0);
|
||||
RECT rect;
|
||||
::GetWindowRect(attached, &rect);
|
||||
|
||||
if (::GetLastError() != 0) {
|
||||
qDebug() << "NM GetLastError()" << ::GetLastError();
|
||||
|
||||
this->timer_.stop();
|
||||
this->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// set the correct z-order
|
||||
HWND next = ::GetNextWindow(attached, GW_HWNDPREV);
|
||||
|
||||
::SetWindowPos(hwnd, next ? next : HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
|
||||
if (this->height_ == -1) {
|
||||
// ::MoveWindow(hwnd, rect.right - this->width_ - 8, rect.top + this->yOffset_ - 8,
|
||||
// this->width_, rect.bottom - rect.top - this->yOffset_, false);
|
||||
} else {
|
||||
::MoveWindow(hwnd, rect.right - this->width_ - 8, rect.bottom - this->height_ - 8,
|
||||
this->width_, this->height_, false);
|
||||
}
|
||||
|
||||
// ::MoveWindow(hwnd, rect.right - 360, rect.top + 82, 360 - 8, rect.bottom -
|
||||
// rect.top - 82 - 8, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,19 @@ namespace widgets {
|
||||
|
||||
class AttachedWindow : public QWidget
|
||||
{
|
||||
AttachedWindow(void *target, int asdf);
|
||||
AttachedWindow(void *target_, int asdf);
|
||||
|
||||
public:
|
||||
~AttachedWindow();
|
||||
struct GetArgs {
|
||||
QString winId;
|
||||
int yOffset = -1;
|
||||
int width = -1;
|
||||
int height = -1;
|
||||
};
|
||||
|
||||
static AttachedWindow *get(void *target, const QString &winId, int yOffset);
|
||||
virtual ~AttachedWindow() override;
|
||||
|
||||
static AttachedWindow *get(void *target_, const GetArgs &args);
|
||||
static void detach(const QString &winId);
|
||||
|
||||
void setChannel(ChannelPtr channel);
|
||||
@@ -26,14 +33,21 @@ protected:
|
||||
// override;
|
||||
|
||||
private:
|
||||
void *target;
|
||||
int yOffset;
|
||||
void *target_;
|
||||
int yOffset_;
|
||||
int currentYOffset_;
|
||||
int width_ = 360;
|
||||
int height_ = -1;
|
||||
bool validProcessName_ = false;
|
||||
bool attached_ = false;
|
||||
QTimer timer_;
|
||||
|
||||
struct {
|
||||
Split *split;
|
||||
} ui;
|
||||
} ui_;
|
||||
|
||||
void attachToHwnd(void *hwnd);
|
||||
void attachToHwnd_(void *attached);
|
||||
void updateWindowRect_(void *attached);
|
||||
|
||||
struct Item {
|
||||
void *hwnd;
|
||||
|
||||
@@ -27,7 +27,6 @@ BaseWidget::~BaseWidget()
|
||||
|
||||
float BaseWidget::getScale() const
|
||||
{
|
||||
// return 1.f;
|
||||
BaseWidget *baseWidget = dynamic_cast<BaseWidget *>(this->window());
|
||||
|
||||
if (baseWidget == nullptr) {
|
||||
|
||||
@@ -18,7 +18,7 @@ class BaseWidget : public QWidget
|
||||
|
||||
public:
|
||||
explicit BaseWidget(QWidget *parent, Qt::WindowFlags f = Qt::WindowFlags());
|
||||
virtual ~BaseWidget();
|
||||
virtual ~BaseWidget() override;
|
||||
|
||||
float getScale() const;
|
||||
pajlada::Signals::Signal<float> scaleChanged;
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
QSize getScaleIndependantSize() const;
|
||||
int getScaleIndependantWidth() const;
|
||||
int getScaleIndependantHeight() const;
|
||||
void setScaleIndependantSize(int width, int yOffset);
|
||||
void setScaleIndependantSize(int width, int height);
|
||||
void setScaleIndependantSize(QSize);
|
||||
void setScaleIndependantWidth(int value);
|
||||
void setScaleIndependantHeight(int value);
|
||||
|
||||
+117
-74
@@ -32,17 +32,28 @@
|
||||
namespace chatterino {
|
||||
namespace widgets {
|
||||
|
||||
BaseWindow::BaseWindow(QWidget *parent, bool _enableCustomFrame)
|
||||
: BaseWidget(parent, Qt::Window)
|
||||
, enableCustomFrame(_enableCustomFrame)
|
||||
BaseWindow::BaseWindow(QWidget *parent, Flags _flags)
|
||||
: BaseWidget(parent,
|
||||
Qt::Window | ((_flags & TopMost) ? Qt::WindowStaysOnTopHint : (Qt::WindowFlags)0))
|
||||
, enableCustomFrame(_flags & EnableCustomFrame)
|
||||
, frameless(_flags & FrameLess)
|
||||
, flags(_flags)
|
||||
{
|
||||
if (this->frameless) {
|
||||
this->enableCustomFrame = false;
|
||||
this->setWindowFlag(Qt::FramelessWindowHint);
|
||||
}
|
||||
|
||||
this->init();
|
||||
}
|
||||
|
||||
BaseWindow::Flags BaseWindow::getFlags()
|
||||
{
|
||||
return this->flags;
|
||||
}
|
||||
|
||||
void BaseWindow::init()
|
||||
{
|
||||
auto app = getApp();
|
||||
|
||||
this->setWindowIcon(QIcon(":/images/icon.png"));
|
||||
|
||||
#ifdef USEWINSDK
|
||||
@@ -53,55 +64,57 @@ void BaseWindow::init()
|
||||
layout->setSpacing(0);
|
||||
this->setLayout(layout);
|
||||
{
|
||||
QHBoxLayout *buttonLayout = this->ui.titlebarBox = new QHBoxLayout();
|
||||
buttonLayout->setMargin(0);
|
||||
layout->addLayout(buttonLayout);
|
||||
if (!this->frameless) {
|
||||
QHBoxLayout *buttonLayout = this->ui.titlebarBox = new QHBoxLayout();
|
||||
buttonLayout->setMargin(0);
|
||||
layout->addLayout(buttonLayout);
|
||||
|
||||
// title
|
||||
QLabel *title = new QLabel(" Chatterino");
|
||||
QObject::connect(this, &QWidget::windowTitleChanged,
|
||||
[title](const QString &text) { title->setText(" " + text); });
|
||||
// title
|
||||
QLabel *title = new QLabel(" Chatterino");
|
||||
QObject::connect(this, &QWidget::windowTitleChanged,
|
||||
[title](const QString &text) { title->setText(" " + text); });
|
||||
|
||||
QSizePolicy policy(QSizePolicy::Ignored, QSizePolicy::Preferred);
|
||||
policy.setHorizontalStretch(1);
|
||||
// title->setBaseSize(0, 0);
|
||||
title->setScaledContents(true);
|
||||
title->setSizePolicy(policy);
|
||||
buttonLayout->addWidget(title);
|
||||
this->ui.titleLabel = title;
|
||||
QSizePolicy policy(QSizePolicy::Ignored, QSizePolicy::Preferred);
|
||||
policy.setHorizontalStretch(1);
|
||||
// title->setBaseSize(0, 0);
|
||||
title->setScaledContents(true);
|
||||
title->setSizePolicy(policy);
|
||||
buttonLayout->addWidget(title);
|
||||
this->ui.titleLabel = title;
|
||||
|
||||
// buttons
|
||||
TitleBarButton *_minButton = new TitleBarButton;
|
||||
_minButton->setButtonStyle(TitleBarButton::Minimize);
|
||||
TitleBarButton *_maxButton = new TitleBarButton;
|
||||
_maxButton->setButtonStyle(TitleBarButton::Maximize);
|
||||
TitleBarButton *_exitButton = new TitleBarButton;
|
||||
_exitButton->setButtonStyle(TitleBarButton::Close);
|
||||
// buttons
|
||||
TitleBarButton *_minButton = new TitleBarButton;
|
||||
_minButton->setButtonStyle(TitleBarButton::Minimize);
|
||||
TitleBarButton *_maxButton = new TitleBarButton;
|
||||
_maxButton->setButtonStyle(TitleBarButton::Maximize);
|
||||
TitleBarButton *_exitButton = new TitleBarButton;
|
||||
_exitButton->setButtonStyle(TitleBarButton::Close);
|
||||
|
||||
QObject::connect(_minButton, &TitleBarButton::clicked, this, [this] {
|
||||
this->setWindowState(Qt::WindowMinimized | this->windowState());
|
||||
});
|
||||
QObject::connect(_maxButton, &TitleBarButton::clicked, this, [this] {
|
||||
this->setWindowState(this->windowState() == Qt::WindowMaximized
|
||||
? Qt::WindowActive
|
||||
: Qt::WindowMaximized);
|
||||
});
|
||||
QObject::connect(_exitButton, &TitleBarButton::clicked, this,
|
||||
[this] { this->close(); });
|
||||
QObject::connect(_minButton, &TitleBarButton::clicked, this, [this] {
|
||||
this->setWindowState(Qt::WindowMinimized | this->windowState());
|
||||
});
|
||||
QObject::connect(_maxButton, &TitleBarButton::clicked, this, [this] {
|
||||
this->setWindowState(this->windowState() == Qt::WindowMaximized
|
||||
? Qt::WindowActive
|
||||
: Qt::WindowMaximized);
|
||||
});
|
||||
QObject::connect(_exitButton, &TitleBarButton::clicked, this,
|
||||
[this] { this->close(); });
|
||||
|
||||
this->ui.minButton = _minButton;
|
||||
this->ui.maxButton = _maxButton;
|
||||
this->ui.exitButton = _exitButton;
|
||||
this->ui.minButton = _minButton;
|
||||
this->ui.maxButton = _maxButton;
|
||||
this->ui.exitButton = _exitButton;
|
||||
|
||||
this->ui.buttons.push_back(_minButton);
|
||||
this->ui.buttons.push_back(_maxButton);
|
||||
this->ui.buttons.push_back(_exitButton);
|
||||
this->ui.buttons.push_back(_minButton);
|
||||
this->ui.buttons.push_back(_maxButton);
|
||||
this->ui.buttons.push_back(_exitButton);
|
||||
|
||||
// buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(_minButton);
|
||||
buttonLayout->addWidget(_maxButton);
|
||||
buttonLayout->addWidget(_exitButton);
|
||||
buttonLayout->setSpacing(0);
|
||||
// buttonLayout->addStretch(1);
|
||||
buttonLayout->addWidget(_minButton);
|
||||
buttonLayout->addWidget(_maxButton);
|
||||
buttonLayout->addWidget(_exitButton);
|
||||
buttonLayout->setSpacing(0);
|
||||
}
|
||||
}
|
||||
this->ui.layoutBase = new BaseWidget(this);
|
||||
layout->addWidget(this->ui.layoutBase);
|
||||
@@ -115,9 +128,21 @@ void BaseWindow::init()
|
||||
}
|
||||
#endif
|
||||
|
||||
if (app->settings->windowTopMost.getValue()) {
|
||||
this->setWindowFlags(this->windowFlags() | Qt::WindowStaysOnTopHint);
|
||||
#ifdef USEWINSDK
|
||||
// fourtf: don't ask me why we need to delay this
|
||||
if (!(this->flags & Flags::TopMost)) {
|
||||
QTimer::singleShot(1, this, [this] {
|
||||
getApp()->settings->windowTopMost.connect([this](bool topMost, auto) {
|
||||
::SetWindowPos(HWND(this->winId()), topMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0,
|
||||
0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
});
|
||||
});
|
||||
}
|
||||
#else
|
||||
// if (getApp()->settings->windowTopMost.getValue()) {
|
||||
// this->setWindowFlag(Qt::WindowStaysOnTopHint);
|
||||
// }
|
||||
#endif
|
||||
}
|
||||
|
||||
void BaseWindow::setStayInScreenRect(bool value)
|
||||
@@ -158,10 +183,12 @@ void BaseWindow::themeRefreshEvent()
|
||||
palette.setColor(QPalette::Foreground, this->themeManager->window.text);
|
||||
this->setPalette(palette);
|
||||
|
||||
QPalette palette_title;
|
||||
palette_title.setColor(QPalette::Foreground,
|
||||
this->themeManager->isLightTheme() ? "#333" : "#ccc");
|
||||
this->ui.titleLabel->setPalette(palette_title);
|
||||
if (this->ui.titleLabel) {
|
||||
QPalette palette_title;
|
||||
palette_title.setColor(QPalette::Foreground,
|
||||
this->themeManager->isLightTheme() ? "#333" : "#ccc");
|
||||
this->ui.titleLabel->setPalette(palette_title);
|
||||
}
|
||||
|
||||
for (RippleEffectButton *button : this->ui.buttons) {
|
||||
button->setMouseEffectColor(this->themeManager->window.text);
|
||||
@@ -205,7 +232,7 @@ void BaseWindow::changeEvent(QEvent *)
|
||||
TooltipWidget::getInstance()->hide();
|
||||
|
||||
#ifdef USEWINSDK
|
||||
if (this->hasCustomWindowFrame()) {
|
||||
if (this->ui.maxButton) {
|
||||
this->ui.maxButton->setButtonStyle(this->windowState() & Qt::WindowMaximized
|
||||
? TitleBarButton::Unmaximize
|
||||
: TitleBarButton::Maximize);
|
||||
@@ -222,10 +249,12 @@ void BaseWindow::leaveEvent(QEvent *)
|
||||
TooltipWidget::getInstance()->hide();
|
||||
}
|
||||
|
||||
void BaseWindow::moveTo(QWidget *parent, QPoint point)
|
||||
void BaseWindow::moveTo(QWidget *parent, QPoint point, bool offset)
|
||||
{
|
||||
point.rx() += 16;
|
||||
point.ry() += 16;
|
||||
if (offset) {
|
||||
point.rx() += 16;
|
||||
point.ry() += 16;
|
||||
}
|
||||
|
||||
this->move(point);
|
||||
this->moveIntoDesktopRect(parent);
|
||||
@@ -295,10 +324,18 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, long *r
|
||||
NCCALCSIZE_PARAMS *ncp = (reinterpret_cast<NCCALCSIZE_PARAMS *>(msg->lParam));
|
||||
ncp->lppos->flags |= SWP_NOREDRAW;
|
||||
RECT *clientRect = &ncp->rgrc[0];
|
||||
clientRect->left += cx;
|
||||
clientRect->top += 0;
|
||||
clientRect->right -= cx;
|
||||
clientRect->bottom -= cy;
|
||||
|
||||
if (IsWindows10OrGreater()) {
|
||||
clientRect->left += cx;
|
||||
clientRect->top += 0;
|
||||
clientRect->right -= cx;
|
||||
clientRect->bottom -= cy;
|
||||
} else {
|
||||
clientRect->left += 1;
|
||||
clientRect->top += 0;
|
||||
clientRect->right -= 1;
|
||||
clientRect->bottom -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
*result = 0;
|
||||
@@ -306,14 +343,13 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, long *r
|
||||
} else {
|
||||
return QWidget::nativeEvent(eventType, message, result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
case WM_NCHITTEST: {
|
||||
if (this->hasCustomWindowFrame()) {
|
||||
*result = 0;
|
||||
const LONG border_width = 8; // in pixels
|
||||
RECT winrect;
|
||||
GetWindowRect((HWND)winId(), &winrect);
|
||||
GetWindowRect(HWND(winId()), &winrect);
|
||||
|
||||
long x = GET_X_LPARAM(msg->lParam);
|
||||
long y = GET_Y_LPARAM(msg->lParam);
|
||||
@@ -400,17 +436,18 @@ void BaseWindow::showEvent(QShowEvent *event)
|
||||
{
|
||||
if (!this->shown && this->isVisible() && this->hasCustomWindowFrame()) {
|
||||
this->shown = true;
|
||||
SetWindowLongPtr((HWND)this->winId(), GWL_STYLE,
|
||||
WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX);
|
||||
// SetWindowLongPtr((HWND)this->winId(), GWL_STYLE,
|
||||
// WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_MAXIMIZEBOX |
|
||||
// WS_MINIMIZEBOX);
|
||||
|
||||
const MARGINS shadow = {8, 8, 8, 8};
|
||||
DwmExtendFrameIntoClientArea((HWND)this->winId(), &shadow);
|
||||
DwmExtendFrameIntoClientArea(HWND(this->winId()), &shadow);
|
||||
}
|
||||
|
||||
BaseWidget::showEvent(event);
|
||||
}
|
||||
|
||||
void BaseWindow::paintEvent(QPaintEvent *event)
|
||||
void BaseWindow::paintEvent(QPaintEvent *)
|
||||
{
|
||||
if (this->hasCustomWindowFrame()) {
|
||||
QPainter painter(this);
|
||||
@@ -434,13 +471,19 @@ void BaseWindow::calcButtonsSizes()
|
||||
return;
|
||||
}
|
||||
if ((this->width() / this->getScale()) < 300) {
|
||||
this->ui.minButton->setScaleIndependantSize(30, 30);
|
||||
this->ui.maxButton->setScaleIndependantSize(30, 30);
|
||||
this->ui.exitButton->setScaleIndependantSize(30, 30);
|
||||
if (this->ui.minButton)
|
||||
this->ui.minButton->setScaleIndependantSize(30, 30);
|
||||
if (this->ui.maxButton)
|
||||
this->ui.maxButton->setScaleIndependantSize(30, 30);
|
||||
if (this->ui.exitButton)
|
||||
this->ui.exitButton->setScaleIndependantSize(30, 30);
|
||||
} else {
|
||||
this->ui.minButton->setScaleIndependantSize(46, 30);
|
||||
this->ui.maxButton->setScaleIndependantSize(46, 30);
|
||||
this->ui.exitButton->setScaleIndependantSize(46, 30);
|
||||
if (this->ui.minButton)
|
||||
this->ui.minButton->setScaleIndependantSize(46, 30);
|
||||
if (this->ui.maxButton)
|
||||
this->ui.maxButton->setScaleIndependantSize(46, 30);
|
||||
if (this->ui.exitButton)
|
||||
this->ui.exitButton->setScaleIndependantSize(46, 30);
|
||||
}
|
||||
}
|
||||
} // namespace widgets
|
||||
|
||||
@@ -19,7 +19,9 @@ class BaseWindow : public BaseWidget
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BaseWindow(QWidget *parent = nullptr, bool enableCustomFrame = false);
|
||||
enum Flags { None = 0, EnableCustomFrame = 1, FrameLess = 2, TopMost = 4 };
|
||||
|
||||
explicit BaseWindow(QWidget *parent = nullptr, Flags flags = None);
|
||||
|
||||
QWidget *getLayoutContainer();
|
||||
bool hasCustomWindowFrame();
|
||||
@@ -29,7 +31,9 @@ public:
|
||||
void setStayInScreenRect(bool value);
|
||||
bool getStayInScreenRect() const;
|
||||
|
||||
void moveTo(QWidget *widget, QPoint point);
|
||||
void moveTo(QWidget *widget, QPoint point, bool offset = true);
|
||||
|
||||
Flags getFlags();
|
||||
|
||||
protected:
|
||||
#ifdef USEWINSDK
|
||||
@@ -51,16 +55,18 @@ private:
|
||||
void calcButtonsSizes();
|
||||
|
||||
bool enableCustomFrame;
|
||||
bool frameless;
|
||||
bool stayInScreenRect = false;
|
||||
bool shown = false;
|
||||
Flags flags;
|
||||
|
||||
struct {
|
||||
QHBoxLayout *titlebarBox;
|
||||
QWidget *titleLabel;
|
||||
QHBoxLayout *titlebarBox = nullptr;
|
||||
QWidget *titleLabel = nullptr;
|
||||
TitleBarButton *minButton = nullptr;
|
||||
TitleBarButton *maxButton = nullptr;
|
||||
TitleBarButton *exitButton = nullptr;
|
||||
QWidget *layoutBase;
|
||||
QWidget *layoutBase = nullptr;
|
||||
std::vector<RippleEffectButton *> buttons;
|
||||
} ui;
|
||||
};
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#include "emotepopup.hpp"
|
||||
|
||||
#include "application.hpp"
|
||||
#include "controllers/accounts/accountcontroller.hpp"
|
||||
#include "messages/messagebuilder.hpp"
|
||||
#include "providers/twitch/twitchchannel.hpp"
|
||||
#include "singletons/accountmanager.hpp"
|
||||
#include "widgets/notebook.hpp"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QShortcut>
|
||||
#include <QTabWidget>
|
||||
|
||||
using namespace chatterino::providers::twitch;
|
||||
@@ -16,14 +17,14 @@ namespace chatterino {
|
||||
namespace widgets {
|
||||
|
||||
EmotePopup::EmotePopup()
|
||||
: BaseWindow(nullptr, true)
|
||||
: BaseWindow(nullptr, BaseWindow::EnableCustomFrame)
|
||||
{
|
||||
this->viewEmotes = new ChannelView();
|
||||
this->viewEmojis = new ChannelView();
|
||||
|
||||
this->viewEmotes->setOverrideFlags((MessageElement::Flags)(
|
||||
this->viewEmotes->setOverrideFlags(MessageElement::Flags(
|
||||
MessageElement::Default | MessageElement::AlwaysShow | MessageElement::EmoteImages));
|
||||
this->viewEmojis->setOverrideFlags((MessageElement::Flags)(
|
||||
this->viewEmojis->setOverrideFlags(MessageElement::Flags(
|
||||
MessageElement::Default | MessageElement::AlwaysShow | MessageElement::EmoteImages));
|
||||
|
||||
this->viewEmotes->setEnableScrollingToBottom(false);
|
||||
@@ -32,7 +33,7 @@ EmotePopup::EmotePopup()
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
this->getLayoutContainer()->setLayout(layout);
|
||||
|
||||
Notebook2 *notebook = new Notebook2(this);
|
||||
Notebook *notebook = new Notebook(this);
|
||||
layout->addWidget(notebook);
|
||||
layout->setMargin(0);
|
||||
|
||||
@@ -83,10 +84,36 @@ void EmotePopup::loadChannel(ChannelPtr _channel)
|
||||
|
||||
auto app = getApp();
|
||||
|
||||
QString userID = app->accounts->Twitch.getCurrent()->getUserId();
|
||||
QString userID = app->accounts->twitch.getCurrent()->getUserId();
|
||||
|
||||
// fourtf: the entire emote manager needs to be refactored so there's no point in trying to
|
||||
// fix this pile of garbage
|
||||
for (const auto &set : app->emotes->twitchAccountEmotes[userID.toStdString()].emoteSets) {
|
||||
// TITLE
|
||||
messages::MessageBuilder builder1;
|
||||
|
||||
builder1.append(new TextElement("Twitch Account Emotes", MessageElement::Text));
|
||||
|
||||
builder1.getMessage()->flags |= Message::Centered;
|
||||
emoteChannel->addMessage(builder1.getMessage());
|
||||
|
||||
// EMOTES
|
||||
messages::MessageBuilder builder2;
|
||||
builder2.getMessage()->flags |= Message::Centered;
|
||||
builder2.getMessage()->flags |= Message::DisableCompactEmotes;
|
||||
|
||||
for (const auto &emote : set.second) {
|
||||
[&](const QString &key, const util::EmoteData &value) {
|
||||
builder2.append((new EmoteElement(value, MessageElement::Flags::AlwaysShow))
|
||||
->setLink(Link(Link::InsertText, key)));
|
||||
}(QString::fromStdString(emote.code),
|
||||
app->emotes->getTwitchEmoteById(QString::fromStdString(emote.id).toLong(),
|
||||
QString::fromStdString(emote.code)));
|
||||
}
|
||||
|
||||
emoteChannel->addMessage(builder2.getMessage());
|
||||
}
|
||||
|
||||
addEmotes(app->emotes->twitchAccountEmotes[userID.toStdString()].emotes,
|
||||
"Twitch Account Emotes", "Twitch Account Emote");
|
||||
addEmotes(app->emotes->bttvGlobalEmotes, "BetterTTV Global Emotes", "BetterTTV Global Emote");
|
||||
addEmotes(*channel->bttvChannelEmotes.get(), "BetterTTV Channel Emotes",
|
||||
"BetterTTV Channel Emote");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user