refactoring

This commit is contained in:
fourtf 2017-04-12 17:46:44 +02:00
parent 8ef492d7ae
commit 96db82e867
114 changed files with 5554 additions and 3703 deletions

View file

@ -1,13 +0,0 @@
#include "account.h"
namespace chatterino {
Account Account::anon("justinfan123", "", "");
Account::Account(QString username, QString oauthToken, QString oauthClient)
{
this->oauthClient = oauthClient;
this->oauthToken = oauthToken;
this->username = username;
}
}

View file

@ -1,52 +0,0 @@
#ifndef ACCOUNT_H
#define ACCOUNT_H
#include <QString>
namespace chatterino {
class Account
{
public:
Account(QString username, QString oauthToken, QString oauthClient);
static const Account *
getAnon()
{
return &anon;
}
const QString &
getUsername() const
{
return username;
}
const QString &
getOauthToken() const
{
return oauthToken;
}
const QString &
getOauthClient() const
{
return oauthClient;
}
bool
isAnon() const
{
return username.startsWith("justinfan");
}
private:
static Account anon;
QString username;
QString oauthClient;
QString oauthToken;
};
}
#endif // ACCOUNT_H

View file

@ -7,19 +7,16 @@
#include <QThreadPool>
#include <functional>
#define async_exec(a) \
QThreadPool::globalInstance()->start(new LambdaRunnable(a));
#define async_exec(a) QThreadPool::globalInstance()->start(new LambdaRunnable(a));
class LambdaRunnable : public QRunnable
{
class LambdaRunnable : public QRunnable {
public:
LambdaRunnable(std::function<void()> action)
{
this->action = action;
}
void
run()
void run()
{
this->action();
}

View file

@ -1,8 +1,9 @@
#include "channel.h"
#include "emotes.h"
#include "emotemanager.h"
#include "logging/loggingmanager.h"
#include "messages/message.h"
#include "windows.h"
#include "util/urlfetch.h"
#include "windowmanager.h"
#include <QJsonArray>
#include <QJsonDocument>
@ -14,133 +15,177 @@
#include <memory>
using namespace chatterino::messages;
namespace chatterino {
Channel::Channel(const QString &channel)
: messages()
, name((channel.length() > 0 && channel[0] == '#') ? channel.mid(1)
: channel)
, bttvChannelEmotes()
, ffzChannelEmotes()
, subLink("https://www.twitch.tv/" + name +
"/subscribe?ref=in_chat_subscriber_link")
, channelLink("https://twitch.tv/" + name)
, popoutPlayerLink("https://player.twitch.tv/?channel=" + name)
, loggingChannel(logging::get(name))
: _messages()
, _name((channel.length() > 0 && channel[0] == '#') ? channel.mid(1) : channel)
, _bttvChannelEmotes()
, _ffzChannelEmotes()
, _subLink("https://www.twitch.tv/" + _name + "/subscribe?ref=in_chat_subscriber_link")
, _channelLink("https://twitch.tv/" + _name)
, _popoutPlayerLink("https://player.twitch.tv/?channel=" + _name)
//, _loggingChannel(logging::get(_name))
{
reloadChannelEmotes();
}
void
Channel::reloadBttvEmotes()
//
// properties
//
ConcurrentMap<QString, messages::LazyLoadedImage *> &Channel::getBttvChannelEmotes()
{
// bttv
QNetworkAccessManager *manager = new QNetworkAccessManager();
QUrl url("https://api.betterttv.net/2/channels/" + this->name);
QNetworkRequest request(url);
QNetworkReply *reply = manager->get(request);
QObject::connect(reply, &QNetworkReply::finished, [=] {
if (reply->error() == QNetworkReply::NetworkError::NoError) {
QByteArray data = reply->readAll();
QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
QJsonObject root = jsonDoc.object();
auto emotes = root.value("emotes").toArray();
QString _template = "https:" + root.value("urlTemplate").toString();
for (const QJsonValue &emote : emotes) {
QString id = emote.toObject().value("id").toString();
QString code = emote.toObject().value("code").toString();
// emote.value("imageType").toString();
QString tmp = _template;
tmp.detach();
QString url =
tmp.replace("{{id}}", id).replace("{{image}}", "1x");
this->getBttvChannelEmotes().insert(
code, Emotes::getBttvChannelEmoteFromCaches().getOrAdd(
id, [&code, &url] {
return new messages::LazyLoadedImage(
url, 1, code,
code + "\nChannel Bttv Emote");
}));
}
}
reply->deleteLater();
manager->deleteLater();
});
return _bttvChannelEmotes;
}
void
Channel::reloadFfzEmotes()
ConcurrentMap<QString, messages::LazyLoadedImage *> &Channel::getFfzChannelEmotes()
{
QNetworkAccessManager *manager = new QNetworkAccessManager();
QUrl url("http://api.frankerfacez.com/v1/room/" + this->name);
QNetworkRequest request(url);
QNetworkReply *reply = manager->get(request);
QObject::connect(reply, &QNetworkReply::finished, [=] {
if (reply->error() == QNetworkReply::NetworkError::NoError) {
QByteArray data = reply->readAll();
QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
QJsonObject root = jsonDoc.object();
auto sets = root.value("sets").toObject();
for (const QJsonValue &set : sets) {
auto emoticons = set.toObject().value("emoticons").toArray();
for (const QJsonValue &emote : emoticons) {
QJsonObject object = emote.toObject();
// margins
int id = object.value("id").toInt();
QString code = object.value("name").toString();
QJsonObject urls = object.value("urls").toObject();
QString url1 = "http:" + urls.value("1").toString();
this->getFfzChannelEmotes().insert(
code, Emotes::getFfzChannelEmoteFromCaches().getOrAdd(
id, [&code, &url1] {
return new messages::LazyLoadedImage(
url1, 1, code,
code + "\nGlobal Ffz Emote");
}));
}
}
}
reply->deleteLater();
manager->deleteLater();
});
return _ffzChannelEmotes;
}
void
Channel::addMessage(std::shared_ptr<messages::Message> message)
bool Channel::isEmpty() const
{
std::shared_ptr<messages::Message> deleted;
return _name.isEmpty();
}
if (this->loggingChannel.get() != nullptr) {
this->loggingChannel->append(message);
}
const QString &Channel::getName() const
{
return _name;
}
if (this->messages.appendItem(message, deleted)) {
this->messageRemovedFromStart(deleted);
int Channel::getRoomID() const
{
return _roomID;
}
const QString &Channel::getSubLink() const
{
return _subLink;
}
const QString &Channel::getChannelLink() const
{
return _channelLink;
}
const QString &Channel::getPopoutPlayerLink() const
{
return _popoutPlayerLink;
}
bool Channel::getIsLive() const
{
return _isLive;
}
int Channel::getStreamViewerCount() const
{
return _streamViewerCount;
}
const QString &Channel::getStreamStatus() const
{
return _streamStatus;
}
const QString &Channel::getStreamGame() const
{
return _streamGame;
}
messages::LimitedQueueSnapshot<messages::SharedMessage> Channel::getMessageSnapshot()
{
return _messages.getSnapshot();
}
//
// methods
//
void Channel::addMessage(std::shared_ptr<Message> message)
{
std::shared_ptr<Message> deleted;
// if (_loggingChannel.get() != nullptr) {
// _loggingChannel->append(message);
// }
if (_messages.appendItem(message, deleted)) {
messageRemovedFromStart(deleted);
}
this->messageAppended(message);
Windows::repaintVisibleChatWidgets(this);
WindowManager::repaintVisibleChatWidgets(this);
}
// private methods
void Channel::reloadChannelEmotes()
{
reloadBttvEmotes();
reloadFfzEmotes();
}
void Channel::reloadBttvEmotes()
{
util::urlJsonFetch(
"https://api.betterttv.net/2/channels/" + _name, [this](QJsonObject &rootNode) {
auto emotesNode = rootNode.value("emotes").toArray();
QString linkTemplate = "https:" + rootNode.value("urlTemplate").toString();
for (const QJsonValue &emoteNode : emotesNode) {
QJsonObject emoteObject = emoteNode.toObject();
QString id = emoteObject.value("id").toString();
QString code = emoteObject.value("code").toString();
// emoteObject.value("imageType").toString();
QString link = linkTemplate;
link.detach();
link = link.replace("{{id}}", id).replace("{{image}}", "1x");
auto emote = EmoteManager::getInstance().getBttvChannelEmoteFromCaches().getOrAdd(
id, [&code, &link] {
return new LazyLoadedImage(link, 1, code, code + "\nChannel Bttv Emote");
});
this->getBttvChannelEmotes().insert(code, emote);
}
});
}
void Channel::reloadFfzEmotes()
{
util::urlJsonFetch("http://api.frankerfacez.com/v1/room/" + _name, [this](
QJsonObject &rootNode) {
auto setsNode = rootNode.value("sets").toObject();
for (const QJsonValue &setNode : setsNode) {
auto emotesNode = setNode.toObject().value("emoticons").toArray();
for (const QJsonValue &emoteNode : emotesNode) {
QJsonObject emoteObject = emoteNode.toObject();
// margins
int id = emoteObject.value("id").toInt();
QString code = emoteObject.value("name").toString();
QJsonObject urls = emoteObject.value("urls").toObject();
QString url1 = "http:" + urls.value("1").toString();
auto emote = EmoteManager::getInstance().getFfzChannelEmoteFromCaches().getOrAdd(
id, [&code, &url1] {
return new LazyLoadedImage(url1, 1, code, code + "\nGlobal Ffz Emote");
});
getFfzChannelEmotes().insert(code, emote);
}
}
});
}
} // namespace chatterino

132
channel.h
View file

@ -18,119 +18,59 @@ namespace messages {
class Message;
}
class ChannelManager;
typedef std::shared_ptr<Channel> SharedChannel;
class Channel
{
public:
Channel(const QString &channel);
boost::signals2::signal<void(std::shared_ptr<messages::Message> &)>
messageRemovedFromStart;
boost::signals2::signal<void(std::shared_ptr<messages::Message> &)>
messageAppended;
boost::signals2::signal<void(messages::SharedMessage &)> messageRemovedFromStart;
boost::signals2::signal<void(messages::SharedMessage &)> messageAppended;
// properties
ConcurrentMap<QString, messages::LazyLoadedImage *> &
getBttvChannelEmotes()
{
return bttvChannelEmotes;
}
ConcurrentMap<QString, messages::LazyLoadedImage *> &getBttvChannelEmotes();
ConcurrentMap<QString, messages::LazyLoadedImage *> &getFfzChannelEmotes();
ConcurrentMap<QString, messages::LazyLoadedImage *> &
getFfzChannelEmotes()
{
return ffzChannelEmotes;
}
bool
isEmpty() const
{
return name.isEmpty();
}
const QString &
getName() const
{
return name;
}
int
getRoomID() const
{
return roomID;
}
const QString &
getSubLink() const
{
return subLink;
}
const QString &
getChannelLink() const
{
return channelLink;
}
const QString &
getPopoutPlayerLink() const
{
return popoutPlayerLink;
}
bool
getIsLive() const
{
return isLive;
}
int
getStreamViewerCount() const
{
return streamViewerCount;
}
const QString &
getStreamStatus() const
{
return streamStatus;
}
const QString &
getStreamGame() const
{
return streamGame;
}
bool isEmpty() const;
const QString &getName() const;
int getRoomID() const;
const QString &getSubLink() const;
const QString &getChannelLink() const;
const QString &getPopoutPlayerLink() const;
bool getIsLive() const;
int getStreamViewerCount() const;
const QString &getStreamStatus() const;
const QString &getStreamGame() const;
messages::LimitedQueueSnapshot<messages::SharedMessage> getMessageSnapshot();
// methods
messages::LimitedQueueSnapshot<std::shared_ptr<messages::Message>>
getMessageSnapshot()
{
return messages.getSnapshot();
}
void addMessage(std::shared_ptr<messages::Message> message);
void
reloadChannelEmotes()
{
this->reloadBttvEmotes();
this->reloadFfzEmotes();
}
void addMessage(messages::SharedMessage message);
void reloadChannelEmotes();
private:
messages::LimitedQueue<std::shared_ptr<messages::Message>> messages;
// variabeles
messages::LimitedQueue<messages::SharedMessage> _messages;
QString name;
int roomID;
QString _name;
int _roomID;
ConcurrentMap<QString, messages::LazyLoadedImage *> bttvChannelEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> ffzChannelEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> _bttvChannelEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> _ffzChannelEmotes;
QString subLink;
QString channelLink;
QString popoutPlayerLink;
QString _subLink;
QString _channelLink;
QString _popoutPlayerLink;
bool isLive;
int streamViewerCount;
QString streamStatus;
QString streamGame;
std::shared_ptr<logging::Channel> loggingChannel;
bool _isLive;
int _streamViewerCount;
QString _streamStatus;
QString _streamGame;
// std::shared_ptr<logging::Channel> _loggingChannel;
// methods
void reloadBttvEmotes();
void reloadFfzEmotes();
};

122
channelmanager.cpp Normal file
View file

@ -0,0 +1,122 @@
#include "channelmanager.h"
#include "ircmanager.h"
namespace chatterino {
ChannelManager ChannelManager::instance;
ChannelManager::ChannelManager()
: _channels()
, _channelsMutex()
, _whispers(new Channel(QString("/whispers")))
, _mentions(new Channel(QString("/mentions")))
, _empty(new Channel(QString("")))
{
}
SharedChannel ChannelManager::getWhispers()
{
return _whispers;
}
SharedChannel ChannelManager::getMentions()
{
return _mentions;
}
SharedChannel ChannelManager::getEmpty()
{
return _empty;
}
const std::vector<SharedChannel> ChannelManager::getItems()
{
QMutexLocker locker(&_channelsMutex);
std::vector<SharedChannel> items;
for (auto &item : _channels.values()) {
items.push_back(std::get<0>(item));
}
return items;
}
SharedChannel ChannelManager::addChannel(const QString &channel)
{
QMutexLocker locker(&_channelsMutex);
QString channelName = channel.toLower();
if (channel.length() > 1 && channel.at(0) == '/') {
return getChannel(channel);
}
auto it = _channels.find(channelName);
if (it == _channels.end()) {
auto channel = SharedChannel(new Channel(channelName));
_channels.insert(channelName, std::make_tuple(channel, 1));
IrcManager::getInstance().sendJoin(channelName);
return channel;
}
std::get<1>(it.value())++;
return std::get<0>(it.value());
}
std::shared_ptr<Channel> ChannelManager::getChannel(const QString &channel)
{
QMutexLocker locker(&_channelsMutex);
QString c = channel.toLower();
if (channel.length() > 1 && channel.at(0) == '/') {
if (c == "/whispers") {
return _whispers;
}
if (c == "/mentions") {
return _mentions;
}
return _empty;
}
auto a = _channels.find(c);
if (a == _channels.end()) {
return _empty;
}
return std::get<0>(a.value());
}
void ChannelManager::removeChannel(const QString &channel)
{
QMutexLocker locker(&_channelsMutex);
if (channel.length() > 1 && channel.at(0) == '/') {
return;
}
QString c = channel.toLower();
auto a = _channels.find(c);
if (a == _channels.end()) {
return;
}
std::get<1>(a.value())--;
if (std::get<1>(a.value()) == 0) {
IrcManager::getInstance().partChannel(c);
_channels.remove(c);
}
}
} // namespace chatterino

41
channelmanager.h Normal file
View file

@ -0,0 +1,41 @@
#ifndef CHANNELS_H
#define CHANNELS_H
#include "channel.h"
namespace chatterino {
class ChannelManager
{
public:
static ChannelManager &getInstance()
{
return instance;
}
SharedChannel getWhispers();
SharedChannel getMentions();
SharedChannel getEmpty();
const std::vector<SharedChannel> getItems();
SharedChannel addChannel(const QString &channel);
SharedChannel getChannel(const QString &channel);
void removeChannel(const QString &channel);
private:
static ChannelManager instance;
ChannelManager();
QMap<QString, std::tuple<SharedChannel, int>> _channels;
QMutex _channelsMutex;
SharedChannel _whispers;
SharedChannel _mentions;
SharedChannel _empty;
};
} // namespace chatterino
#endif // CHANNELS_H

View file

@ -1,133 +0,0 @@
#include "channels.h"
#include "ircmanager.h"
namespace chatterino {
static std::shared_ptr<Channel> whispers(nullptr);
static std::shared_ptr<Channel> mentions(nullptr);
static std::shared_ptr<Channel> empty(nullptr);
QMap<QString, std::tuple<std::shared_ptr<Channel>, int>> Channels::channels;
QMutex Channels::channelsMutex;
void
initChannels()
{
whispers.reset(new Channel(QString("/whispers")));
mentions.reset(new Channel(QString("/mentions")));
empty.reset(new Channel(QString("")));
}
std::shared_ptr<Channel>
Channels::getWhispers()
{
return whispers;
}
std::shared_ptr<Channel>
Channels::getMentions()
{
return mentions;
}
std::shared_ptr<Channel>
Channels::getEmpty()
{
return empty;
}
const std::vector<std::shared_ptr<Channel>>
Channels::getItems()
{
QMutexLocker locker(&Channels::channelsMutex);
std::vector<std::shared_ptr<Channel>> items;
for (auto &item : Channels::channels.values()) {
items.push_back(std::get<0>(item));
}
return items;
}
std::shared_ptr<Channel>
Channels::addChannel(const QString &channel)
{
QMutexLocker locker(&Channels::channelsMutex);
QString channelName = channel.toLower();
if (channel.length() > 1 && channel.at(0) == '/') {
return getChannel(channel);
}
auto it = Channels::channels.find(channelName);
if (it == Channels::channels.end()) {
auto channel = std::shared_ptr<Channel>(new Channel(channelName));
Channels::channels.insert(channelName, std::make_tuple(channel, 1));
IrcManager::sendJoin(channelName);
return channel;
}
std::get<1>(it.value())++;
return std::get<0>(it.value());
}
std::shared_ptr<Channel>
Channels::getChannel(const QString &channel)
{
QMutexLocker locker(&Channels::channelsMutex);
QString c = channel.toLower();
if (channel.length() > 1 && channel.at(0) == '/') {
if (c == "/whispers") {
return whispers;
}
if (c == "/mentions") {
return mentions;
}
return empty;
}
auto a = Channels::channels.find(c);
if (a == Channels::channels.end()) {
return empty;
}
return std::get<0>(a.value());
}
void
Channels::removeChannel(const QString &channel)
{
QMutexLocker locker(&Channels::channelsMutex);
if (channel.length() > 1 && channel.at(0) == '/') {
return;
}
QString c = channel.toLower();
auto a = Channels::channels.find(c);
if (a == Channels::channels.end()) {
return;
}
std::get<1>(a.value())--;
if (std::get<1>(a.value()) == 0) {
IrcManager::partChannel(c);
Channels::channels.remove(c);
}
}
} // namespace chatterino

View file

@ -1,34 +0,0 @@
#ifndef CHANNELS_H
#define CHANNELS_H
#include "channel.h"
namespace chatterino {
void initChannels();
class Channels
{
public:
static std::shared_ptr<Channel> getWhispers();
static std::shared_ptr<Channel> getMentions();
static std::shared_ptr<Channel> getEmpty();
const static std::vector<std::shared_ptr<Channel>> getItems();
static std::shared_ptr<Channel> addChannel(const QString &channel);
static std::shared_ptr<Channel> getChannel(const QString &channel);
static void removeChannel(const QString &channel);
private:
Channels()
{
}
static QMap<QString, std::tuple<std::shared_ptr<Channel>, int>> channels;
static QMutex channelsMutex;
};
} // namespace chatterino
#endif // CHANNELS_H

View file

@ -17,17 +17,14 @@ TARGET = chatterino
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
DEFINES += IRC_NAMESPACE=Communi
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += main.cpp\
account.cpp \
channel.cpp \
channels.cpp \
colorscheme.cpp \
emojis.cpp \
emotes.cpp \
fonts.cpp \
ircmanager.cpp \
messages/lazyloadedimage.cpp \
messages/link.cpp \
@ -35,7 +32,6 @@ SOURCES += main.cpp\
messages/word.cpp \
messages/wordpart.cpp \
resources.cpp \
settings.cpp \
widgets/chatwidget.cpp \
widgets/chatwidgetheader.cpp \
widgets/chatwidgetheaderbutton.cpp \
@ -52,21 +48,31 @@ SOURCES += main.cpp\
widgets/settingsdialog.cpp \
widgets/settingsdialogtab.cpp \
widgets/textinputdialog.cpp \
windows.cpp \
messages/messageref.cpp \
logging/loggingmanager.cpp \
logging/loggingchannel.cpp
logging/loggingchannel.cpp \
windowmanager.cpp \
channelmanager.cpp \
fontmanager.cpp \
settingsmanager.cpp \
emotemanager.cpp \
usermanager.cpp \
twitch/twitchuser.cpp \
messages/messagebuilder.cpp \
twitch/twitchmessagebuilder.cpp \
ircuser2.cpp \
twitch/twitchparsemessage.cpp \
widgets/fancybutton.cpp \
widgets/titlebar.cpp \
widgets/userpopupwidget.cpp
HEADERS += account.h \
HEADERS += \
asyncexec.h \
channel.h \
channels.h \
colorscheme.h \
common.h \
concurrentmap.h \
emojis.h \
emotes.h \
fonts.h \
ircmanager.h \
messages/lazyloadedimage.h \
messages/link.h \
@ -75,8 +81,7 @@ HEADERS += account.h \
messages/wordpart.h \
resources.h \
setting.h \
settings.h \
twitchemotevalue.h \
twitch/emotevalue.h \
widgets/chatwidget.h \
widgets/chatwidgetheader.h \
widgets/chatwidgetheaderbutton.h \
@ -94,14 +99,29 @@ HEADERS += account.h \
widgets/settingsdialogtab.h \
widgets/signallabel.h \
widgets/textinputdialog.h \
windows.h \
widgets/resizingtextedit.h \
settingssnapshot.h \
messages/limitedqueue.h \
messages/limitedqueuesnapshot.h \
messages/messageref.h \
logging/loggingmanager.h \
logging/loggingchannel.h
logging/loggingchannel.h \
channelmanager.h \
windowmanager.h \
settingsmanager.h \
fontmanager.h \
emotemanager.h \
util/urlfetch.h \
usermanager.h \
twitch/twitchuser.h \
messages/messageparseargs.h \
messages/messagebuilder.h \
twitch/twitchmessagebuilder.h \
ircuser2.h \
twitch/twitchparsemessage.h \
widgets/fancybutton.h \
widgets/titlebar.h \
widgets/userpopupwidget.h
PRECOMPILED_HEADER =
@ -112,9 +132,29 @@ DISTFILES +=
# Include boost
win32 {
INCLUDEPATH += C:\local\boost\
INCLUDEPATH += C:\local\boost\
}
# Optional dependency on windows sdk 7.1
win32:exists(C:\Program Files\Microsoft SDKs\Windows\v7.1\Include\Windows.h) {
LIBS += -L"C:\Program Files\Microsoft SDKs\Windows\v7.1\Lib" \
-ldwmapi \
-lgdi32
SOURCES += platform/borderless/qwinwidget.cpp \
platform/borderless/winnativewindow.cpp \
platform/borderless/widget.cpp
HEADERS += platform/borderless/qwinwidget.h \
platform/borderless/winnativewindow.h \
platform/borderless/widget.h
DEFINES += "USEWINSDK"
}
macx {
INCLUDEPATH += /usr/local/include
}
FORMS += \
forms/userpopup.ui

View file

@ -7,7 +7,7 @@ Language: Cpp
SpacesBeforeTrailingComments: 2
AccessModifierOffset: -1
AlignEscapedNewlinesLeft: true
AlwaysBreakAfterDefinitionReturnType: true
AlwaysBreakAfterDefinitionReturnType: false
AlwaysBreakBeforeMultilineStrings: false
BreakConstructorInitializersBeforeComma: true
# BreakBeforeBraces: Linux
@ -25,4 +25,5 @@ BraceWrapping: {
AfterControlStatement: 'false'
AfterFunction: 'true'
BeforeCatch: 'false'
}
}
ColumnLimit: 100

View file

@ -1,38 +1,36 @@
#define LOOKUP_COLOR_COUNT 360
#include "colorscheme.h"
#include "settings.h"
#include "windows.h"
#include "settingsmanager.h"
#include "windowmanager.h"
#include <QColor>
namespace chatterino {
void
ColorScheme::init()
void ColorScheme::init()
{
static bool initiated = false;
if (!initiated) {
initiated = true;
ColorScheme::getInstance().update();
Settings::getInstance().theme.valueChanged.connect(
SettingsManager::getInstance().theme.valueChanged.connect(
[](const QString &) { ColorScheme::getInstance().update(); });
Settings::getInstance().themeHue.valueChanged.connect(
SettingsManager::getInstance().themeHue.valueChanged.connect(
[](const float &) { ColorScheme::getInstance().update(); });
ColorScheme::getInstance().updated.connect(
[] { Windows::repaintVisibleChatWidgets(); });
[] { WindowManager::repaintVisibleChatWidgets(); });
}
}
void
ColorScheme::update()
void ColorScheme::update()
{
QString theme = Settings::getInstance().theme.get();
QString theme = SettingsManager::getInstance().theme.get();
theme = theme.toLower();
qreal hue = Settings::getInstance().themeHue.get();
qreal hue = SettingsManager::getInstance().themeHue.get();
if (theme == "light") {
setColors(hue, 0.8);
@ -47,33 +45,40 @@ ColorScheme::update()
// hue: theme color (0 - 1)
// multiplyer: 1 = white, 0.8 = light, -0.8 dark, -1 black
void
ColorScheme::setColors(float hue, float multiplyer)
void ColorScheme::setColors(float hue, float multiplyer)
{
IsLightTheme = multiplyer > 0;
bool hasDarkBorder = false;
SystemMessageColor = QColor(140, 127, 127);
auto isLightTheme = IsLightTheme;
auto getColor = [isLightTheme, multiplyer](qreal h, qreal s, qreal l,
qreal a = 1.0) {
auto getColor = [isLightTheme, multiplyer](qreal h, qreal s, qreal l, qreal a = 1.0) {
return QColor::fromHslF(h, s, (((l - 0.5) * multiplyer) + 0.5), a);
};
DropPreviewBackground = getColor(hue, 0.5, 0.5, 0.3);
DropPreviewBackground = getColor(hue, 0.5, 0.5, 0.6);
Text = TextCaret = IsLightTheme ? QColor(0, 0, 0) : QColor(255, 255, 255);
// tab
TabPanelBackground = QColor(255, 255, 255);
TabBackground = QColor(255, 255, 255);
TabHoverBackground = getColor(hue, 0, 0.05);
if (hasDarkBorder) {
// TabPanelBackground = getColor(hue, 0, 0.8);
// TabBackground = getColor(hue, 0, 0.8);
// TabHoverBackground = getColor(hue, 0, 0.8);
} else {
TabPanelBackground = QColor(255, 255, 255);
TabBackground = QColor(255, 255, 255);
TabHoverBackground = getColor(hue, 0, 0.05);
}
TabSelectedBackground = getColor(hue, 0.5, 0.5);
TabHighlightedBackground = getColor(hue, 0.5, 0.2);
TabNewMessageBackground =
QBrush(getColor(hue, 0.5, 0.2), Qt::DiagCrossPattern);
TabText = QColor(0, 0, 0);
TabNewMessageBackground = QBrush(getColor(hue, 0.5, 0.2), Qt::DiagCrossPattern);
if (hasDarkBorder)
// TabText = QColor(210, 210, 210);
// TabHoverText = QColor(210, 210, 210);
TabText = QColor(0, 0, 0);
TabHoverText = QColor(0, 0, 0);
TabSelectedText = QColor(255, 255, 255);
TabHighlightedText = QColor(0, 0, 0);
@ -103,17 +108,14 @@ ColorScheme::setColors(float hue, float multiplyer)
fillLookupTableValues(this->minLookupTable, 0.833, 1.000, 0.30, 0.33);
// stylesheet
InputStyleSheet =
"background:" + ChatInputBackground.name() + ";" +
"border:" + TabSelectedBackground.name() + ";" +
"color:" + Text.name() + ";" +
"selection-background-color:" + TabSelectedBackground.name();
InputStyleSheet = "background:" + ChatInputBackground.name() + ";" +
"border:" + TabSelectedBackground.name() + ";" + "color:" + Text.name() +
";" + "selection-background-color:" + TabSelectedBackground.name();
updated();
}
void ColorScheme::fillLookupTableValues(qreal (&array)[360], qreal from,
qreal to, qreal fromValue,
void ColorScheme::fillLookupTableValues(qreal (&array)[360], qreal from, qreal to, qreal fromValue,
qreal toValue)
{
qreal diff = toValue - fromValue;
@ -127,8 +129,7 @@ void ColorScheme::fillLookupTableValues(qreal (&array)[360], qreal from,
}
}
void
ColorScheme::normalizeColor(QColor &color)
void ColorScheme::normalizeColor(QColor &color)
{
// qreal l = color.lightnessF();
// qreal s = color.saturationF();

View file

@ -61,8 +61,7 @@ public:
const int HighlightColorCount = 3;
QColor HighlightColors[3];
static ColorScheme &
getInstance()
static ColorScheme &getInstance()
{
static ColorScheme instance;
@ -87,8 +86,8 @@ private:
qreal middleLookupTable[360] = {};
qreal minLookupTable[360] = {};
void fillLookupTableValues(qreal (&array)[360], qreal from, qreal to,
qreal fromValue, qreal toValue);
void fillLookupTableValues(qreal (&array)[360], qreal from, qreal to, qreal fromValue,
qreal toValue);
};
}

View file

@ -14,17 +14,16 @@ class ConcurrentMap
{
public:
ConcurrentMap()
: map()
: _map()
{
}
bool
tryGet(const TKey &name, TValue &value) const
bool tryGet(const TKey &name, TValue &value) const
{
QMutexLocker lock(&this->mutex);
QMutexLocker lock(&_mutex);
auto a = map.find(name);
if (a == map.end()) {
auto a = _map.find(name);
if (a == _map.end()) {
return false;
}
@ -33,40 +32,37 @@ public:
return true;
}
TValue
getOrAdd(const TKey &name, std::function<TValue()> addLambda)
TValue getOrAdd(const TKey &name, std::function<TValue()> addLambda)
{
QMutexLocker lock(&this->mutex);
QMutexLocker lock(&_mutex);
auto a = map.find(name);
if (a == map.end()) {
auto a = _map.find(name);
if (a == _map.end()) {
TValue value = addLambda();
map.insert(name, value);
_map.insert(name, value);
return value;
}
return a.value();
}
void
clear()
void clear()
{
QMutexLocker lock(&this->mutex);
QMutexLocker lock(&_mutex);
map.clear();
_map.clear();
}
void
insert(const TKey &name, const TValue &value)
void insert(const TKey &name, const TValue &value)
{
QMutexLocker lock(&this->mutex);
QMutexLocker lock(&_mutex);
map.insert(name, value);
_map.insert(name, value);
}
private:
mutable QMutex mutex;
QMap<TKey, TValue> map;
mutable QMutex _mutex;
QMap<TKey, TValue> _map;
};
} // namespace chatterino

View file

@ -1,5 +1,5 @@
#include "emojis.h"
#include "emotes.h"
#include "emotemanager.h"
#include <QFile>
#include <QStringBuilder>
@ -15,17 +15,14 @@ QMap<QChar, QMap<QString, QString>> Emojis::firstEmojiChars;
ConcurrentMap<QString, messages::LazyLoadedImage *> Emojis::imageCache;
QString
Emojis::replaceShortCodes(const QString &text)
QString Emojis::replaceShortCodes(const QString &text)
{
// TODO: Implement this xD
return text;
}
void
Emojis::parseEmojis(
std::vector<std::tuple<messages::LazyLoadedImage *, QString>> &vector,
const QString &text)
void Emojis::parseEmojis(std::vector<std::tuple<messages::LazyLoadedImage *, QString>> &vector,
const QString &text)
{
long lastSlice = 0;
@ -44,21 +41,14 @@ Emojis::parseEmojis(
emojiIter.value() + ".png";
if (i - lastSlice != 0) {
vector.push_back(
std::tuple<messages::LazyLoadedImage *,
QString>(
NULL, text.mid(lastSlice, i - lastSlice)));
vector.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
NULL, text.mid(lastSlice, i - lastSlice)));
}
vector.push_back(
std::tuple<messages::LazyLoadedImage *, QString>(
imageCache.getOrAdd(
url,
[&url] {
return new messages::LazyLoadedImage(
url, 0.35);
}),
QString()));
vector.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
imageCache.getOrAdd(
url, [&url] { return new messages::LazyLoadedImage(url, 0.35); }),
QString()));
i += j - 1;
@ -72,13 +62,12 @@ Emojis::parseEmojis(
}
if (lastSlice < text.length()) {
vector.push_back(std::tuple<messages::LazyLoadedImage *, QString>(
NULL, text.mid(lastSlice)));
vector.push_back(
std::tuple<messages::LazyLoadedImage *, QString>(NULL, text.mid(lastSlice)));
}
}
void
Emojis::loadEmojis()
void Emojis::loadEmojis()
{
QFile file(":/emojidata.txt");
file.open(QFile::ReadOnly);
@ -106,8 +95,7 @@ Emojis::loadEmojis()
emotes[i++] = QString(item).toUInt(nullptr, 16);
}
shortCodeToEmoji.insert(
a.at(0), Emojis::EmojiData{QString::fromUcs4(emotes, i), a.at(1)});
shortCodeToEmoji.insert(a.at(0), Emojis::EmojiData{QString::fromUcs4(emotes, i), a.at(1)});
}
for (auto const &emoji : shortCodeToEmoji.toStdMap()) {
@ -122,9 +110,8 @@ Emojis::loadEmojis()
continue;
}
firstEmojiChars.insert(
emoji.first.at(0),
QMap<QString, QString>{{emoji.second.value, emoji.second.code}});
firstEmojiChars.insert(emoji.first.at(0),
QMap<QString, QString>{{emoji.second.value, emoji.second.code}});
}
}
}

View file

@ -14,9 +14,8 @@ namespace chatterino {
class Emojis
{
public:
static void parseEmojis(
std::vector<std::tuple<messages::LazyLoadedImage *, QString>> &vector,
const QString &text);
static void parseEmojis(std::vector<std::tuple<messages::LazyLoadedImage *, QString>> &vector,
const QString &text);
static void loadEmojis();

View file

@ -1,4 +1,4 @@
#include "emotes.h"
#include "emotemanager.h"
#include "resources.h"
#include <QDebug>
@ -11,41 +11,77 @@
#include <QNetworkRequest>
#include <memory>
#define TWITCH_EMOTE_TEMPLATE "https://static-cdn.jtvnw.net/emoticons/v1/{id}/{scale}.0"
using namespace chatterino::messages;
namespace chatterino {
QString Emotes::twitchEmoteTemplate(
"https://static-cdn.jtvnw.net/emoticons/v1/{id}/{scale}.0");
EmoteManager EmoteManager::instance;
ConcurrentMap<QString, TwitchEmoteValue *> Emotes::twitchEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> Emotes::bttvEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> Emotes::ffzEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> Emotes::chatterinoEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *>
Emotes::bttvChannelEmoteFromCaches;
ConcurrentMap<int, messages::LazyLoadedImage *>
Emotes::ffzChannelEmoteFromCaches;
ConcurrentMap<long, messages::LazyLoadedImage *> Emotes::twitchEmoteFromCache;
ConcurrentMap<QString, messages::LazyLoadedImage *> Emotes::miscImageFromCache;
boost::signals2::signal<void()> Emotes::gifUpdateTimerSignal;
QTimer Emotes::gifUpdateTimer;
bool Emotes::gifUpdateTimerInitiated(false);
int Emotes::generation = 0;
Emotes::Emotes()
EmoteManager::EmoteManager()
: _twitchEmotes()
, _bttvEmotes()
, _ffzEmotes()
, _chatterinoEmotes()
, _bttvChannelEmoteFromCaches()
, _ffzChannelEmoteFromCaches()
, _twitchEmoteFromCache()
, _miscImageFromCache()
, _gifUpdateTimerSignal()
, _gifUpdateTimer()
, _gifUpdateTimerInitiated(false)
, _generation(0)
{
}
void
Emotes::loadGlobalEmotes()
ConcurrentMap<QString, twitch::EmoteValue *> &EmoteManager::getTwitchEmotes()
{
return _twitchEmotes;
}
ConcurrentMap<QString, messages::LazyLoadedImage *> &EmoteManager::getBttvEmotes()
{
return _bttvEmotes;
}
ConcurrentMap<QString, messages::LazyLoadedImage *> &EmoteManager::getFfzEmotes()
{
return _ffzEmotes;
}
ConcurrentMap<QString, messages::LazyLoadedImage *> &EmoteManager::getChatterinoEmotes()
{
return _chatterinoEmotes;
}
ConcurrentMap<QString, messages::LazyLoadedImage *> &EmoteManager::getBttvChannelEmoteFromCaches()
{
return _bttvChannelEmoteFromCaches;
}
ConcurrentMap<int, messages::LazyLoadedImage *> &EmoteManager::getFfzChannelEmoteFromCaches()
{
return _ffzChannelEmoteFromCaches;
}
ConcurrentMap<long, messages::LazyLoadedImage *> &EmoteManager::getTwitchEmoteFromCache()
{
return _twitchEmoteFromCache;
}
ConcurrentMap<QString, messages::LazyLoadedImage *> &EmoteManager::getMiscImageFromCache()
{
return _miscImageFromCache;
}
void EmoteManager::loadGlobalEmotes()
{
loadBttvEmotes();
loadFfzEmotes();
}
void
Emotes::loadBttvEmotes()
void EmoteManager::loadBttvEmotes()
{
// bttv
QNetworkAccessManager *manager = new QNetworkAccessManager();
@ -63,21 +99,19 @@ Emotes::loadBttvEmotes()
auto emotes = root.value("emotes").toArray();
QString _template = "https:" + root.value("urlTemplate").toString();
QString linkTemplate = "https:" + root.value("urlTemplate").toString();
for (const QJsonValue &emote : emotes) {
QString id = emote.toObject().value("id").toString();
QString code = emote.toObject().value("code").toString();
// emote.value("imageType").toString();
QString tmp = _template;
QString tmp = linkTemplate;
tmp.detach();
QString url =
tmp.replace("{{id}}", id).replace("{{image}}", "1x");
QString url = tmp.replace("{{id}}", id).replace("{{image}}", "1x");
Emotes::getBttvEmotes().insert(
code, new messages::LazyLoadedImage(
url, 1, code, code + "\nGlobal Bttv Emote"));
EmoteManager::getBttvEmotes().insert(
code, new LazyLoadedImage(url, 1, code, code + "\nGlobal Bttv Emote"));
}
}
@ -86,8 +120,7 @@ Emotes::loadBttvEmotes()
});
}
void
Emotes::loadFfzEmotes()
void EmoteManager::loadFfzEmotes()
{
// ffz
QNetworkAccessManager *manager = new QNetworkAccessManager();
@ -113,15 +146,14 @@ Emotes::loadFfzEmotes()
// margins
int id = object.value("id").toInt();
// int id = object.value("id").toInt();
QString code = object.value("name").toString();
QJsonObject urls = object.value("urls").toObject();
QString url1 = "http:" + urls.value("1").toString();
Emotes::getBttvEmotes().insert(
code, new messages::LazyLoadedImage(
url1, 1, code, code + "\nGlobal Ffz Emote"));
EmoteManager::getBttvEmotes().insert(
code, new LazyLoadedImage(url1, 1, code, code + "\nGlobal Ffz Emote"));
}
}
}
@ -131,41 +163,34 @@ Emotes::loadFfzEmotes()
});
}
messages::LazyLoadedImage *
Emotes::getTwitchEmoteById(const QString &name, long id)
LazyLoadedImage *EmoteManager::getTwitchEmoteById(const QString &name, long id)
{
qDebug() << "loading twitch emote: " << id;
return Emotes::twitchEmoteFromCache.getOrAdd(id, [&name, &id] {
qDebug() << "loading twitch emote: " << id;
return EmoteManager::_twitchEmoteFromCache.getOrAdd(id, [&name, &id] {
qDebug() << "added twitch emote: " << id;
qreal scale;
QString url = getTwitchEmoteLink(id, scale);
return new messages::LazyLoadedImage(url, scale, name,
name + "\nTwitch Emote");
return new LazyLoadedImage(url, scale, name, name + "\nTwitch Emote");
});
}
QString
Emotes::getTwitchEmoteLink(long id, qreal &scale)
QString EmoteManager::getTwitchEmoteLink(long id, qreal &scale)
{
scale = .5;
QString value = Emotes::twitchEmoteTemplate;
QString value = TWITCH_EMOTE_TEMPLATE;
value.detach();
return value.replace("{id}", QString::number(id)).replace("{scale}", "2");
}
messages::LazyLoadedImage *
Emotes::getCheerImage(long long amount, bool animated)
LazyLoadedImage *EmoteManager::getCheerImage(long long amount, bool animated)
{
// TODO: fix this xD
return getCheerBadge(amount);
}
messages::LazyLoadedImage *
Emotes::getCheerBadge(long long amount)
LazyLoadedImage *EmoteManager::getCheerBadge(long long amount)
{
if (amount >= 100000) {
return Resources::getCheerBadge100000();

98
emotemanager.h Normal file
View file

@ -0,0 +1,98 @@
#ifndef EMOTES_H
#define EMOTES_H
#define GIF_FRAME_LENGTH 33
#include "concurrentmap.h"
#include "messages/lazyloadedimage.h"
#include "twitch/emotevalue.h"
#include "windowmanager.h"
#include <QMap>
#include <QMutex>
#include <QTimer>
#include <boost/signals2.hpp>
namespace chatterino {
class EmoteManager
{
public:
static EmoteManager &getInstance()
{
return instance;
}
ConcurrentMap<QString, twitch::EmoteValue *> &getTwitchEmotes();
ConcurrentMap<QString, messages::LazyLoadedImage *> &getBttvEmotes();
ConcurrentMap<QString, messages::LazyLoadedImage *> &getFfzEmotes();
ConcurrentMap<QString, messages::LazyLoadedImage *> &getChatterinoEmotes();
ConcurrentMap<QString, messages::LazyLoadedImage *> &getBttvChannelEmoteFromCaches();
ConcurrentMap<int, messages::LazyLoadedImage *> &getFfzChannelEmoteFromCaches();
ConcurrentMap<long, messages::LazyLoadedImage *> &getTwitchEmoteFromCache();
ConcurrentMap<QString, messages::LazyLoadedImage *> &getMiscImageFromCache();
void loadGlobalEmotes();
messages::LazyLoadedImage *getCheerImage(long long int amount, bool animated);
messages::LazyLoadedImage *getCheerBadge(long long int amount);
messages::LazyLoadedImage *getTwitchEmoteById(const QString &name, long int id);
int getGeneration()
{
return _generation;
}
void incGeneration()
{
_generation++;
}
boost::signals2::signal<void()> &getGifUpdateSignal()
{
if (!_gifUpdateTimerInitiated) {
_gifUpdateTimerInitiated = true;
_gifUpdateTimer.setInterval(30);
_gifUpdateTimer.start();
QObject::connect(&_gifUpdateTimer, &QTimer::timeout, [this] {
_gifUpdateTimerSignal();
WindowManager::repaintGifEmotes();
});
}
return _gifUpdateTimerSignal;
}
private:
static EmoteManager instance;
EmoteManager();
// variables
ConcurrentMap<QString, twitch::EmoteValue *> _twitchEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> _bttvEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> _ffzEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> _chatterinoEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> _bttvChannelEmoteFromCaches;
ConcurrentMap<int, messages::LazyLoadedImage *> _ffzChannelEmoteFromCaches;
ConcurrentMap<long, messages::LazyLoadedImage *> _twitchEmoteFromCache;
ConcurrentMap<QString, messages::LazyLoadedImage *> _miscImageFromCache;
QTimer _gifUpdateTimer;
bool _gifUpdateTimerInitiated;
int _generation;
boost::signals2::signal<void()> _gifUpdateTimerSignal;
// methods
static QString getTwitchEmoteLink(long id, qreal &scale);
void loadFfzEmotes();
void loadBttvEmotes();
};
}
#endif // EMOTES_H

140
emotes.h
View file

@ -1,140 +0,0 @@
#ifndef EMOTES_H
#define EMOTES_H
#define GIF_FRAME_LENGTH 33
#include "concurrentmap.h"
#include "messages/lazyloadedimage.h"
#include "twitchemotevalue.h"
#include "windows.h"
#include <QMap>
#include <QMutex>
#include <QTimer>
#include <boost/signals2.hpp>
namespace chatterino {
class Emotes
{
public:
static ConcurrentMap<QString, TwitchEmoteValue *> &
getTwitchEmotes()
{
return twitchEmotes;
}
static ConcurrentMap<QString, messages::LazyLoadedImage *> &
getBttvEmotes()
{
return bttvEmotes;
}
static ConcurrentMap<QString, messages::LazyLoadedImage *> &
getFfzEmotes()
{
return ffzEmotes;
}
static ConcurrentMap<QString, messages::LazyLoadedImage *> &
getChatterinoEmotes()
{
return chatterinoEmotes;
}
static ConcurrentMap<QString, messages::LazyLoadedImage *> &
getBttvChannelEmoteFromCaches()
{
return bttvChannelEmoteFromCaches;
}
static ConcurrentMap<int, messages::LazyLoadedImage *> &
getFfzChannelEmoteFromCaches()
{
return ffzChannelEmoteFromCaches;
}
static ConcurrentMap<long, messages::LazyLoadedImage *> &
getTwitchEmoteFromCache()
{
return twitchEmoteFromCache;
}
static ConcurrentMap<QString, messages::LazyLoadedImage *> &
getMiscImageFromCache()
{
return miscImageFromCache;
}
static void loadGlobalEmotes();
static messages::LazyLoadedImage *getCheerImage(long long int amount,
bool animated);
static messages::LazyLoadedImage *getCheerBadge(long long int amount);
static messages::LazyLoadedImage *getTwitchEmoteById(const QString &name,
long int id);
static int
getGeneration()
{
return generation;
}
static void
incGeneration()
{
generation++;
}
static boost::signals2::signal<void()> &
getGifUpdateSignal()
{
if (!gifUpdateTimerInitiated) {
gifUpdateTimerInitiated = true;
gifUpdateTimer.setInterval(30);
gifUpdateTimer.start();
QObject::connect(&gifUpdateTimer, &QTimer::timeout, [] {
gifUpdateTimerSignal();
Windows::repaintGifEmotes();
});
}
return gifUpdateTimerSignal;
}
private:
Emotes();
static QString twitchEmoteTemplate;
static ConcurrentMap<QString, TwitchEmoteValue *> twitchEmotes;
static ConcurrentMap<QString, messages::LazyLoadedImage *> bttvEmotes;
static ConcurrentMap<QString, messages::LazyLoadedImage *> ffzEmotes;
static ConcurrentMap<QString, messages::LazyLoadedImage *> chatterinoEmotes;
static ConcurrentMap<QString, messages::LazyLoadedImage *>
bttvChannelEmoteFromCaches;
static ConcurrentMap<int, messages::LazyLoadedImage *>
ffzChannelEmoteFromCaches;
static ConcurrentMap<long, messages::LazyLoadedImage *>
twitchEmoteFromCache;
static ConcurrentMap<QString, messages::LazyLoadedImage *>
miscImageFromCache;
static QString getTwitchEmoteLink(long id, qreal &scale);
static QTimer gifUpdateTimer;
static bool gifUpdateTimerInitiated;
static void loadFfzEmotes();
static void loadBttvEmotes();
static int generation;
static boost::signals2::signal<void()> gifUpdateTimerSignal;
};
}
#endif // EMOTES_H

62
fontmanager.cpp Normal file
View file

@ -0,0 +1,62 @@
#include "fontmanager.h"
#define DEFAULT_FONT "Arial"
namespace chatterino {
FontManager FontManager::instance;
FontManager::FontManager()
: _generation(0)
{
_medium = new QFont(DEFAULT_FONT, 14);
_mediumBold = new QFont(DEFAULT_FONT, 14);
_mediumItalic = new QFont(DEFAULT_FONT, 14);
_small = new QFont(DEFAULT_FONT, 10);
_large = new QFont(DEFAULT_FONT, 16);
_veryLarge = new QFont(DEFAULT_FONT, 18);
_metricsMedium = new QFontMetrics(*_medium);
_metricsMediumBold = new QFontMetrics(*_mediumBold);
_metricsMediumItalic = new QFontMetrics(*_mediumItalic);
_metricsSmall = new QFontMetrics(*_small);
_metricsLarge = new QFontMetrics(*_large);
_metricsVeryLarge = new QFontMetrics(*_veryLarge);
}
QFont &FontManager::getFont(Type type)
{
if (type == Medium)
return *_medium;
if (type == MediumBold)
return *_mediumBold;
if (type == MediumItalic)
return *_mediumItalic;
if (type == Small)
return *_small;
if (type == Large)
return *_large;
if (type == VeryLarge)
return *_veryLarge;
return *_medium;
}
QFontMetrics &FontManager::getFontMetrics(Type type)
{
if (type == Medium)
return *_metricsMedium;
if (type == MediumBold)
return *_metricsMediumBold;
if (type == MediumItalic)
return *_metricsMediumItalic;
if (type == Small)
return *_metricsSmall;
if (type == Large)
return *_metricsLarge;
if (type == VeryLarge)
return *_metricsVeryLarge;
return *_metricsMedium;
}
}

55
fontmanager.h Normal file
View file

@ -0,0 +1,55 @@
#ifndef FONTS_H
#define FONTS_H
#include <QFont>
#include <QFontMetrics>
namespace chatterino {
class FontManager
{
public:
enum Type : char { Medium, MediumBold, MediumItalic, Small, Large, VeryLarge };
static FontManager &getInstance()
{
return instance;
}
QFont &getFont(Type type);
QFontMetrics &getFontMetrics(Type type);
int getGeneration()
{
return _generation;
}
void incGeneration()
{
_generation++;
}
private:
static FontManager instance;
FontManager();
QFont *_medium;
QFont *_mediumBold;
QFont *_mediumItalic;
QFont *_small;
QFont *_large;
QFont *_veryLarge;
QFontMetrics *_metricsMedium;
QFontMetrics *_metricsMediumBold;
QFontMetrics *_metricsMediumItalic;
QFontMetrics *_metricsSmall;
QFontMetrics *_metricsLarge;
QFontMetrics *_metricsVeryLarge;
int _generation;
};
}
#endif // FONTS_H

View file

@ -1,64 +0,0 @@
#include "fonts.h"
#define DEFAULT_FONT "Arial"
namespace chatterino {
QFont *Fonts::medium = new QFont(DEFAULT_FONT, 14);
QFont *Fonts::mediumBold = new QFont(DEFAULT_FONT, 14);
QFont *Fonts::mediumItalic = new QFont(DEFAULT_FONT, 14);
QFont *Fonts::small = new QFont(DEFAULT_FONT, 10);
QFont *Fonts::large = new QFont(DEFAULT_FONT, 16);
QFont *Fonts::veryLarge = new QFont(DEFAULT_FONT, 18);
QFontMetrics *Fonts::metricsMedium = new QFontMetrics(*medium);
QFontMetrics *Fonts::metricsMediumBold = new QFontMetrics(*mediumBold);
QFontMetrics *Fonts::metricsMediumItalic = new QFontMetrics(*mediumItalic);
QFontMetrics *Fonts::metricsSmall = new QFontMetrics(*small);
QFontMetrics *Fonts::metricsLarge = new QFontMetrics(*large);
QFontMetrics *Fonts::metricsVeryLarge = new QFontMetrics(*veryLarge);
int Fonts::generation = 0;
Fonts::Fonts()
{
}
QFont &
Fonts::getFont(Type type)
{
if (type == Medium)
return *medium;
if (type == MediumBold)
return *mediumBold;
if (type == MediumItalic)
return *mediumItalic;
if (type == Small)
return *small;
if (type == Large)
return *large;
if (type == VeryLarge)
return *veryLarge;
return *medium;
}
QFontMetrics &
Fonts::getFontMetrics(Type type)
{
if (type == Medium)
return *metricsMedium;
if (type == MediumBold)
return *metricsMediumBold;
if (type == MediumItalic)
return *metricsMediumItalic;
if (type == Small)
return *metricsSmall;
if (type == Large)
return *metricsLarge;
if (type == VeryLarge)
return *metricsVeryLarge;
return *metricsMedium;
}
}

57
fonts.h
View file

@ -1,57 +0,0 @@
#ifndef FONTS_H
#define FONTS_H
#include <QFont>
#include <QFontMetrics>
namespace chatterino {
class Fonts
{
public:
enum Type : char {
Medium,
MediumBold,
MediumItalic,
Small,
Large,
VeryLarge
};
static QFont &getFont(Type type);
static QFontMetrics &getFontMetrics(Type type);
static int
getGeneration()
{
return generation;
}
static void
incGeneration()
{
generation++;
}
private:
Fonts();
static QFont *medium;
static QFont *mediumBold;
static QFont *mediumItalic;
static QFont *small;
static QFont *large;
static QFont *veryLarge;
static QFontMetrics *metricsMedium;
static QFontMetrics *metricsMediumBold;
static QFontMetrics *metricsMediumItalic;
static QFontMetrics *metricsSmall;
static QFontMetrics *metricsLarge;
static QFontMetrics *metricsVeryLarge;
static int generation;
};
}
#endif // FONTS_H

124
forms/userpopup.ui Normal file
View file

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UserPopup</class>
<widget class="QWidget" name="UserPopup">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>131</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>UserPopup</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QPushButton" name="btnPurge">
<property name="text">
<string>Purge</string>
</property>
</widget>
</item>
<item row="1" column="1" rowspan="2" colspan="2">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Views</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lblViews">
<property name="text">
<string>420</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Followers</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="lblFollowers">
<property name="text">
<string>69</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Account Age</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="lblAccountAge">
<property name="text">
<string>6 years</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="lblUsername">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>USERNAME</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnClose">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0" rowspan="3">
<widget class="QLabel" name="lblAvatar">
<property name="text">
<string>AVATAR</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -1,73 +1,89 @@
#include "ircmanager.h"
#include "asyncexec.h"
#include "channel.h"
#include "channels.h"
#include "channelmanager.h"
#include "messages/messageparseargs.h"
#include "twitch/twitchmessagebuilder.h"
#include "twitch/twitchparsemessage.h"
#include "twitch/twitchuser.h"
#include "usermanager.h"
#include <irccommand.h>
#include <ircconnection.h>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <future>
using namespace chatterino::messages;
namespace chatterino {
Account *IrcManager::account = nullptr;
std::shared_ptr<IrcConnection> IrcManager::connection;
QMutex IrcManager::connectionMutex;
long IrcManager::connectionGeneration = 0;
const QString IrcManager::defaultClientId = "7ue61iz46fz11y3cugd0l3tawb4taal";
QNetworkAccessManager IrcManager::accessManager;
IrcManager IrcManager::instance;
QMap<QString, bool> IrcManager::twitchBlockedUsers;
QMutex IrcManager::twitchBlockedUsersMutex;
const QString IrcManager::defaultClientId("7ue61iz46fz11y3cugd0l3tawb4taal");
IrcManager::IrcManager()
: _account(AccountManager::getInstance().getAnon())
, _connection()
, _connectionMutex()
, _connectionGeneration(0)
, _twitchBlockedUsers()
, _twitchBlockedUsersMutex()
, _accessManager()
{
}
void
IrcManager::connect()
const twitch::TwitchUser &IrcManager::getUser() const
{
return _account;
}
void IrcManager::setUser(const twitch::TwitchUser &account)
{
_account = account;
}
void IrcManager::connect()
{
disconnect();
async_exec([] { beginConnecting(); });
async_exec([this] { beginConnecting(); });
}
void
IrcManager::beginConnecting()
void IrcManager::beginConnecting()
{
IrcManager::account = const_cast<Account *>(Account::getAnon());
int generation = ++IrcManager::_connectionGeneration;
int generation = ++IrcManager::connectionGeneration;
Communi::IrcConnection *c = new Communi::IrcConnection;
auto c = new IrcConnection();
QObject::connect(c, &Communi::IrcConnection::messageReceived, this,
&IrcManager::messageReceived);
QObject::connect(c, &Communi::IrcConnection::privateMessageReceived, this,
&IrcManager::privateMessageReceived);
QObject::connect(c, &IrcConnection::messageReceived, &messageReceived);
QObject::connect(c, &IrcConnection::privateMessageReceived,
&privateMessageReceived);
if (account->isAnon()) {
if (_account.isAnon()) {
// fetch ignored users
QString username = account->getUsername();
QString oauthClient = account->getOauthClient();
QString oauthToken = account->getOauthToken();
QString username = _account.getUserName();
QString oauthClient = _account.getOAuthClient();
QString oauthToken = _account.getOAuthToken();
{
QString nextLink = "https://api.twitch.tv/kraken/users/" +
username + "/blocks?limit=" + 100 +
"&client_id=" + oauthClient;
QString nextLink = "https://api.twitch.tv/kraken/users/" + username +
"/blocks?limit=" + 100 + "&client_id=" + oauthClient;
QNetworkAccessManager *manager = new QNetworkAccessManager();
QNetworkRequest req(QUrl(nextLink + "&oauth_token=" + oauthToken));
QNetworkReply *reply = manager->get(req);
QObject::connect(reply, &QNetworkReply::finished, [=] {
IrcManager::twitchBlockedUsersMutex.lock();
IrcManager::twitchBlockedUsers.clear();
IrcManager::twitchBlockedUsersMutex.unlock();
_twitchBlockedUsersMutex.lock();
_twitchBlockedUsers.clear();
_twitchBlockedUsersMutex.unlock();
QByteArray data = reply->readAll();
QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
@ -78,15 +94,13 @@ IrcManager::beginConnecting()
auto blocks = root.value("blocks").toArray();
IrcManager::twitchBlockedUsersMutex.lock();
_twitchBlockedUsersMutex.lock();
for (QJsonValue block : blocks) {
QJsonObject user =
block.toObject().value("user").toObject();
QJsonObject user = block.toObject().value("user").toObject();
// display_name
IrcManager::twitchBlockedUsers.insert(
user.value("name").toString().toLower(), true);
_twitchBlockedUsers.insert(user.value("name").toString().toLower(), true);
}
IrcManager::twitchBlockedUsersMutex.unlock();
_twitchBlockedUsersMutex.unlock();
manager->deleteLater();
});
@ -94,10 +108,10 @@ IrcManager::beginConnecting()
// fetch available twitch emtoes
{
QNetworkRequest req(QUrl("https://api.twitch.tv/kraken/users/" +
username + "/emotes?oauth_token=" +
oauthToken + "&client_id=" + oauthClient));
QNetworkReply *reply = IrcManager::accessManager.get(req);
QNetworkRequest req(QUrl("https://api.twitch.tv/kraken/users/" + username +
"/emotes?oauth_token=" + oauthToken +
"&client_id=" + oauthClient));
QNetworkReply *reply = _accessManager.get(req);
QObject::connect(reply, &QNetworkReply::finished, [=] {
QByteArray data = reply->readAll();
@ -109,15 +123,13 @@ IrcManager::beginConnecting()
auto blocks = root.value("blocks").toArray();
IrcManager::twitchBlockedUsersMutex.lock();
_twitchBlockedUsersMutex.lock();
for (QJsonValue block : blocks) {
QJsonObject user =
block.toObject().value("user").toObject();
QJsonObject user = block.toObject().value("user").toObject();
// display_name
IrcManager::twitchBlockedUsers.insert(
user.value("name").toString().toLower(), true);
_twitchBlockedUsers.insert(user.value("name").toString().toLower(), true);
}
IrcManager::twitchBlockedUsersMutex.unlock();
_twitchBlockedUsersMutex.unlock();
});
}
}
@ -129,19 +141,17 @@ IrcManager::beginConnecting()
c->setNickName("justinfan123");
c->setRealName("justinfan123");
c->sendCommand(IrcCommand::createCapability("REQ", "twitch.tv/commands"));
c->sendCommand(IrcCommand::createCapability("REQ", "twitch.tv/tags"));
c->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/commands"));
c->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/tags"));
QMutexLocker locker(&IrcManager::connectionMutex);
QMutexLocker locker(&_connectionMutex);
if (generation == IrcManager::connectionGeneration) {
if (generation == _connectionGeneration) {
c->moveToThread(QCoreApplication::instance()->thread());
IrcManager::connection = std::shared_ptr<IrcConnection>(c);
_connection = std::shared_ptr<Communi::IrcConnection>(c);
auto channels = Channels::getItems();
for (auto &channel : channels) {
c->sendRaw("JOIN #" + channel.get()->getName());
for (auto &channel : ChannelManager::getInstance().getItems()) {
c->sendRaw("JOIN #" + channel->getName());
}
c->open();
@ -150,53 +160,50 @@ IrcManager::beginConnecting()
}
}
void
IrcManager::disconnect()
void IrcManager::disconnect()
{
IrcManager::connectionMutex.lock();
_connectionMutex.lock();
auto c = IrcManager::connection;
if (IrcManager::connection.get() != NULL) {
IrcManager::connection = std::shared_ptr<IrcConnection>();
auto c = _connection;
if (_connection.get() != NULL) {
_connection = std::shared_ptr<Communi::IrcConnection>();
}
IrcManager::connectionMutex.unlock();
_connectionMutex.unlock();
}
void
IrcManager::send(QString raw)
void IrcManager::send(QString raw)
{
IrcManager::connectionMutex.lock();
IrcManager::connection->sendRaw(raw);
IrcManager::connectionMutex.unlock();
_connectionMutex.lock();
_connection->sendRaw(raw);
_connectionMutex.unlock();
}
void
IrcManager::sendJoin(const QString &channel)
void IrcManager::sendJoin(const QString &channel)
{
IrcManager::connectionMutex.lock();
_connectionMutex.lock();
if (IrcManager::connection.get() != NULL) {
IrcManager::connection.get()->sendRaw("JOIN #" + channel);
if (_connection.get() != NULL) {
_connection.get()->sendRaw("JOIN #" + channel);
}
IrcManager::connectionMutex.unlock();
_connectionMutex.unlock();
}
void
IrcManager::partChannel(const QString &channel)
void IrcManager::partChannel(const QString &channel)
{
IrcManager::connectionMutex.lock();
_connectionMutex.lock();
if (IrcManager::connection.get() != NULL) {
IrcManager::connection.get()->sendRaw("PART #" + channel);
if (_connection.get() != NULL) {
_connection.get()->sendRaw("PART #" + channel);
}
IrcManager::connectionMutex.unlock();
_connectionMutex.unlock();
}
void
IrcManager::messageReceived(IrcMessage *message)
void IrcManager::messageReceived(Communi::IrcMessage *message)
{
// qInfo(message->command().toStdString().c_str());
@ -211,63 +218,51 @@ IrcManager::messageReceived(IrcMessage *message)
// }
}
void
IrcManager::privateMessageReceived(IrcPrivateMessage *message)
void IrcManager::privateMessageReceived(Communi::IrcPrivateMessage *message)
{
// qInfo(message->content().toStdString().c_str());
auto c = Channels::getChannel(message->target().mid(1));
auto c = ChannelManager::getInstance().getChannel(message->target().mid(1));
if (c != NULL) {
c->addMessage(std::shared_ptr<messages::Message>(
new messages::Message(*message, *c)));
messages::MessageParseArgs args;
c->addMessage(twitch::TwitchMessageBuilder::parse(message, c.get(), args));
}
}
bool
IrcManager::isTwitchBlockedUser(QString const &username)
bool IrcManager::isTwitchBlockedUser(QString const &username)
{
IrcManager::twitchBlockedUsersMutex.lock();
QMutexLocker locker(&_twitchBlockedUsersMutex);
auto iterator = IrcManager::twitchBlockedUsers.find(username);
auto iterator = _twitchBlockedUsers.find(username);
if (iterator == IrcManager::twitchBlockedUsers.end()) {
IrcManager::twitchBlockedUsersMutex.unlock();
return false;
}
IrcManager::twitchBlockedUsersMutex.unlock();
return true;
return iterator != _twitchBlockedUsers.end();
}
bool
IrcManager::tryAddIgnoredUser(QString const &username, QString &errorMessage)
bool IrcManager::tryAddIgnoredUser(QString const &username, QString &errorMessage)
{
QUrl url("https://api.twitch.tv/kraken/users/" + account->getUsername() +
"/blocks/" + username +
"?oauth_token=" + account->getOauthToken() +
"&client_id=" + account->getOauthClient());
QUrl url("https://api.twitch.tv/kraken/users/" + _account.getUserName() + "/blocks/" +
username + "?oauth_token=" + _account.getOAuthToken() +
"&client_id=" + _account.getOAuthClient());
QNetworkRequest request(url);
auto reply = IrcManager::accessManager.put(request, QByteArray());
auto reply = _accessManager.put(request, QByteArray());
reply->waitForReadyRead(10000);
if (reply->error() == QNetworkReply::NoError) {
IrcManager::twitchBlockedUsersMutex.lock();
IrcManager::twitchBlockedUsers.insert(username, true);
IrcManager::twitchBlockedUsersMutex.unlock();
_twitchBlockedUsersMutex.lock();
_twitchBlockedUsers.insert(username, true);
_twitchBlockedUsersMutex.unlock();
delete reply;
return true;
}
errorMessage = "Error while ignoring user \"" + username +
"\": " + reply->errorString();
reply->deleteLater();
errorMessage = "Error while ignoring user \"" + username + "\": " + reply->errorString();
return false;
}
void
IrcManager::addIgnoredUser(QString const &username)
void IrcManager::addIgnoredUser(QString const &username)
{
QString errorMessage;
if (!tryAddIgnoredUser(username, errorMessage)) {
@ -275,38 +270,40 @@ IrcManager::addIgnoredUser(QString const &username)
}
}
bool
IrcManager::tryRemoveIgnoredUser(QString const &username, QString &errorMessage)
bool IrcManager::tryRemoveIgnoredUser(QString const &username, QString &errorMessage)
{
QUrl url("https://api.twitch.tv/kraken/users/" + account->getUsername() +
"/blocks/" + username +
"?oauth_token=" + account->getOauthToken() +
"&client_id=" + account->getOauthClient());
QUrl url("https://api.twitch.tv/kraken/users/" + _account.getUserName() + "/blocks/" +
username + "?oauth_token=" + _account.getOAuthToken() +
"&client_id=" + _account.getOAuthClient());
QNetworkRequest request(url);
auto reply = IrcManager::accessManager.deleteResource(request);
auto reply = _accessManager.deleteResource(request);
reply->waitForReadyRead(10000);
if (reply->error() == QNetworkReply::NoError) {
IrcManager::twitchBlockedUsersMutex.lock();
IrcManager::twitchBlockedUsers.remove(username);
IrcManager::twitchBlockedUsersMutex.unlock();
_twitchBlockedUsersMutex.lock();
_twitchBlockedUsers.remove(username);
_twitchBlockedUsersMutex.unlock();
delete reply;
return true;
}
errorMessage = "Error while unignoring user \"" + username +
"\": " + reply->errorString();
reply->deleteLater();
errorMessage = "Error while unignoring user \"" + username + "\": " + reply->errorString();
return false;
}
void
IrcManager::removeIgnoredUser(QString const &username)
void IrcManager::removeIgnoredUser(QString const &username)
{
QString errorMessage;
if (!tryRemoveIgnoredUser(username, errorMessage)) {
// TODO: Implement IrcManager::removeIgnoredUser
}
}
QNetworkAccessManager &IrcManager::getAccessManager()
{
return _accessManager;
}
}

View file

@ -3,8 +3,8 @@
#define TWITCH_MAX_MESSAGELENGTH 500
#include "account.h"
#include "messages/message.h"
#include "twitch/twitchuser.h"
#include <IrcMessage>
#include <QMap>
@ -16,50 +16,59 @@
namespace chatterino {
class IrcManager
class IrcManager : public QObject
{
public:
static void connect();
static void disconnect();
Q_OBJECT
static void send(QString raw);
public:
static IrcManager &getInstance()
{
return instance;
}
static const QString defaultClientId;
void connect();
void disconnect();
void send(QString raw);
bool isTwitchBlockedUser(QString const &username);
bool tryAddIgnoredUser(QString const &username, QString &errorMessage);
void addIgnoredUser(QString const &username);
bool tryRemoveIgnoredUser(QString const &username, QString &errorMessage);
void removeIgnoredUser(QString const &username);
static Account *account;
QNetworkAccessManager &getAccessManager();
static QNetworkAccessManager &
getAccessManager()
{
return accessManager;
}
void sendJoin(const QString &channel);
static void sendJoin(const QString &channel);
void partChannel(const QString &channel);
static void partChannel(const QString &channel);
const twitch::TwitchUser &getUser() const;
void setUser(const twitch::TwitchUser &account);
private:
static IrcManager instance;
IrcManager();
static QMap<QString, bool> twitchBlockedUsers;
static QMutex twitchBlockedUsersMutex;
// variables
twitch::TwitchUser _account;
static QNetworkAccessManager accessManager;
std::shared_ptr<Communi::IrcConnection> _connection;
QMutex _connectionMutex;
long _connectionGeneration;
static void beginConnecting();
QMap<QString, bool> _twitchBlockedUsers;
QMutex _twitchBlockedUsersMutex;
static std::shared_ptr<IrcConnection> connection;
static QMutex connectionMutex;
static long connectionGeneration;
QNetworkAccessManager _accessManager;
static void messageReceived(IrcMessage *message);
static void privateMessageReceived(IrcPrivateMessage *message);
// methods
void beginConnecting();
void messageReceived(Communi::IrcMessage *message);
void privateMessageReceived(Communi::IrcPrivateMessage *message);
};
}

33
ircuser2.cpp Normal file
View file

@ -0,0 +1,33 @@
#include "ircuser2.h"
namespace chatterino {
IrcUser2::IrcUser2(const QString &userName, const QString &nickName, const QString &realName,
const QString &password)
: _userName(userName)
, _nickName(nickName)
, _realName(realName)
, _password(password)
{
}
const QString &IrcUser2::getUserName() const
{
return _userName;
}
const QString &IrcUser2::getNickName() const
{
return _nickName;
}
const QString &IrcUser2::getRealName() const
{
return _realName;
}
const QString &IrcUser2::getPassword() const
{
return _password;
}
}

27
ircuser2.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef IRCUSER_H
#define IRCUSER_H
#include <QString>
namespace chatterino {
class IrcUser2
{
public:
IrcUser2(const QString &userName, const QString &nickName, const QString &realName,
const QString &password);
const QString &getUserName() const;
const QString &getNickName() const;
const QString &getRealName() const;
const QString &getPassword() const;
private:
QString _userName;
QString _nickName;
QString _realName;
QString _password;
};
}
#endif // IRCUSER_H

View file

@ -14,12 +14,10 @@ Channel::Channel(const QString &_channelName, const QString &_baseDirectory)
{
QDateTime now = QDateTime::currentDateTime();
this->fileName =
this->channelName + "-" + now.toString("yyyy-MM-dd") + ".log";
this->fileName = this->channelName + "-" + now.toString("yyyy-MM-dd") + ".log";
// Open file handle to log file of current date
this->fileHandle.setFileName(this->baseDirectory + QDir::separator() +
this->fileName);
this->fileHandle.setFileName(this->baseDirectory + QDir::separator() + this->fileName);
this->fileHandle.open(QIODevice::Append);
this->appendLine(this->generateOpeningString(now));
@ -31,8 +29,7 @@ Channel::~Channel()
this->fileHandle.close();
}
void
Channel::append(std::shared_ptr<messages::Message> message)
void Channel::append(std::shared_ptr<messages::Message> message)
{
QDateTime now = QDateTime::currentDateTime();
@ -47,8 +44,7 @@ Channel::append(std::shared_ptr<messages::Message> message)
this->appendLine(str);
}
QString
Channel::generateOpeningString(const QDateTime &now) const
QString Channel::generateOpeningString(const QDateTime &now) const
{
QString ret = QLatin1Literal("# Start logging at ");
@ -59,8 +55,7 @@ Channel::generateOpeningString(const QDateTime &now) const
return ret;
}
QString
Channel::generateClosingString(const QDateTime &now) const
QString Channel::generateClosingString(const QDateTime &now) const
{
QString ret = QLatin1Literal("# Stop logging at ");
@ -71,8 +66,7 @@ Channel::generateClosingString(const QDateTime &now) const
return ret;
}
void
Channel::appendLine(const QString &line)
void Channel::appendLine(const QString &line)
{
this->fileHandle.write(line.toUtf8());
this->fileHandle.flush();

View file

@ -15,17 +15,14 @@ namespace logging {
class Channel
{
public:
explicit Channel(const QString &_channelName,
const QString &_baseDirectory);
explicit Channel(const QString &_channelName, const QString &_baseDirectory);
~Channel();
void append(std::shared_ptr<messages::Message> message);
private:
QString generateOpeningString(
const QDateTime &now = QDateTime::currentDateTime()) const;
QString generateClosingString(
const QDateTime &now = QDateTime::currentDateTime()) const;
QString generateOpeningString(const QDateTime &now = QDateTime::currentDateTime()) const;
QString generateClosingString(const QDateTime &now = QDateTime::currentDateTime()) const;
void appendLine(const QString &line);

View file

@ -15,13 +15,11 @@ static QString mentionsBasePath;
std::unordered_map<std::string, std::weak_ptr<Channel>> channels;
void
init()
void init()
{
// Make sure all folders are properly created
logBasePath =
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
QDir::separator() + "Logs";
logBasePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
QDir::separator() + "Logs";
channelBasePath = logBasePath + QDir::separator() + "Channels";
whispersBasePath = logBasePath + QDir::separator() + "Whispers";
mentionsBasePath = logBasePath + QDir::separator() + "Mentions";
@ -55,8 +53,7 @@ init()
}
}
static const QString &
getBaseDirectory(const QString &channelName)
static const QString &getBaseDirectory(const QString &channelName)
{
if (channelName == "/whispers") {
return whispersBasePath;
@ -67,8 +64,7 @@ getBaseDirectory(const QString &channelName)
}
}
std::shared_ptr<Channel>
get(const QString &channelName)
std::shared_ptr<Channel> get(const QString &channelName)
{
if (channelName.isEmpty()) {
return nullptr;

View file

@ -1,13 +1,13 @@
#include "channels.h"
#include "channelmanager.h"
#include "colorscheme.h"
#include "emojis.h"
#include "emotes.h"
#include "emotemanager.h"
#include "ircmanager.h"
#include "logging/loggingmanager.h"
#include "resources.h"
#include "settings.h"
#include "settingsmanager.h"
#include "widgets/mainwindow.h"
#include "windows.h"
#include "windowmanager.h"
#include <QApplication>
#include <QClipboard>
@ -22,26 +22,25 @@ main(int argc, char *argv[])
QApplication a(argc, argv);
chatterino::logging::init();
chatterino::initChannels();
Settings::getInstance().load();
SettingsManager::getInstance().load();
Resources::load();
Emojis::loadEmojis();
Emotes::loadGlobalEmotes();
EmoteManager::getInstance().loadGlobalEmotes();
ColorScheme::getInstance().init();
Windows::load();
WindowManager::load();
MainWindow &w = Windows::getMainWindow();
MainWindow &w = WindowManager::getMainWindow();
w.show();
IrcManager::connect();
IrcManager::getInstance().connect();
int ret = a.exec();
Settings::getInstance().save();
SettingsManager::getInstance().save();
Windows::save();
WindowManager::save();
return ret;
}

View file

@ -1,9 +1,9 @@
#include "messages/lazyloadedimage.h"
#include "asyncexec.h"
#include "emotes.h"
#include "emotemanager.h"
#include "ircmanager.h"
#include "windows.h"
#include "windowmanager.h"
#include <QBuffer>
#include <QImageReader>
@ -16,9 +16,8 @@
namespace chatterino {
namespace messages {
LazyLoadedImage::LazyLoadedImage(const QString &url, qreal scale,
const QString &name, const QString &tooltip,
const QMargins &margin, bool isHat)
LazyLoadedImage::LazyLoadedImage(const QString &url, qreal scale, const QString &name,
const QString &tooltip, const QMargins &margin, bool isHat)
: currentPixmap(NULL)
, allFrames()
, currentFrame(0)
@ -34,9 +33,8 @@ LazyLoadedImage::LazyLoadedImage(const QString &url, qreal scale,
{
}
LazyLoadedImage::LazyLoadedImage(QPixmap *image, qreal scale,
const QString &name, const QString &tooltip,
const QMargins &margin, bool isHat)
LazyLoadedImage::LazyLoadedImage(QPixmap *image, qreal scale, const QString &name,
const QString &tooltip, const QMargins &margin, bool isHat)
: currentPixmap(image)
, allFrames()
, currentFrame(0)
@ -52,8 +50,7 @@ LazyLoadedImage::LazyLoadedImage(QPixmap *image, qreal scale,
{
}
void
LazyLoadedImage::loadImage()
void LazyLoadedImage::loadImage()
{
QNetworkAccessManager *manager = new QNetworkAccessManager();
@ -92,29 +89,25 @@ LazyLoadedImage::loadImage()
if (allFrames.size() > 1) {
this->animated = true;
Emotes::getGifUpdateSignal().connect([this] { gifUpdateTimout(); });
EmoteManager::getInstance().getGifUpdateSignal().connect([this] { gifUpdateTimout(); });
}
Emotes::incGeneration();
Windows::layoutVisibleChatWidgets();
EmoteManager::getInstance().incGeneration();
WindowManager::layoutVisibleChatWidgets();
reply->deleteLater();
manager->deleteLater();
});
}
void
LazyLoadedImage::gifUpdateTimout()
void LazyLoadedImage::gifUpdateTimout()
{
this->currentFrameOffset += GIF_FRAME_LENGTH;
while (true) {
if (this->currentFrameOffset >
this->allFrames.at(this->currentFrame).duration) {
this->currentFrameOffset -=
this->allFrames.at(this->currentFrame).duration;
this->currentFrame =
(this->currentFrame + 1) % this->allFrames.size();
if (this->currentFrameOffset > this->allFrames.at(this->currentFrame).duration) {
this->currentFrameOffset -= this->allFrames.at(this->currentFrame).duration;
this->currentFrame = (this->currentFrame + 1) % this->allFrames.size();
} else {
break;
}

View file

@ -10,19 +10,14 @@ namespace messages {
class LazyLoadedImage : QObject
{
public:
explicit LazyLoadedImage(const QString &url, qreal scale = 1,
const QString &name = "",
const QString &tooltip = "",
const QMargins &margin = QMargins(),
explicit LazyLoadedImage(const QString &url, qreal scale = 1, const QString &name = "",
const QString &tooltip = "", const QMargins &margin = QMargins(),
bool isHat = false);
explicit LazyLoadedImage(QPixmap *currentPixmap, qreal scale = 1,
const QString &name = "",
const QString &tooltip = "",
const QMargins &margin = QMargins(),
explicit LazyLoadedImage(QPixmap *currentPixmap, qreal scale = 1, const QString &name = "",
const QString &tooltip = "", const QMargins &margin = QMargins(),
bool isHat = false);
const QPixmap *
getPixmap()
const QPixmap *getPixmap()
{
if (!isLoading) {
isLoading = true;
@ -32,50 +27,42 @@ public:
return currentPixmap;
}
qreal
getScale() const
qreal getScale() const
{
return scale;
}
const QString &
getUrl() const
const QString &getUrl() const
{
return url;
}
const QString &
getName() const
const QString &getName() const
{
return name;
}
const QString &
getTooltip() const
const QString &getTooltip() const
{
return tooltip;
}
const QMargins &
getMargin() const
const QMargins &getMargin() const
{
return margin;
}
bool
getAnimated() const
bool getAnimated() const
{
return animated;
}
bool
getIsHat() const
bool getIsHat() const
{
return ishat;
}
int
getWidth() const
int getWidth() const
{
if (currentPixmap == NULL) {
return 16;
@ -83,8 +70,7 @@ public:
return currentPixmap->width();
}
int
getHeight() const
int getHeight() const
{
if (currentPixmap == NULL) {
return 16;

View file

@ -24,8 +24,7 @@ public:
vector->reserve(this->limit + this->buffer);
}
void
clear()
void clear()
{
std::lock_guard<std::mutex> lock(this->mutex);
@ -37,8 +36,7 @@ public:
// return true if an item was deleted
// deleted will be set if the item was deleted
bool
appendItem(const T &item, T &deleted)
bool appendItem(const T &item, T &deleted)
{
std::lock_guard<std::mutex> lock(this->mutex);
@ -63,7 +61,7 @@ public:
} else {
deleted = this->vector->at(this->offset);
//append item and increment offset("deleting" first element)
// append item and increment offset("deleting" first element)
this->vector->push_back(item);
this->offset++;
@ -77,12 +75,11 @@ public:
}
}
messages::LimitedQueueSnapshot<T>
getSnapshot()
messages::LimitedQueueSnapshot<T> getSnapshot()
{
std::lock_guard<std::mutex> lock(this->mutex);
if(vector->size() < limit) {
if (vector->size() < limit) {
return LimitedQueueSnapshot<T>(this->vector, this->offset, this->vector->size());
} else {
return LimitedQueueSnapshot<T>(this->vector, this->offset, this->limit);

View file

@ -11,16 +11,14 @@ template <typename T>
class LimitedQueueSnapshot
{
public:
LimitedQueueSnapshot(std::shared_ptr<std::vector<T>> ptr, int offset,
int length)
LimitedQueueSnapshot(std::shared_ptr<std::vector<T>> ptr, int offset, int length)
: vector(ptr)
, offset(offset)
, length(length)
{
}
int
getLength()
int getLength()
{
return this->length;
}

View file

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

View file

@ -2,13 +2,13 @@
#include "channel.h"
#include "colorscheme.h"
#include "emojis.h"
#include "emotes.h"
#include "fonts.h"
#include "emotemanager.h"
#include "fontmanager.h"
#include "ircmanager.h"
#include "messages/link.h"
#include "qcolor.h"
#include "resources.h"
#include "settings.h"
#include "settingsmanager.h"
#include <QObjectUserData>
#include <QStringList>
@ -19,396 +19,66 @@
namespace chatterino {
namespace messages {
QRegularExpression *Message::cheerRegex =
new QRegularExpression("cheer[1-9][0-9]*");
Message::Message(const QString &text)
: _words()
{
words.push_back(Word(text, Word::Text,
ColorScheme::getInstance().SystemMessageColor, text,
QString()));
_words.push_back(
Word(text, Word::Text, ColorScheme::getInstance().SystemMessageColor, text, QString()));
}
Message::Message(const IrcPrivateMessage &ircMessage, Channel &channel,
bool enablePingSound, bool isReceivedWhisper,
bool isSentWhisper, bool includeChannel)
: content(ircMessage.content())
Message::Message(const std::vector<Word> &words)
: _words(words)
{
this->parseTime = std::chrono::system_clock::now();
auto words = std::vector<Word>();
auto tags = ircMessage.tags();
auto iterator = tags.find("id");
if (iterator != tags.end()) {
this->id = iterator.value().toString();
}
// timestamps
iterator = tags.find("tmi-sent-ts");
std::time_t time = std::time(NULL);
// if (iterator != tags.end()) {
// time = strtoll(iterator.value().toString().toStdString().c_str(),
// NULL,
// 10);
// }
char timeStampBuffer[69];
strftime(timeStampBuffer, 69, "%H:%M", localtime(&time));
QString timestamp = QString(timeStampBuffer);
strftime(timeStampBuffer, 69, "%H:%M:%S", localtime(&time));
QString timestampWithSeconds = QString(timeStampBuffer);
words.push_back(Word(timestamp, Word::TimestampNoSeconds,
ColorScheme::getInstance().SystemMessageColor,
QString(), QString()));
words.push_back(Word(timestampWithSeconds, Word::TimestampWithSeconds,
ColorScheme::getInstance().SystemMessageColor,
QString(), QString()));
// mod buttons
static QString buttonBanTooltip("Ban user");
static QString buttonTimeoutTooltip("Timeout user");
words.push_back(Word(Resources::getButtonBan(), Word::ButtonBan, QString(),
buttonBanTooltip,
Link(Link::UserBan, ircMessage.account())));
words.push_back(Word(Resources::getButtonTimeout(), Word::ButtonTimeout,
QString(), buttonTimeoutTooltip,
Link(Link::UserTimeout, ircMessage.account())));
// badges
iterator = tags.find("badges");
if (iterator != tags.end()) {
auto badges = iterator.value().toString().split(',');
for (QString badge : badges) {
if (badge.startsWith("bits/")) {
long long int cheer =
strtoll(badge.mid(5).toStdString().c_str(), NULL, 10);
words.push_back(Word(
Emotes::getCheerBadge(cheer), Word::BadgeCheer, QString(),
QString("Twitch Cheer" + QString::number(cheer))));
} else if (badge == "staff/1") {
words.push_back(Word(Resources::getBadgeStaff(),
Word::BadgeStaff, QString(),
QString("Twitch Staff")));
} else if (badge == "admin/1") {
words.push_back(Word(Resources::getBadgeAdmin(),
Word::BadgeAdmin, QString(),
QString("Twitch Admin")));
} else if (badge == "global_mod/1") {
words.push_back(Word(Resources::getBadgeGlobalmod(),
Word::BadgeGlobalMod, QString(),
QString("Global Moderator")));
} else if (badge == "moderator/1") {
// TODO: implement this xD
words.push_back(Word(
Resources::getBadgeTurbo(), Word::BadgeModerator, QString(),
QString("Channel Moderator"))); // custom badge
} else if (badge == "turbo/1") {
words.push_back(Word(Resources::getBadgeStaff(),
Word::BadgeTurbo, QString(),
QString("Turbo Subscriber")));
} else if (badge == "broadcaster/1") {
words.push_back(Word(Resources::getBadgeBroadcaster(),
Word::BadgeBroadcaster, QString(),
QString("Channel Broadcaster")));
} else if (badge == "premium/1") {
words.push_back(Word(Resources::getBadgePremium(),
Word::BadgePremium, QString(),
QString("Twitch Prime")));
}
}
}
// color
QColor usernameColor = ColorScheme::getInstance().SystemMessageColor;
iterator = tags.find("color");
if (iterator != tags.end()) {
usernameColor = QColor(iterator.value().toString());
}
// channel name
if (includeChannel) {
QString channelName("#" + channel.getName());
words.push_back(Word(
channelName, Word::Misc,
ColorScheme::getInstance().SystemMessageColor, QString(channelName),
QString(), Link(Link::Url, channel.getName() + "\n" + this->id)));
}
// username
this->userName = ircMessage.nick();
if (this->userName.isEmpty()) {
this->userName = tags.value(QLatin1String("login")).toString();
}
QString displayName;
iterator = tags.find("display-name");
if (iterator == tags.end()) {
displayName = ircMessage.account();
} else {
displayName = iterator.value().toString();
}
bool hasLocalizedName =
QString::compare(displayName, ircMessage.account()) == 0;
QString userDisplayString =
displayName +
(hasLocalizedName ? (" (" + ircMessage.account() + ")") : QString());
if (isSentWhisper) {
userDisplayString = IrcManager::account->getUsername() + " -> ";
}
if (isReceivedWhisper) {
userDisplayString += " -> " + IrcManager::account->getUsername();
}
if (!ircMessage.isAction()) {
userDisplayString += ": ";
}
words.push_back(Word(userDisplayString, Word::Username, usernameColor,
userDisplayString, QString()));
// highlights
// TODO: implement this xD
// bits
QString bits = "";
iterator = tags.find("bits");
if (iterator != tags.end()) {
bits = iterator.value().toString();
}
// twitch emotes
std::vector<std::pair<long int, LazyLoadedImage *>> twitchEmotes;
iterator = tags.find("emotes");
if (iterator != tags.end()) {
auto emotes = iterator.value().toString().split('/');
for (QString emote : emotes) {
if (!emote.contains(':'))
continue;
QStringList parameters = emote.split(':');
if (parameters.length() < 2)
continue;
long int id = std::stol(parameters.at(0).toStdString(), NULL, 10);
QStringList occurences = parameters.at(1).split(',');
for (QString occurence : occurences) {
QStringList coords = occurence.split('-');
if (coords.length() < 2)
continue;
long int start =
std::stol(coords.at(0).toStdString(), NULL, 10);
long int end = std::stol(coords.at(1).toStdString(), NULL, 10);
if (start >= end || start < 0 ||
end > ircMessage.content().length())
continue;
QString name = ircMessage.content().mid(start, end - start + 1);
twitchEmotes.push_back(std::pair<long int, LazyLoadedImage *>(
start, Emotes::getTwitchEmoteById(name, id)));
}
}
std::sort(twitchEmotes.begin(), twitchEmotes.end(), sortTwitchEmotes);
}
auto currentTwitchEmote = twitchEmotes.begin();
// words
QColor textColor =
ircMessage.isAction() ? usernameColor : ColorScheme::getInstance().Text;
QStringList splits = ircMessage.content().split(' ');
long int i = 0;
for (QString split : splits) {
// twitch emote
if (currentTwitchEmote != twitchEmotes.end() &&
currentTwitchEmote->first == i) {
words.push_back(Word(currentTwitchEmote->second,
Word::TwitchEmoteImage,
currentTwitchEmote->second->getName(),
currentTwitchEmote->second->getName() +
QString("\nTwitch Emote")));
words.push_back(Word(currentTwitchEmote->second->getName(),
Word::TwitchEmoteText, textColor,
currentTwitchEmote->second->getName(),
currentTwitchEmote->second->getName() +
QString("\nTwitch Emote")));
i += split.length() + 1;
currentTwitchEmote = std::next(currentTwitchEmote);
continue;
}
// split words
std::vector<std::tuple<LazyLoadedImage *, QString>> parsed;
Emojis::parseEmojis(parsed, split);
for (const std::tuple<LazyLoadedImage *, QString> &tuple : parsed) {
LazyLoadedImage *image = std::get<0>(tuple);
if (image == NULL) { // is text
QString string = std::get<1>(tuple);
// cheers
if (!bits.isEmpty() && string.length() >= 6 &&
cheerRegex->match(string).isValid()) {
auto cheer = string.mid(5).toInt();
QString color;
QColor bitsColor;
if (cheer >= 10000) {
color = "red";
bitsColor = QColor::fromHslF(0, 1, 0.5);
} else if (cheer >= 5000) {
color = "blue";
bitsColor = QColor::fromHslF(0.61, 1, 0.4);
} else if (cheer >= 1000) {
color = "green";
bitsColor = QColor::fromHslF(0.5, 1, 0.5);
} else if (cheer >= 100) {
color = "purple";
bitsColor = QColor::fromHslF(0.8, 1, 0.5);
} else {
color = "gray";
bitsColor = QColor::fromHslF(0.5f, 0.5f, 0.5f);
}
QString bitsLinkAnimated = QString(
"http://static-cdn.jtvnw.net/bits/dark/animated/" +
color + "/1");
QString bitsLink = QString(
"http://static-cdn.jtvnw.net/bits/dark/static/" +
color + "/1");
LazyLoadedImage *imageAnimated =
Emotes::getMiscImageFromCache().getOrAdd(
bitsLinkAnimated, [&bitsLinkAnimated] {
return new LazyLoadedImage(bitsLinkAnimated);
});
LazyLoadedImage *image =
Emotes::getMiscImageFromCache().getOrAdd(
bitsLink, [&bitsLink] {
return new LazyLoadedImage(bitsLink);
});
words.push_back(
Word(imageAnimated, Word::BitsAnimated,
QString("cheer"), QString("Twitch Cheer"),
Link(Link::Url,
QString("https://blog.twitch.tv/"
"introducing-cheering-celebrate-"
"together-da62af41fac6"))));
words.push_back(
Word(image, Word::BitsStatic, QString("cheer"),
QString("Twitch Cheer"),
Link(Link::Url,
QString("https://blog.twitch.tv/"
"introducing-cheering-celebrate-"
"together-da62af41fac6"))));
words.push_back(
Word(QString("x" + string.mid(5)), Word::BitsAmount,
bitsColor, QString(string.mid(5)),
QString("Twitch Cheer"),
Link(Link::Url,
QString("https://blog.twitch.tv/"
"introducing-cheering-celebrate-"
"together-da62af41fac6"))));
continue;
}
// bttv / ffz emotes
LazyLoadedImage *bttvEmote;
// TODO: Implement this (ignored emotes)
if (Emotes::getBttvEmotes().tryGet(string, bttvEmote) ||
channel.getBttvChannelEmotes().tryGet(string, bttvEmote) ||
Emotes::getFfzEmotes().tryGet(string, bttvEmote) ||
channel.getFfzChannelEmotes().tryGet(string, bttvEmote) ||
Emotes::getChatterinoEmotes().tryGet(string, bttvEmote)) {
words.push_back(Word(bttvEmote, Word::BttvEmoteImage,
bttvEmote->getName(),
bttvEmote->getTooltip(),
Link(Link::Url, bttvEmote->getUrl())));
continue;
}
// actually just a word
QString link = matchLink(string);
words.push_back(
Word(string, Word::Text, textColor, string, QString(),
link.isEmpty() ? Link() : Link(Link::Url, link)));
} else { // is emoji
static QString emojiTooltip("Emoji");
words.push_back(Word(image, Word::EmojiImage, image->getName(),
emojiTooltip));
Word(image->getName(), Word::EmojiText, textColor,
image->getName(), emojiTooltip);
}
}
i += split.length() + 1;
}
this->words = words;
// TODO: Implement this xD
// if (!isReceivedWhisper &&
// AppSettings.HighlightIgnoredUsers.ContainsKey(Username))
// {
// HighlightTab = false;
// }
}
QString
Message::matchLink(const QString &string)
bool Message::getCanHighlightTab() const
{
// TODO: Implement this xD
return QString();
return _highlightTab;
}
bool
Message::sortTwitchEmotes(const std::pair<long int, LazyLoadedImage *> &a,
const std::pair<long int, LazyLoadedImage *> &b)
const QString &Message::getTimeoutUser() const
{
return a.first < b.first;
return _timeoutUser;
}
int Message::getTimeoutCount() const
{
return _timeoutCount;
}
const QString &Message::getUserName() const
{
return _userName;
}
const QString &Message::getDisplayName() const
{
return _displayName;
}
const QString &Message::getContent() const
{
return _content;
}
const std::chrono::time_point<std::chrono::system_clock> &Message::getParseTime() const
{
return _parseTime;
}
std::vector<Word> &Message::getWords()
{
return _words;
}
bool Message::isDisabled() const
{
return _isDisabled;
}
const QString &Message::getId() const
{
return _id;
}
} // namespace messages

View file

@ -1,6 +1,8 @@
#ifndef MESSAGE_H
#define MESSAGE_H
#include "messages/message.h"
#include "messages/messageparseargs.h"
#include "messages/word.h"
#include "messages/wordpart.h"
@ -8,84 +10,33 @@
#include <QVector>
#include <chrono>
#include <memory>
namespace chatterino {
class Channel;
namespace messages {
class Message;
typedef std::shared_ptr<Message> SharedMessage;
class Message
{
public:
Message(const QString &text);
Message(const IrcPrivateMessage &ircMessage, Channel &channel,
bool enablePingSound = true, bool isReceivedWhisper = false,
bool isSentWhisper = false, bool includeChannel = false);
Message(const std::vector<messages::Word> &words);
~Message()
{
}
bool
getCanHighlightTab() const
{
return highlightTab;
}
const QString &
getTimeoutUser() const
{
return timeoutUser;
}
int
getTimeoutCount() const
{
return timeoutCount;
}
const QString &
getUserName() const
{
return userName;
}
const QString &
getDisplayName() const
{
return displayName;
}
inline const QString &
getContent() const
{
return this->content;
}
inline const std::chrono::time_point<std::chrono::system_clock> &
getParseTime() const
{
return this->parseTime;
}
std::vector<Word> &
getWords()
{
return words;
}
bool
getDisabled() const
{
return disabled;
}
const QString &
getId() const
{
return id;
}
bool getCanHighlightTab() const;
const QString &getTimeoutUser() const;
int getTimeoutCount() const;
const QString &getUserName() const;
const QString &getDisplayName() const;
const QString &getContent() const;
const std::chrono::time_point<std::chrono::system_clock> &getParseTime() const;
std::vector<Word> &getWords();
bool isDisabled() const;
const QString &getId() const;
private:
static LazyLoadedImage *badgeStaff;
@ -98,24 +49,18 @@ private:
static QRegularExpression *cheerRegex;
bool highlightTab = false;
QString timeoutUser = "";
int timeoutCount = 0;
bool disabled = false;
std::chrono::time_point<std::chrono::system_clock> parseTime;
bool _highlightTab = false;
QString _timeoutUser = "";
int _timeoutCount = 0;
bool _isDisabled = false;
std::chrono::time_point<std::chrono::system_clock> _parseTime;
QString userName = "";
QString displayName = "";
QString content;
QString id = "";
QString _userName = "";
QString _displayName = "";
QString _content;
QString _id = "";
std::vector<Word> words;
static QString matchLink(const QString &string);
static bool sortTwitchEmotes(
const std::pair<long int, LazyLoadedImage *> &a,
const std::pair<long int, LazyLoadedImage *> &b);
std::vector<Word> _words;
};
} // namespace messages

View file

@ -0,0 +1,54 @@
#include "messagebuilder.h"
#include "colorscheme.h"
#include "emotemanager.h"
#include "resources.h"
namespace chatterino {
namespace messages {
MessageBuilder::MessageBuilder()
: _words()
{
_parseTime = std::chrono::system_clock::now();
}
SharedMessage MessageBuilder::build()
{
return SharedMessage(new Message(_words));
}
void MessageBuilder::appendWord(const Word &word)
{
_words.push_back(word);
}
void MessageBuilder::appendTimestamp()
{
time_t t;
time(&t);
appendTimestamp(t);
}
void MessageBuilder::appendTimestamp(time_t time)
{
char timeStampBuffer[69];
strftime(timeStampBuffer, 69, "%H:%M", localtime(&time));
QString timestamp = QString(timeStampBuffer);
strftime(timeStampBuffer, 69, "%H:%M:%S", localtime(&time));
QString timestampWithSeconds = QString(timeStampBuffer);
appendWord(Word(timestamp, Word::TimestampNoSeconds,
ColorScheme::getInstance().SystemMessageColor, QString(), QString()));
appendWord(Word(timestampWithSeconds, Word::TimestampWithSeconds,
ColorScheme::getInstance().SystemMessageColor, QString(), QString()));
}
QString MessageBuilder::matchLink(const QString &string)
{
// TODO: Implement this xD
return QString();
}
}
}

29
messages/messagebuilder.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef MESSAGEBUILDER_H
#define MESSAGEBUILDER_H
#include "messages/message.h"
namespace chatterino {
namespace messages {
class MessageBuilder
{
public:
MessageBuilder();
SharedMessage build();
void appendWord(const Word &word);
void appendTimestamp();
void appendTimestamp(std::time_t time);
QString matchLink(const QString &string);
private:
std::vector<Word> _words;
std::chrono::time_point<std::chrono::system_clock> _parseTime;
};
}
}
#endif // MESSAGEBUILDER_H

View file

@ -0,0 +1,17 @@
#ifndef MESSAGEPARSEARGS_H
#define MESSAGEPARSEARGS_H
namespace chatterino {
namespace messages {
struct MessageParseArgs {
public:
bool disablePingSoungs = false;
bool isReceivedWhisper = false;
bool isSentWhisper = false;
bool includeChannelName = false;
};
}
}
#endif // MESSAGEPARSEARGS_H

View file

@ -1,6 +1,6 @@
#include "messageref.h"
#include "emotes.h"
#include "settings.h"
#include "emotemanager.h"
#include "settingsmanager.h"
#include <QDebug>
@ -9,82 +9,87 @@
#define MARGIN_TOP 8
#define MARGIN_BOTTOM 8
using namespace chatterino::messages;
namespace chatterino {
namespace messages {
MessageRef::MessageRef(std::shared_ptr<Message> message)
: message(message.get())
, messagePtr(message)
, wordParts()
MessageRef::MessageRef(SharedMessage message)
: _message(message)
, _wordParts()
, buffer()
{
}
bool
MessageRef::layout(int width, bool enableEmoteMargins)
Message *MessageRef::getMessage()
{
auto &settings = Settings::getInstance();
return _message.get();
}
bool sizeChanged = width != this->currentLayoutWidth;
bool redraw = width != this->currentLayoutWidth;
int MessageRef::getHeight() const
{
return _height;
}
bool MessageRef::layout(int width, bool enableEmoteMargins)
{
auto &settings = SettingsManager::getInstance();
bool sizeChanged = width != _currentLayoutWidth;
bool redraw = width != _currentLayoutWidth;
int spaceWidth = 4;
{
int mediumTextLineHeight =
Fonts::getFontMetrics(Fonts::Medium).height();
int mediumTextLineHeight =
FontManager::getInstance().getFontMetrics(FontManager::Medium).height();
bool recalculateImages =
this->emoteGeneration != Emotes::getGeneration();
bool recalculateText = this->fontGeneration != Fonts::getGeneration();
bool newWordTypes =
this->currentWordTypes != Settings::getInstance().getWordTypeMask();
bool recalculateImages = _emoteGeneration != EmoteManager::getInstance().getGeneration();
bool recalculateText = _fontGeneration != FontManager::getInstance().getGeneration();
bool newWordTypes = _currentWordTypes != SettingsManager::getInstance().getWordTypeMask();
qreal emoteScale = settings.emoteScale.get();
bool scaleEmotesByLineHeight = settings.scaleEmotesByLineHeight.get();
qreal emoteScale = settings.emoteScale.get();
bool scaleEmotesByLineHeight = settings.scaleEmotesByLineHeight.get();
if (recalculateImages || recalculateText || newWordTypes) {
this->emoteGeneration = Emotes::getGeneration();
this->fontGeneration = Fonts::getGeneration();
redraw = true;
for (auto &word : this->message->getWords()) {
if (word.isImage()) {
if (recalculateImages) {
auto &image = word.getImage();
qreal w = image.getWidth();
qreal h = image.getHeight();
if (scaleEmotesByLineHeight) {
word.setSize(
w * mediumTextLineHeight / h * emoteScale,
mediumTextLineHeight * emoteScale);
} else {
word.setSize(w * image.getScale() * emoteScale,
h * image.getScale() * emoteScale);
}
}
} else {
if (recalculateText) {
QFontMetrics &metrics = word.getFontMetrics();
word.setSize(metrics.width(word.getText()),
metrics.height());
}
}
}
}
if (newWordTypes) {
this->currentWordTypes = Settings::getInstance().getWordTypeMask();
}
}
if (!redraw) {
// calculate word sizes
if (!redraw && !recalculateImages && !recalculateText && !newWordTypes) {
return false;
}
this->currentLayoutWidth = width;
_emoteGeneration = EmoteManager::getInstance().getGeneration();
_fontGeneration = FontManager::getInstance().getGeneration();
for (auto &word : _message->getWords()) {
if (word.isImage()) {
if (!recalculateImages) {
continue;
}
auto &image = word.getImage();
qreal w = image.getWidth();
qreal h = image.getHeight();
if (scaleEmotesByLineHeight) {
word.setSize(w * mediumTextLineHeight / h * emoteScale,
mediumTextLineHeight * emoteScale);
} else {
word.setSize(w * image.getScale() * emoteScale, h * image.getScale() * emoteScale);
}
} else {
if (!recalculateText) {
continue;
}
QFontMetrics &metrics = word.getFontMetrics();
word.setSize(metrics.width(word.getText()), metrics.height());
}
}
if (newWordTypes) {
_currentWordTypes = SettingsManager::getInstance().getWordTypeMask();
}
// layout
_currentLayoutWidth = width;
int x = MARGIN_LEFT;
int y = MARGIN_TOP;
@ -96,12 +101,11 @@ MessageRef::layout(int width, bool enableEmoteMargins)
int lineHeight = 0;
bool first = true;
this->wordParts.clear();
_wordParts.clear();
uint32_t flags = Settings::getInstance().getWordTypeMask();
uint32_t flags = SettingsManager::getInstance().getWordTypeMask();
for (auto it = this->message->getWords().begin();
it != this->message->getWords().end(); ++it) {
for (auto it = _message->getWords().begin(); it != _message->getWords().end(); ++it) {
Word &word = *it;
if ((word.getType() & flags) == Word::None) {
@ -121,7 +125,7 @@ MessageRef::layout(int width, bool enableEmoteMargins)
// word wrapping
if (word.isText() && word.getWidth() + MARGIN_LEFT > right) {
this->alignWordParts(lineStart, lineHeight);
alignWordParts(lineStart, lineHeight);
y += lineHeight;
@ -144,9 +148,8 @@ MessageRef::layout(int width, bool enableEmoteMargins)
if ((width = width + charWidths[i - 1]) + MARGIN_LEFT > right) {
QString mid = text.mid(start, i - start - 1);
this->wordParts.push_back(WordPart(word, MARGIN_LEFT, y,
width, word.getHeight(),
lineNumber, mid, mid));
_wordParts.push_back(WordPart(word, MARGIN_LEFT, y, width, word.getHeight(),
lineNumber, mid, mid));
y += metrics.height();
@ -160,20 +163,19 @@ MessageRef::layout(int width, bool enableEmoteMargins)
QString mid(text.mid(start));
width = metrics.width(mid);
this->wordParts.push_back(
WordPart(word, MARGIN_LEFT, y - word.getHeight(), width,
word.getHeight(), lineNumber, mid, mid));
_wordParts.push_back(WordPart(word, MARGIN_LEFT, y - word.getHeight(), width,
word.getHeight(), lineNumber, mid, mid));
x = width + MARGIN_LEFT + spaceWidth;
lineHeight = word.getHeight();
lineStart = this->wordParts.size() - 1;
lineStart = _wordParts.size() - 1;
first = false;
} else if (first || x + word.getWidth() + xOffset <= right) {
// fits in the line
this->wordParts.push_back(WordPart(word, x, y - word.getHeight(),
lineNumber, word.getCopyText()));
_wordParts.push_back(
WordPart(word, x, y - word.getHeight(), lineNumber, word.getCopyText()));
x += word.getWidth() + xOffset;
x += spaceWidth;
@ -183,15 +185,14 @@ MessageRef::layout(int width, bool enableEmoteMargins)
first = false;
} else {
// doesn't fit in the line
this->alignWordParts(lineStart, lineHeight);
alignWordParts(lineStart, lineHeight);
y += lineHeight;
this->wordParts.push_back(WordPart(word, MARGIN_LEFT,
y - word.getHeight(), lineNumber,
word.getCopyText()));
_wordParts.push_back(
WordPart(word, MARGIN_LEFT, y - word.getHeight(), lineNumber, word.getCopyText()));
lineStart = this->wordParts.size() - 1;
lineStart = _wordParts.size() - 1;
lineHeight = word.getHeight();
@ -202,36 +203,40 @@ MessageRef::layout(int width, bool enableEmoteMargins)
}
}
this->alignWordParts(lineStart, lineHeight);
alignWordParts(lineStart, lineHeight);
if (this->height != y + lineHeight) {
if (_height != y + lineHeight) {
sizeChanged = true;
this->height = y + lineHeight;
_height = y + lineHeight;
}
if (sizeChanged) {
this->buffer = nullptr;
buffer = nullptr;
}
this->updateBuffer = true;
updateBuffer = true;
return true;
}
void
MessageRef::alignWordParts(int lineStart, int lineHeight)
const std::vector<WordPart> &MessageRef::getWordParts() const
{
for (size_t i = lineStart; i < this->wordParts.size(); i++) {
WordPart &wordPart2 = this->wordParts.at(i);
return _wordParts;
}
void MessageRef::alignWordParts(int lineStart, int lineHeight)
{
for (size_t i = lineStart; i < _wordParts.size(); i++) {
WordPart &wordPart2 = _wordParts.at(i);
wordPart2.setY(wordPart2.getY() + lineHeight);
}
}
bool
MessageRef::tryGetWordPart(QPoint point, messages::Word &word)
bool MessageRef::tryGetWordPart(QPoint point, Word &word)
{
for (messages::WordPart &wordPart : this->wordParts) {
// go through all words and return the first one that contains the point.
for (WordPart &wordPart : _wordParts) {
if (wordPart.getRect().contains(point)) {
word = wordPart.getWord();
return true;
@ -241,18 +246,17 @@ MessageRef::tryGetWordPart(QPoint point, messages::Word &word)
return false;
}
int
MessageRef::getSelectionIndex(QPoint position)
int MessageRef::getSelectionIndex(QPoint position)
{
if (this->wordParts.size() == 0) {
if (_wordParts.size() == 0) {
return 0;
}
// find out in which line the cursor is
int lineNumber = 0, lineStart = 0, lineEnd = 0;
for (int i = 0; i < this->wordParts.size(); i++) {
WordPart &part = this->wordParts[i];
for (int i = 0; i < _wordParts.size(); i++) {
WordPart &part = _wordParts[i];
// return if curser under the word
if (position.y() >= part.getBottom()) {
@ -271,13 +275,13 @@ MessageRef::getSelectionIndex(QPoint position)
int index = 0;
for (int i = 0; i < lineStart; i++) {
WordPart &part = this->wordParts[i];
WordPart &part = _wordParts[i];
index += part.getWord().isImage() ? 2 : part.getText().length() + 1;
}
for (int i = lineStart; i < lineEnd; i++) {
WordPart &part = this->wordParts[i];
WordPart &part = _wordParts[i];
// curser is left of the word part
if (position.x() < part.getX()) {
@ -304,8 +308,7 @@ MessageRef::getSelectionIndex(QPoint position)
}
index++;
x = part.getX() +
part.getWord().getFontMetrics().width(text, j + 1);
x = part.getX() + part.getWord().getFontMetrics().width(text, j + 1);
}
}
@ -315,9 +318,9 @@ MessageRef::getSelectionIndex(QPoint position)
return index;
// go through all the wordparts
// for (int i = 0; i < this->wordParts; i < this->wordParts.size()) {
// for (int i = 0; i < wordParts; i < wordParts.size()) {
// WordPart &part = this->wordParts[i];
// WordPart &part = wordParts[i];
// // return if curser under the word
// if (position.y() >= part.getBottom()) {

View file

@ -9,30 +9,21 @@
namespace chatterino {
namespace messages {
class MessageRef;
typedef std::shared_ptr<MessageRef> SharedMessageRef;
class MessageRef
{
public:
MessageRef(std::shared_ptr<Message> message);
MessageRef(SharedMessage message);
Message *
getMessage()
{
return this->message;
}
int
getHeight() const
{
return height;
}
Message *getMessage();
int getHeight() const;
bool layout(int width, bool enableEmoteMargins = true);
const std::vector<WordPart> &
getWordParts() const
{
return wordParts;
}
const std::vector<WordPart> &getWordParts() const;
std::shared_ptr<QPixmap> buffer = nullptr;
bool updateBuffer = false;
@ -42,18 +33,18 @@ public:
int getSelectionIndex(QPoint position);
private:
Message *message;
std::shared_ptr<Message> messagePtr;
// variables
SharedMessage _message;
std::vector<messages::WordPart> _wordParts;
std::vector<messages::WordPart> wordParts;
int _height = 0;
int height = 0;
int currentLayoutWidth = -1;
int fontGeneration = -1;
int emoteGeneration = -1;
Word::Type currentWordTypes = Word::None;
int _currentLayoutWidth = -1;
int _fontGeneration = -1;
int _emoteGeneration = -1;
Word::Type _currentWordTypes = Word::None;
// methods
void alignWordParts(int lineStart, int lineHeight);
};
}

View file

@ -4,35 +4,131 @@ namespace chatterino {
namespace messages {
// Image word
Word::Word(LazyLoadedImage *image, Type type, const QString &copytext,
const QString &tooltip, const Link &link)
: image(image)
, text()
, color()
Word::Word(LazyLoadedImage *image, Type type, const QString &copytext, const QString &tooltip,
const Link &link)
: _image(image)
, _text()
, _color()
, _isImage(true)
, type(type)
, copyText(copytext)
, tooltip(tooltip)
, link(link)
, characterWidthCache()
, _type(type)
, _copyText(copytext)
, _tooltip(tooltip)
, _link(link)
, _characterWidthCache()
{
image->getWidth(); // professional segfault test
}
// Text word
Word::Word(const QString &text, Type type, const QColor &color,
const QString &copytext, const QString &tooltip, const Link &link)
: image(NULL)
, text(text)
, color(color)
Word::Word(const QString &text, Type type, const QColor &color, const QString &copytext,
const QString &tooltip, const Link &link)
: _image(NULL)
, _text(text)
, _color(color)
, _isImage(false)
, type(type)
, copyText(copytext)
, tooltip(tooltip)
, link(link)
, characterWidthCache()
, _type(type)
, _copyText(copytext)
, _tooltip(tooltip)
, _link(link)
, _characterWidthCache()
{
}
LazyLoadedImage &Word::getImage() const
{
return *_image;
}
const QString &Word::getText() const
{
return _text;
}
int Word::getWidth() const
{
return _width;
}
int Word::getHeight() const
{
return _height;
}
void Word::setSize(int width, int height)
{
_width = width;
_height = height;
}
bool Word::isImage() const
{
return _isImage;
}
bool Word::isText() const
{
return !_isImage;
}
const QString &Word::getCopyText() const
{
return _copyText;
}
bool Word::hasTrailingSpace() const
{
return _hasTrailingSpace;
}
QFont &Word::getFont() const
{
return FontManager::getInstance().getFont(_font);
}
QFontMetrics &Word::getFontMetrics() const
{
return FontManager::getInstance().getFontMetrics(_font);
}
Word::Type Word::getType() const
{
return _type;
}
const QString &Word::getTooltip() const
{
return _tooltip;
}
const QColor &Word::getColor() const
{
return _color;
}
const Link &Word::getLink() const
{
return _link;
}
int Word::getXOffset() const
{
return _xOffset;
}
int Word::getYOffset() const
{
return _yOffset;
}
void Word::setOffset(int xOffset, int yOffset)
{
_xOffset = std::max(0, xOffset);
_yOffset = std::max(0, yOffset);
}
std::vector<short> &Word::getCharacterWidthCache() const
{
return _characterWidthCache;
}
} // namespace messages
} // namespace chatterino

View file

@ -1,7 +1,7 @@
#ifndef WORD_H
#define WORD_H
#include "fonts.h"
#include "fontmanager.h"
#include "messages/lazyloadedimage.h"
#include "messages/link.h"
@ -31,8 +31,7 @@ public:
BttvGifEmoteText = (1 << 9),
FfzEmoteImage = (1 << 10),
FfzEmoteText = (1 << 11),
EmoteImages = TwitchEmoteImage | BttvEmoteImage | BttvGifEmoteImage |
FfzEmoteImage,
EmoteImages = TwitchEmoteImage | BttvEmoteImage | BttvGifEmoteImage | FfzEmoteImage,
BitsStatic = (1 << 12),
BitsAnimated = (1 << 13),
@ -46,9 +45,8 @@ public:
BadgePremium = (1 << 20),
BadgeChatterino = (1 << 21),
BadgeCheer = (1 << 22),
Badges = BadgeStaff | BadgeAdmin | BadgeGlobalMod | BadgeModerator |
BadgeTurbo | BadgeBroadcaster | BadgePremium |
BadgeChatterino | BadgeCheer,
Badges = BadgeStaff | BadgeAdmin | BadgeGlobalMod | BadgeModerator | BadgeTurbo |
BadgeBroadcaster | BadgePremium | BadgeChatterino | BadgeCheer,
Username = (1 << 23),
BitsAmount = (1 << 24),
@ -59,157 +57,61 @@ public:
EmojiImage = (1 << 27),
EmojiText = (1 << 28),
Default = TimestampNoSeconds | Badges | Username | BitsStatic |
FfzEmoteImage | BttvEmoteImage | BttvGifEmoteImage |
TwitchEmoteImage | BitsAmount | Text | ButtonBan |
ButtonTimeout
Default = TimestampNoSeconds | Badges | Username | BitsStatic | FfzEmoteImage |
BttvEmoteImage | BttvGifEmoteImage | TwitchEmoteImage | BitsAmount | Text |
ButtonBan | ButtonTimeout
};
Word()
{
}
explicit Word(LazyLoadedImage *image, Type getType, const QString &copytext,
explicit Word(LazyLoadedImage *_image, Type getType, const QString &copytext,
const QString &getTooltip, const Link &getLink = Link());
explicit Word(const QString &text, Type getType, const QColor &getColor,
const QString &copytext, const QString &getTooltip,
const Link &getLink = Link());
explicit Word(const QString &_text, Type getType, const QColor &getColor,
const QString &copytext, const QString &getTooltip, const Link &getLink = Link());
LazyLoadedImage &
getImage() const
{
return *image;
}
LazyLoadedImage &getImage() const;
const QString &getText() const;
int getWidth() const;
int getHeight() const;
void setSize(int _width, int _height);
const QString &
getText() const
{
return this->text;
}
bool isImage() const;
bool isText() const;
const QString &getCopyText() const;
bool hasTrailingSpace() const;
QFont &getFont() const;
QFontMetrics &getFontMetrics() const;
Type getType() const;
const QString &getTooltip() const;
const QColor &getColor() const;
const Link &getLink() const;
int getXOffset() const;
int getYOffset() const;
void setOffset(int _xOffset, int _yOffset);
int
getWidth() const
{
return this->width;
}
int
getHeight() const
{
return this->height;
}
void
setSize(int width, int height)
{
this->width = width;
this->height = height;
}
bool
isImage() const
{
return this->_isImage;
}
bool
isText() const
{
return !this->_isImage;
}
const QString &
getCopyText() const
{
return this->copyText;
}
bool
hasTrailingSpace() const
{
return this->_hasTrailingSpace;
}
QFont &
getFont() const
{
return Fonts::getFont(this->font);
}
QFontMetrics &
getFontMetrics() const
{
return Fonts::getFontMetrics(this->font);
}
Type
getType() const
{
return this->type;
}
const QString &
getTooltip() const
{
return this->tooltip;
}
const QColor &
getColor() const
{
return this->color;
}
const Link &
getLink() const
{
return this->link;
}
int
getXOffset() const
{
return this->xOffset;
}
int
getYOffset() const
{
return this->yOffset;
}
void
setOffset(int xOffset, int yOffset)
{
this->xOffset = std::max(0, xOffset);
this->yOffset = std::max(0, yOffset);
}
std::vector<short> &
getCharacterWidthCache() const
{
return this->characterWidthCache;
}
std::vector<short> &getCharacterWidthCache() const;
private:
LazyLoadedImage *image;
QString text;
QColor color;
LazyLoadedImage *_image;
QString _text;
QColor _color;
bool _isImage;
Type type;
QString copyText;
QString tooltip;
Type _type;
QString _copyText;
QString _tooltip;
int width = 16;
int height = 16;
int xOffset = 0;
int yOffset = 0;
int _width = 16;
int _height = 16;
int _xOffset = 0;
int _yOffset = 0;
bool _hasTrailingSpace;
Fonts::Type font = Fonts::Medium;
Link link;
FontManager::Type _font = FontManager::Medium;
Link _link;
mutable std::vector<short> characterWidthCache;
mutable std::vector<short> _characterWidthCache;
};
} // namespace messages

View file

@ -4,33 +4,103 @@
namespace chatterino {
namespace messages {
WordPart::WordPart(Word &word, int x, int y, int lineNumber,
const QString &copyText, bool allowTrailingSpace)
: m_word(word)
, copyText(copyText)
, text(word.isText() ? m_word.getText() : QString())
, x(x)
, y(y)
, width(word.getWidth())
, height(word.getHeight())
, lineNumber(lineNumber)
WordPart::WordPart(Word &word, int x, int y, int lineNumber, const QString &copyText,
bool allowTrailingSpace)
: _word(word)
, _copyText(copyText)
, _text(word.isText() ? _word.getText() : QString())
, _x(x)
, _y(y)
, _width(word.getWidth())
, _height(word.getHeight())
, _lineNumber(lineNumber)
, _trailingSpace(word.hasTrailingSpace() & allowTrailingSpace)
{
}
WordPart::WordPart(Word &word, int x, int y, int width, int height,
int lineNumber, const QString &copyText,
const QString &customText, bool allowTrailingSpace)
: m_word(word)
, copyText(copyText)
, text(customText)
, x(x)
, y(y)
, width(width)
, height(height)
, lineNumber(lineNumber)
WordPart::WordPart(Word &word, int x, int y, int width, int height, int lineNumber,
const QString &copyText, const QString &customText, bool allowTrailingSpace)
: _word(word)
, _copyText(copyText)
, _text(customText)
, _x(x)
, _y(y)
, _width(width)
, _height(height)
, _lineNumber(lineNumber)
, _trailingSpace(word.hasTrailingSpace() & allowTrailingSpace)
{
}
const Word &WordPart::getWord() const
{
return _word;
}
int WordPart::getWidth() const
{
return _width;
}
int WordPart::getHeight() const
{
return _height;
}
int WordPart::getX() const
{
return _x;
}
int WordPart::getY() const
{
return _y;
}
void WordPart::setPosition(int x, int y)
{
_x = x;
_y = y;
}
void WordPart::setY(int y)
{
_y = y;
}
int WordPart::getRight() const
{
return _x + _width;
}
int WordPart::getBottom() const
{
return _y + _height;
}
QRect WordPart::getRect() const
{
return QRect(_x, _y, _width, _height);
}
const QString WordPart::getCopyText() const
{
return _copyText;
}
int WordPart::hasTrailingSpace() const
{
return _trailingSpace;
}
const QString &WordPart::getText() const
{
return _text;
}
int WordPart::getLineNumber()
{
return _lineNumber;
}
}
}

View file

@ -12,110 +12,39 @@ class Word;
class WordPart
{
public:
WordPart(Word &getWord, int getX, int getY, int lineNumber,
const QString &getCopyText, bool allowTrailingSpace = true);
WordPart(Word &getWord, int getX, int getY, int _lineNumber, const QString &getCopyText,
bool allowTrailingSpace = true);
WordPart(Word &getWord, int getX, int getY, int getWidth, int getHeight,
int lineNumber, const QString &getCopyText,
const QString &customText, bool allowTrailingSpace = true);
WordPart(Word &getWord, int getX, int getY, int getWidth, int getHeight, int _lineNumber,
const QString &getCopyText, const QString &customText, bool allowTrailingSpace = true);
const Word &
getWord() const
{
return this->m_word;
}
int
getWidth() const
{
return this->width;
}
int
getHeight() const
{
return this->height;
}
int
getX() const
{
return this->x;
}
int
getY() const
{
return this->y;
}
void
setPosition(int x, int y)
{
this->x = x;
this->y = y;
}
void
setY(int y)
{
this->y = y;
}
int
getRight() const
{
return this->x + this->width;
}
int
getBottom() const
{
return this->y + this->height;
}
QRect
getRect() const
{
return QRect(this->x, this->y, this->width, this->height);
}
const QString
getCopyText() const
{
return this->copyText;
}
int
hasTrailingSpace() const
{
return this->_trailingSpace;
}
const QString &
getText() const
{
return this->text;
}
int
getLineNumber()
{
return this->lineNumber;
}
const Word &getWord() const;
int getWidth() const;
int getHeight() const;
int getX() const;
int getY() const;
void setPosition(int _x, int _y);
void setY(int _y);
int getRight() const;
int getBottom() const;
QRect getRect() const;
const QString getCopyText() const;
int hasTrailingSpace() const;
const QString &getText() const;
int getLineNumber();
private:
Word &m_word;
Word &_word;
QString copyText;
QString text;
QString _copyText;
QString _text;
int x;
int y;
int width;
int height;
int _x;
int _y;
int _width;
int _height;
int lineNumber;
int _lineNumber;
bool _trailingSpace;
};

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Ian Bannerman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,555 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
/* */
/* */
/* File is originally from
* https://github.com/qtproject/qt-solutions/tree/master/qtwinmigrate/src */
/* */
/* It has been modified to support borderless window (HTTRANSPARENT) & to remove
* pre Qt5 cruft */
/* */
/* */
#include "QWinWidget.h"
#include <qt_windows.h>
#include <QApplication>
#include <QEvent>
#include <QFocusEvent>
#include <QWindow>
/*!
\class QWinWidget qwinwidget.h
\brief The QWinWidget class is a Qt widget that can be child of a
native Win32 widget.
The QWinWidget class is the bridge between an existing application
user interface developed using native Win32 APIs or toolkits like
MFC, and Qt based GUI elements.
Using QWinWidget as the parent of QDialogs will ensure that
modality, placement and stacking works properly throughout the
entire application. If the child widget is a top level window that
uses the \c WDestructiveClose flag, QWinWidget will destroy itself
when the child window closes down.
Applications moving to Qt can use QWinWidget to add new
functionality, and gradually replace the existing interface.
*/
QWinWidget::QWinWidget()
: QWidget(nullptr)
, m_Layout()
, p_Widget(nullptr)
, m_ParentNativeWindowHandle(nullptr)
, _prevFocus(nullptr)
, _reenableParent(false)
{
// Create a native window and give it geometry values * devicePixelRatio for
// HiDPI support
p_ParentWinNativeWindow = new WinNativeWindow(
1 * window()->devicePixelRatio(), 1 * window()->devicePixelRatio(),
1 * window()->devicePixelRatio(), 1 * window()->devicePixelRatio());
// If you want to set a minimize size for your app, do so here
// p_ParentWinNativeWindow->setMinimumSize(1024 *
// window()->devicePixelRatio(), 768 * window()->devicePixelRatio());
// If you want to set a maximum size for your app, do so here
// p_ParentWinNativeWindow->setMaximumSize(1024 *
// window()->devicePixelRatio(), 768 * window()->devicePixelRatio());
// Save the native window handle for shorthand use
m_ParentNativeWindowHandle = p_ParentWinNativeWindow->hWnd;
Q_ASSERT(m_ParentNativeWindowHandle);
// Create the child window & embed it into the native one
if (m_ParentNativeWindowHandle) {
SetWindowLong((HWND)winId(), GWL_STYLE,
WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
QWindow *window = windowHandle();
window->setProperty("_q_embedded_native_parent_handle",
(WId)m_ParentNativeWindowHandle);
SetParent((HWND)winId(), m_ParentNativeWindowHandle);
window->setFlags(Qt::FramelessWindowHint);
QEvent e(QEvent::EmbeddingControl);
QApplication::sendEvent(this, &e);
}
// Pass along our window handle & widget pointer to WinFramelessWidget so we
// can exchange messages
p_ParentWinNativeWindow->childWindow = (HWND)winId();
p_ParentWinNativeWindow->childWidget = this;
// Clear margins & spacing & add the layout to prepare for the MainAppWidget
setContentsMargins(0, 0, 0, 0);
setLayout(&m_Layout);
m_Layout.setContentsMargins(0, 0, 0, 0);
m_Layout.setSpacing(0);
// Create the true app widget
// p_Widget = new Widget(this);
// m_Layout.addWidget(p_Widget);
// p_Widget->setParent(this, Qt::Widget);
// p_Widget->setVisible(true);
// Update the BORDERWIDTH value if needed for HiDPI displays
BORDERWIDTH = BORDERWIDTH * window()->devicePixelRatio();
// Update the TOOLBARHEIGHT value to match the height of toolBar * if
// needed, the HiDPI display
// if (p_Widget->toolBar) {
// TOOLBARHEIGHT =
// p_Widget->toolBar->height() * window()->devicePixelRatio();
// }
// You need to keep the native window in sync with the Qt window & children,
// so wire min/max/close buttons to
// slots inside of QWinWidget. QWinWidget can then talk with the native
// window as needed
// if (p_Widget->minimizeButton) {
// connect(p_Widget->minimizeButton, &QPushButton::clicked, this,
// &QWinWidget::onMinimizeButtonClicked);
// }
// if (p_Widget->maximizeButton) {
// connect(p_Widget->maximizeButton, &QPushButton::clicked, this,
// &QWinWidget::onMaximizeButtonClicked);
// }
// if (p_Widget->closeButton) {
// connect(p_Widget->closeButton, &QPushButton::clicked, this,
// &QWinWidget::onCloseButtonClicked);
// }
// Send the parent native window a WM_SIZE message to update the widget size
SendMessage(m_ParentNativeWindowHandle, WM_SIZE, 0, 0);
}
/*!
Destroys this object, freeing all allocated resources.
*/
QWinWidget::~QWinWidget()
{
}
/*!
Returns the handle of the native Win32 parent window.
*/
HWND
QWinWidget::getParentWindow() const
{
return m_ParentNativeWindowHandle;
}
/*!
\reimp
*/
void
QWinWidget::childEvent(QChildEvent *e)
{
QObject *obj = e->child();
if (obj->isWidgetType()) {
if (e->added()) {
if (obj->isWidgetType()) {
obj->installEventFilter(this);
}
} else if (e->removed() && _reenableParent) {
_reenableParent = false;
EnableWindow(m_ParentNativeWindowHandle, true);
obj->removeEventFilter(this);
}
}
QWidget::childEvent(e);
}
/*! \internal */
void
QWinWidget::saveFocus()
{
if (!_prevFocus)
_prevFocus = ::GetFocus();
if (!_prevFocus)
_prevFocus = getParentWindow();
}
/*!
Shows this widget. Overrides QWidget::show().
\sa showCentered()
*/
void
QWinWidget::show()
{
ShowWindow(m_ParentNativeWindowHandle, true);
saveFocus();
QWidget::show();
}
/*!
Centers this widget over the native parent window. Use this
function to have Qt toplevel windows (i.e. dialogs) positioned
correctly over their native parent windows.
\code
QWinWidget qwin(hParent);
qwin.center();
QMessageBox::information(&qwin, "Caption", "Information Text");
\endcode
This will center the message box over the client area of hParent.
*/
void
QWinWidget::center()
{
const QWidget *child = findChild<QWidget *>();
if (child && !child->isWindow()) {
qWarning("QWinWidget::center: Call this function only for QWinWidgets "
"with toplevel children");
}
RECT r;
GetWindowRect(m_ParentNativeWindowHandle, &r);
setGeometry((r.right - r.left) / 2 + r.left, (r.bottom - r.top) / 2 + r.top,
0, 0);
}
/*!
\obsolete
Call center() instead.
*/
void
QWinWidget::showCentered()
{
center();
show();
}
void
QWinWidget::setGeometry(int x, int y, int w, int h)
{
p_ParentWinNativeWindow->setGeometry(
x * window()->devicePixelRatio(), y * window()->devicePixelRatio(),
w * window()->devicePixelRatio(), h * window()->devicePixelRatio());
}
/*!
Sets the focus to the window that had the focus before this widget
was shown, or if there was no previous window, sets the focus to
the parent window.
*/
void
QWinWidget::resetFocus()
{
if (_prevFocus)
::SetFocus(_prevFocus);
else
::SetFocus(getParentWindow());
}
// Tell the parent native window to minimize
void
QWinWidget::onMinimizeButtonClicked()
{
SendMessage(m_ParentNativeWindowHandle, WM_SYSCOMMAND, SC_MINIMIZE, 0);
}
// Tell the parent native window to maximize or restore as appropriate
void
QWinWidget::onMaximizeButtonClicked()
{
if (p_Widget->maximizeButton->isChecked()) {
SendMessage(m_ParentNativeWindowHandle, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
} else {
SendMessage(m_ParentNativeWindowHandle, WM_SYSCOMMAND, SC_RESTORE, 0);
}
}
void
QWinWidget::onCloseButtonClicked()
{
if (true /* put your check for it if it safe to close your app here */) // eg, does the user need to save a document
{
// Safe to close, so hide the parent window
ShowWindow(m_ParentNativeWindowHandle, false);
// And then quit
QApplication::quit();
} else {
// Do nothing, and thus, don't actually close the window
}
}
bool
QWinWidget::nativeEvent(const QByteArray &, void *message, long *result)
{
MSG *msg = (MSG *)message;
if (msg->message == WM_SETFOCUS) {
Qt::FocusReason reason;
if (::GetKeyState(VK_LBUTTON) < 0 || ::GetKeyState(VK_RBUTTON) < 0)
reason = Qt::MouseFocusReason;
else if (::GetKeyState(VK_SHIFT) < 0)
reason = Qt::BacktabFocusReason;
else
reason = Qt::TabFocusReason;
QFocusEvent e(QEvent::FocusIn, reason);
QApplication::sendEvent(this, &e);
}
// Only close if safeToClose clears()
if (msg->message == WM_CLOSE) {
if (true /* put your check for it if it safe to close your app here */) // eg, does the user need to save a document
{
// Safe to close, so hide the parent window
ShowWindow(m_ParentNativeWindowHandle, false);
// And then quit
QApplication::quit();
} else {
*result = 0; // Set the message to 0 to ignore it, and thus, don't
// actually close
return true;
}
}
// Double check WM_SIZE messages to see if the parent native window is
// maximized
if (msg->message == WM_SIZE) {
if (p_Widget && p_Widget->maximizeButton) {
// Get the window state
WINDOWPLACEMENT wp;
GetWindowPlacement(m_ParentNativeWindowHandle, &wp);
// If we're maximized,
if (wp.showCmd == SW_MAXIMIZE) {
// Maximize button should show as Restore
p_Widget->maximizeButton->setChecked(true);
} else {
// Maximize button should show as Maximize
p_Widget->maximizeButton->setChecked(false);
}
}
}
// Pass NCHITTESTS on the window edges as determined by BORDERWIDTH &
// TOOLBARHEIGHT through to the parent native window
if (msg->message == WM_NCHITTEST) {
RECT WindowRect;
int x, y;
GetWindowRect(msg->hwnd, &WindowRect);
x = GET_X_LPARAM(msg->lParam) - WindowRect.left;
y = GET_Y_LPARAM(msg->lParam) - WindowRect.top;
if (x >= BORDERWIDTH &&
x <= WindowRect.right - WindowRect.left - BORDERWIDTH &&
y >= BORDERWIDTH && y <= TOOLBARHEIGHT) {
if (false) { // if (p_Widget->toolBar) {
// If the mouse is over top of the toolbar area BUT is actually
// positioned over a child widget of the toolbar,
// Then we don't want to enable dragging. This allows for
// buttons in the toolbar, eg, a Maximize button, to keep the
// mouse interaction
if (QApplication::widgetAt(QCursor::pos()) != p_Widget->toolBar)
return false;
} else {
// The mouse is over the toolbar area & is NOT over a child of
// the toolbar, so pass this message
// through to the native window for HTCAPTION dragging
*result = HTTRANSPARENT;
return true;
}
} else if (x < BORDERWIDTH && y < BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x > WindowRect.right - WindowRect.left - BORDERWIDTH &&
y < BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x > WindowRect.right - WindowRect.left - BORDERWIDTH &&
y > WindowRect.bottom - WindowRect.top - BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x < BORDERWIDTH &&
y > WindowRect.bottom - WindowRect.top - BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x < BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (y < BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x > WindowRect.right - WindowRect.left - BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (y > WindowRect.bottom - WindowRect.top - BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
}
return false;
}
return false;
}
/*!
\reimp
*/
bool
QWinWidget::eventFilter(QObject *o, QEvent *e)
{
QWidget *w = (QWidget *)o;
switch (e->type()) {
case QEvent::WindowDeactivate:
if (w->isModal() && w->isHidden())
BringWindowToTop(m_ParentNativeWindowHandle);
break;
case QEvent::Hide:
if (_reenableParent) {
EnableWindow(m_ParentNativeWindowHandle, true);
_reenableParent = false;
}
resetFocus();
if (w->testAttribute(Qt::WA_DeleteOnClose) && w->isWindow())
deleteLater();
break;
case QEvent::Show:
if (w->isWindow()) {
saveFocus();
hide();
if (w->isModal() && !_reenableParent) {
EnableWindow(m_ParentNativeWindowHandle, false);
_reenableParent = true;
}
}
break;
case QEvent::Close: {
::SetActiveWindow(m_ParentNativeWindowHandle);
if (w->testAttribute(Qt::WA_DeleteOnClose))
deleteLater();
break;
}
default:
break;
}
return QWidget::eventFilter(o, e);
}
/*! \reimp
*/
void
QWinWidget::focusInEvent(QFocusEvent *e)
{
QWidget *candidate = this;
switch (e->reason()) {
case Qt::TabFocusReason:
case Qt::BacktabFocusReason:
while (!(candidate->focusPolicy() & Qt::TabFocus)) {
candidate = candidate->nextInFocusChain();
if (candidate == this) {
candidate = 0;
break;
}
}
if (candidate) {
candidate->setFocus(e->reason());
if (e->reason() == Qt::BacktabFocusReason ||
e->reason() == Qt::TabFocusReason) {
candidate->setAttribute(Qt::WA_KeyboardFocusChange);
candidate->window()->setAttribute(
Qt::WA_KeyboardFocusChange);
}
if (e->reason() == Qt::BacktabFocusReason)
QWidget::focusNextPrevChild(false);
}
break;
default:
break;
}
}
/*! \reimp
*/
bool
QWinWidget::focusNextPrevChild(bool next)
{
QWidget *curFocus = focusWidget();
if (!next) {
if (!curFocus->isWindow()) {
QWidget *nextFocus = curFocus->nextInFocusChain();
QWidget *prevFocus = 0;
QWidget *topLevel = 0;
while (nextFocus != curFocus) {
if (nextFocus->focusPolicy() & Qt::TabFocus) {
prevFocus = nextFocus;
topLevel = 0;
}
nextFocus = nextFocus->nextInFocusChain();
}
if (!topLevel) {
return QWidget::focusNextPrevChild(false);
}
}
} else {
QWidget *nextFocus = curFocus;
while (1 && nextFocus != 0) {
nextFocus = nextFocus->nextInFocusChain();
if (nextFocus->focusPolicy() & Qt::TabFocus) {
return QWidget::focusNextPrevChild(true);
}
}
}
::SetFocus(m_ParentNativeWindowHandle);
return true;
}

View file

@ -0,0 +1,109 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
/* */
/* File is originally from
* https://github.com/qtproject/qt-solutions/tree/master/qtwinmigrate/src */
/* */
/* It has been modified to support borderless window (HTTTRANSPARENT) & to
* remove pre Qt5 cruft */
/* */
/* */
// Declaration of the QWinWidget classes
#ifndef QWINWIDGET_H
#define QWINWIDGET_H
#include <QVBoxLayout>
#include <QWidget>
#include "WinNativeWindow.h"
#include "widget.h"
class QWinWidget : public QWidget
{
Q_OBJECT
public:
QWinWidget();
~QWinWidget();
void show();
void center();
void showCentered();
void setGeometry(int x, int y, int w, int h);
HWND getParentWindow() const;
public slots:
void onMaximizeButtonClicked();
void onMinimizeButtonClicked();
void onCloseButtonClicked();
protected:
void childEvent(QChildEvent *e) override;
bool eventFilter(QObject *o, QEvent *e) override;
bool focusNextPrevChild(bool next) override;
void focusInEvent(QFocusEvent *e) override;
bool nativeEvent(const QByteArray &eventType, void *message, long *result) override;
private:
QVBoxLayout m_Layout;
Widget *p_Widget;
WinNativeWindow *p_ParentWinNativeWindow;
HWND m_ParentNativeWindowHandle;
HWND _prevFocus;
bool _reenableParent;
int BORDERWIDTH = 6; // Adjust this as you wish for # of pixels on the
// edges to show resize handles
int TOOLBARHEIGHT = 40; // Adjust this as you wish for # of pixels from the
// top to allow dragging the window
void saveFocus();
void resetFocus();
};
#endif // QWINWIDGET_H

View file

@ -0,0 +1,11 @@
#include "widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
// Set a black background for funsies
QPalette Pal(palette());
Pal.setColor(QPalette::Background, Qt::blue);
setAutoFillBackground(true);
setPalette(Pal);
}

View file

@ -0,0 +1,28 @@
#ifndef WIDGET_H
#define WIDGET_H
#include <QPushButton>
#include <QToolBar>
#include <QWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
// If you want to have Max/Min/Close buttons, look at how QWinWidget uses these
QPushButton *maximizeButton = nullptr;
QPushButton *minimizeButton = nullptr;
QPushButton *closeButton = nullptr;
// If you want to enable dragging the window when the mouse is over top of, say, a QToolBar,
// then look at how QWinWidget uses this
QToolBar *toolBar = nullptr;
signals:
public slots:
};
#endif // WIDGET_H

View file

@ -0,0 +1,235 @@
#include "WinNativeWindow.h"
#include <dwmapi.h>
#include <stdexcept>
HWND WinNativeWindow::childWindow = nullptr;
QWidget *WinNativeWindow::childWidget = nullptr;
WinNativeWindow::WinNativeWindow(const int x, const int y, const int width, const int height)
: hWnd(nullptr)
{
// The native window technically has a background color. You can set it here
HBRUSH windowBackground = CreateSolidBrush(RGB(255, 255, 255));
HINSTANCE hInstance = GetModuleHandle(nullptr);
WNDCLASSEX wcx = {0};
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.style = CS_HREDRAW | CS_VREDRAW | CS_DROPSHADOW;
wcx.hInstance = hInstance;
wcx.lpfnWndProc = WndProc;
wcx.cbClsExtra = 0;
wcx.cbWndExtra = 0;
wcx.lpszClassName = L"WindowClass";
wcx.hbrBackground = windowBackground;
wcx.hCursor = LoadCursor(hInstance, IDC_ARROW);
RegisterClassEx(&wcx);
if (FAILED(RegisterClassEx(&wcx))) {
throw std::runtime_error("Couldn't register window class");
}
// Create a native window with the appropriate style
hWnd = CreateWindow(L"WindowClass", L"WindowTitle", aero_borderless, x, y, width, height, 0, 0,
hInstance, nullptr);
if (!hWnd) {
throw std::runtime_error("couldn't create window because of reasons");
}
SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
// This code may be required for aero shadows on some versions of Windows
// const MARGINS aero_shadow_on = { 1, 1, 1, 1 };
// DwmExtendFrameIntoClientArea(hWnd, &aero_shadow_on);
SetWindowPos(hWnd, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
}
WinNativeWindow::~WinNativeWindow()
{
// Hide the window & send the destroy message
ShowWindow(hWnd, SW_HIDE);
DestroyWindow(hWnd);
}
LRESULT CALLBACK WinNativeWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
WinNativeWindow *window =
reinterpret_cast<WinNativeWindow *>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
if (!window) {
return DefWindowProc(hWnd, message, wParam, lParam);
}
switch (message) {
// ALT + SPACE or F10 system menu
case WM_SYSCOMMAND: {
if (wParam == SC_KEYMENU) {
RECT winrect;
GetWindowRect(hWnd, &winrect);
TrackPopupMenu(GetSystemMenu(hWnd, false), TPM_TOPALIGN | TPM_LEFTALIGN,
winrect.left + 5, winrect.top + 5, 0, hWnd, NULL);
break;
} else {
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
case WM_NCCALCSIZE: {
// this kills the window frame and title bar we added with
// WS_THICKFRAME and WS_CAPTION
return 0;
}
// If the parent window gets any close messages, send them over to
// QWinWidget and don't actually close here
case WM_CLOSE: {
if (childWindow) {
SendMessage(childWindow, WM_CLOSE, 0, 0);
return 0;
}
break;
}
case WM_DESTROY: {
PostQuitMessage(0);
break;
}
case WM_NCHITTEST: {
const LONG borderWidth =
8 * childWidget->window()->devicePixelRatio(); // This value can be arbitrarily
// large as only
// intentionally-HTTRANSPARENT'd
// messages arrive here
RECT winrect;
GetWindowRect(hWnd, &winrect);
long x = GET_X_LPARAM(lParam);
long y = GET_Y_LPARAM(lParam);
// bottom left corner
if (x >= winrect.left && x < winrect.left + borderWidth && y < winrect.bottom &&
y >= winrect.bottom - borderWidth) {
return HTBOTTOMLEFT;
}
// bottom right corner
if (x < winrect.right && x >= winrect.right - borderWidth && y < winrect.bottom &&
y >= winrect.bottom - borderWidth) {
return HTBOTTOMRIGHT;
}
// top left corner
if (x >= winrect.left && x < winrect.left + borderWidth && y >= winrect.top &&
y < winrect.top + borderWidth) {
return HTTOPLEFT;
}
// top right corner
if (x < winrect.right && x >= winrect.right - borderWidth && y >= winrect.top &&
y < winrect.top + borderWidth) {
return HTTOPRIGHT;
}
// left border
if (x >= winrect.left && x < winrect.left + borderWidth) {
return HTLEFT;
}
// right border
if (x < winrect.right && x >= winrect.right - borderWidth) {
return HTRIGHT;
}
// bottom border
if (y < winrect.bottom && y >= winrect.bottom - borderWidth) {
return HTBOTTOM;
}
// top border
if (y >= winrect.top && y < winrect.top + borderWidth) {
return HTTOP;
}
// If it wasn't a border but we still got the message, return
// HTCAPTION to allow click-dragging the window
return HTCAPTION;
break;
}
// When this native window changes size, it needs to manually resize the
// QWinWidget child
case WM_SIZE: {
RECT winrect;
GetClientRect(hWnd, &winrect);
WINDOWPLACEMENT wp;
wp.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(hWnd, &wp);
if (childWidget) {
if (wp.showCmd == SW_MAXIMIZE) {
childWidget->setGeometry(
8, 8 // Maximized window draw 8 pixels off screen
,
winrect.right / childWidget->window()->devicePixelRatio() - 16,
winrect.bottom / childWidget->window()->devicePixelRatio() - 16);
} else {
childWidget->setGeometry(
0, 0, winrect.right / childWidget->window()->devicePixelRatio(),
winrect.bottom / childWidget->window()->devicePixelRatio());
}
}
break;
}
case WM_GETMINMAXINFO: {
MINMAXINFO *minMaxInfo = (MINMAXINFO *)lParam;
if (window->minimumSize.required) {
minMaxInfo->ptMinTrackSize.x = window->getMinimumWidth();
;
minMaxInfo->ptMinTrackSize.y = window->getMinimumHeight();
}
if (window->maximumSize.required) {
minMaxInfo->ptMaxTrackSize.x = window->getMaximumWidth();
minMaxInfo->ptMaxTrackSize.y = window->getMaximumHeight();
}
return 0;
}
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
void WinNativeWindow::setGeometry(const int x, const int y, const int width, const int height)
{
MoveWindow(hWnd, x, y, width, height, 1);
}
void WinNativeWindow::setMinimumSize(const int width, const int height)
{
this->minimumSize.required = true;
this->minimumSize.width = width;
this->minimumSize.height = height;
}
int WinNativeWindow::getMinimumWidth()
{
return minimumSize.width;
}
int WinNativeWindow::getMinimumHeight()
{
return minimumSize.height;
}
void WinNativeWindow::setMaximumSize(const int width, const int height)
{
this->maximumSize.required = true;
this->maximumSize.width = width;
this->maximumSize.height = height;
}
int WinNativeWindow::getMaximumWidth()
{
return maximumSize.width;
}
int WinNativeWindow::getMaximumHeight()
{
return maximumSize.height;
}

View file

@ -0,0 +1,53 @@
#ifndef WINNATIVEWINDOW_H
#define WINNATIVEWINDOW_H
#include "Windows.h"
#include "Windowsx.h"
#include <QWidget>
class WinNativeWindow
{
public:
WinNativeWindow(const int x, const int y, const int width, const int height);
~WinNativeWindow();
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// These six functions exist to restrict native window resizing to whatever
// you want your app minimum/maximum size to be
void setMinimumSize(const int width, const int height);
int getMinimumHeight();
int getMinimumWidth();
void setMaximumSize(const int width, const int height);
int getMaximumHeight();
int getMaximumWidth();
void setGeometry(const int x, const int y, const int width, const int height);
HWND hWnd;
static HWND childWindow;
static QWidget *childWidget;
private:
struct sizeType {
sizeType()
: required(false)
, width(0)
, height(0)
{
}
bool required;
int width;
int height;
};
sizeType minimumSize;
sizeType maximumSize;
DWORD aero_borderless = WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX |
WS_THICKFRAME | WS_CLIPCHILDREN;
};
#endif // WINNATIVEWINDOW_H

View file

@ -26,43 +26,34 @@ Resources::Resources()
{
}
void
Resources::load()
void Resources::load()
{
// badges
Resources::badgeStaff =
new messages::LazyLoadedImage(new QPixmap(":/images/staff_bg.png"));
Resources::badgeAdmin =
new messages::LazyLoadedImage(new QPixmap(":/images/admin_bg.png"));
Resources::badgeStaff = new messages::LazyLoadedImage(new QPixmap(":/images/staff_bg.png"));
Resources::badgeAdmin = new messages::LazyLoadedImage(new QPixmap(":/images/admin_bg.png"));
Resources::badgeModerator =
new messages::LazyLoadedImage(new QPixmap(":/images/moderator_bg.png"));
Resources::badgeGlobalmod =
new messages::LazyLoadedImage(new QPixmap(":/images/globalmod_bg.png"));
Resources::badgeTurbo =
new messages::LazyLoadedImage(new QPixmap(":/images/turbo_bg.png"));
Resources::badgeBroadcaster = new messages::LazyLoadedImage(
new QPixmap(":/images/broadcaster_bg.png"));
Resources::badgePremium = new messages::LazyLoadedImage(
new QPixmap(":/images/twitchprime_bg.png"));
Resources::badgeTurbo = new messages::LazyLoadedImage(new QPixmap(":/images/turbo_bg.png"));
Resources::badgeBroadcaster =
new messages::LazyLoadedImage(new QPixmap(":/images/broadcaster_bg.png"));
Resources::badgePremium =
new messages::LazyLoadedImage(new QPixmap(":/images/twitchprime_bg.png"));
// cheer badges
Resources::cheerBadge100000 =
new messages::LazyLoadedImage(new QPixmap(":/images/cheer100000"));
Resources::cheerBadge10000 =
new messages::LazyLoadedImage(new QPixmap(":/images/cheer10000"));
Resources::cheerBadge5000 =
new messages::LazyLoadedImage(new QPixmap(":/images/cheer5000"));
Resources::cheerBadge1000 =
new messages::LazyLoadedImage(new QPixmap(":/images/cheer1000"));
Resources::cheerBadge100 =
new messages::LazyLoadedImage(new QPixmap(":/images/cheer100"));
Resources::cheerBadge1 =
new messages::LazyLoadedImage(new QPixmap(":/images/cheer1"));
Resources::cheerBadge10000 = new messages::LazyLoadedImage(new QPixmap(":/images/cheer10000"));
Resources::cheerBadge5000 = new messages::LazyLoadedImage(new QPixmap(":/images/cheer5000"));
Resources::cheerBadge1000 = new messages::LazyLoadedImage(new QPixmap(":/images/cheer1000"));
Resources::cheerBadge100 = new messages::LazyLoadedImage(new QPixmap(":/images/cheer100"));
Resources::cheerBadge1 = new messages::LazyLoadedImage(new QPixmap(":/images/cheer1"));
// button
Resources::buttonBan = new messages::LazyLoadedImage(
new QPixmap(":/images/button_ban.png"), 0.25);
Resources::buttonTimeout = new messages::LazyLoadedImage(
new QPixmap(":/images/button_timeout.png"), 0.25);
Resources::buttonBan =
new messages::LazyLoadedImage(new QPixmap(":/images/button_ban.png"), 0.25);
Resources::buttonTimeout =
new messages::LazyLoadedImage(new QPixmap(":/images/button_timeout.png"), 0.25);
}
}

View file

@ -11,93 +11,78 @@ public:
static void load();
// badges
static messages::LazyLoadedImage *
getBadgeStaff()
static messages::LazyLoadedImage *getBadgeStaff()
{
return badgeStaff;
}
static messages::LazyLoadedImage *
getBadgeAdmin()
static messages::LazyLoadedImage *getBadgeAdmin()
{
return badgeAdmin;
}
static messages::LazyLoadedImage *
getBadgeGlobalmod()
static messages::LazyLoadedImage *getBadgeGlobalmod()
{
return badgeGlobalmod;
}
static messages::LazyLoadedImage *
getBadgeModerator()
static messages::LazyLoadedImage *getBadgeModerator()
{
return badgeModerator;
}
static messages::LazyLoadedImage *
getBadgeTurbo()
static messages::LazyLoadedImage *getBadgeTurbo()
{
return badgeTurbo;
}
static messages::LazyLoadedImage *
getBadgeBroadcaster()
static messages::LazyLoadedImage *getBadgeBroadcaster()
{
return badgeBroadcaster;
}
static messages::LazyLoadedImage *
getBadgePremium()
static messages::LazyLoadedImage *getBadgePremium()
{
return badgePremium;
}
// cheer badges
static messages::LazyLoadedImage *
getCheerBadge100000()
static messages::LazyLoadedImage *getCheerBadge100000()
{
return cheerBadge100000;
}
static messages::LazyLoadedImage *
getCheerBadge10000()
static messages::LazyLoadedImage *getCheerBadge10000()
{
return cheerBadge10000;
}
static messages::LazyLoadedImage *
getCheerBadge5000()
static messages::LazyLoadedImage *getCheerBadge5000()
{
return cheerBadge5000;
}
static messages::LazyLoadedImage *
getCheerBadge1000()
static messages::LazyLoadedImage *getCheerBadge1000()
{
return cheerBadge1000;
}
static messages::LazyLoadedImage *
getCheerBadge100()
static messages::LazyLoadedImage *getCheerBadge100()
{
return cheerBadge100;
}
static messages::LazyLoadedImage *
getCheerBadge1()
static messages::LazyLoadedImage *getCheerBadge1()
{
return cheerBadge1;
}
static messages::LazyLoadedImage *
getButtonBan()
static messages::LazyLoadedImage *getButtonBan()
{
return buttonBan;
}
static messages::LazyLoadedImage *
getButtonTimeout()
static messages::LazyLoadedImage *getButtonTimeout()
{
return buttonTimeout;
}

View file

@ -11,70 +11,65 @@ class BaseSetting
{
public:
BaseSetting(const QString &_name)
: name(_name)
: _name(_name)
{
}
virtual QVariant getVariant() = 0;
virtual void setVariant(QVariant value) = 0;
const QString &
getName() const
const QString &getName() const
{
return this->name;
return _name;
}
private:
QString name;
QString _name;
};
template <typename T>
class Setting : public BaseSetting
{
public:
Setting(std::vector<std::reference_wrapper<BaseSetting>> &settingItems,
const QString &_name, const T &defaultValue)
Setting(std::vector<std::reference_wrapper<BaseSetting>> &settingItems, const QString &_name,
const T &defaultValue)
: BaseSetting(_name)
, value(defaultValue)
, _value(defaultValue)
{
settingItems.push_back(*this);
}
const T &
get() const
const T &get() const
{
return this->value;
return _value;
}
void
set(const T &newValue)
void set(const T &newValue)
{
if (this->value != newValue) {
this->value = newValue;
if (_value != newValue) {
_value = newValue;
this->valueChanged(newValue);
valueChanged(newValue);
}
}
virtual QVariant
getVariant() final
virtual QVariant getVariant() final
{
return QVariant::fromValue(value);
return QVariant::fromValue(_value);
}
virtual void
setVariant(QVariant value) final
virtual void setVariant(QVariant value) final
{
if (value.isValid()) {
assert(value.canConvert<T>());
this->set(value.value<T>());
set(value.value<T>());
}
}
boost::signals2::signal<void(const T &newValue)> valueChanged;
private:
T value;
T _value;
};
} // namespace chatterino

View file

@ -1,127 +0,0 @@
#include "settings.h"
#include <QDebug>
#include <QDir>
#include <QStandardPaths>
namespace chatterino {
Settings Settings::instance;
Settings::Settings()
: settings(
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
"/Chatterino/newsettings.ini",
QSettings::IniFormat)
, portable(false)
, wordTypeMask(messages::Word::Default)
, theme(this->settingsItems, "theme", "dark")
, themeHue(this->settingsItems, "themeHue", 0)
, selectedUser(this->settingsItems, "selectedUser", "")
, emoteScale(this->settingsItems, "emoteScale", 1.0)
, mouseScrollMultiplier(this->settingsItems, "mouseScrollMultiplier", 1.0)
, scaleEmotesByLineHeight(this->settingsItems, "scaleEmotesByLineHeight",
false)
, showTimestamps(this->settingsItems, "showTimestamps", true)
, showTimestampSeconds(this->settingsItems, "showTimestampSeconds", false)
, showLastMessageIndicator(this->settingsItems, "showLastMessageIndicator",
false)
, allowDouplicateMessages(this->settingsItems, "allowDouplicateMessages",
true)
, linksDoubleClickOnly(this->settingsItems, "linksDoubleClickOnly", false)
, hideEmptyInput(this->settingsItems, "hideEmptyInput", false)
, showMessageLength(this->settingsItems, "showMessageLength", false)
, seperateMessages(this->settingsItems, "seperateMessages", false)
, mentionUsersWithAt(this->settingsItems, "mentionUsersWithAt", false)
, allowCommandsAtEnd(this->settingsItems, "allowCommandsAtEnd", false)
, enableHighlights(this->settingsItems, "enableHighlights", true)
, enableHighlightSound(this->settingsItems, "enableHighlightSound", true)
, enableHighlightTaskbar(this->settingsItems, "enableHighlightTaskbar",
true)
, customHighlightSound(this->settingsItems, "customHighlightSound", false)
, enableTwitchEmotes(this->settingsItems, "enableTwitchEmotes", true)
, enableBttvEmotes(this->settingsItems, "enableBttvEmotes", true)
, enableFfzEmotes(this->settingsItems, "enableFfzEmotes", true)
, enableEmojis(this->settingsItems, "enableEmojis", true)
, enableGifAnimations(this->settingsItems, "enableGifAnimations", true)
, enableGifs(this->settingsItems, "enableGifs", true)
, inlineWhispers(this->settingsItems, "inlineWhispers", true)
, windowTopMost(this->settingsItems, "windowTopMost", false)
, hideTabX(this->settingsItems, "hideTabX", false)
, hidePreferencesButton(this->settingsItems, "hidePreferencesButton", false)
, hideUserButton(this->settingsItems, "hideUserButton", false)
{
this->showTimestamps.valueChanged.connect(
[this](const auto &) { this->updateWordTypeMask(); });
this->showTimestampSeconds.valueChanged.connect(
[this](const auto &) { this->updateWordTypeMask(); });
this->enableBttvEmotes.valueChanged.connect(
[this](const auto &) { this->updateWordTypeMask(); });
this->enableEmojis.valueChanged.connect(
[this](const auto &) { this->updateWordTypeMask(); });
this->enableFfzEmotes.valueChanged.connect(
[this](const auto &) { this->updateWordTypeMask(); });
this->enableTwitchEmotes.valueChanged.connect(
[this](const auto &) { this->updateWordTypeMask(); });
}
void
Settings::save()
{
for (auto &item : settingsItems) {
this->settings.setValue(item.get().getName(), item.get().getVariant());
}
}
void
Settings::load()
{
for (auto &item : settingsItems) {
qDebug() << "Loading settings for " << item.get().getName();
item.get().setVariant(this->settings.value(item.get().getName()));
}
}
bool
Settings::isIgnoredEmote(const QString &)
{
return false;
}
void
Settings::updateWordTypeMask()
{
using namespace messages;
uint32_t mask = Word::Text;
if (showTimestamps.get()) {
mask |= showTimestampSeconds.get() ? Word::TimestampWithSeconds
: Word::TimestampNoSeconds;
}
mask |= enableTwitchEmotes.get() ? Word::TwitchEmoteImage
: Word::TwitchEmoteText;
mask |= enableFfzEmotes.get() ? Word::FfzEmoteImage : Word::FfzEmoteText;
mask |= enableBttvEmotes.get() ? Word::BttvEmoteImage : Word::BttvEmoteText;
mask |= (enableBttvEmotes.get() && enableGifs.get()) ? Word::BttvEmoteImage
: Word::BttvEmoteText;
mask |= enableEmojis.get() ? Word::EmojiImage : Word::EmojiText;
mask |= Word::BitsAmount;
mask |= enableGifs.get() ? Word::BitsAnimated : Word::BitsStatic;
mask |= Word::Badges;
mask |= Word::Username;
Word::Type _mask = (Word::Type)mask;
// if (mask != _mask) {
wordTypeMask = _mask;
emit wordTypeMaskChanged();
// }
}
} // namespace chatterino

146
settingsmanager.cpp Normal file
View file

@ -0,0 +1,146 @@
#include "settingsmanager.h"
#include <QDebug>
#include <QDir>
#include <QStandardPaths>
using namespace chatterino::messages;
namespace chatterino {
SettingsManager SettingsManager::instance;
SettingsManager::SettingsManager()
: _settings(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
"/Chatterino/newsettings.ini",
QSettings::IniFormat)
, _portable(false)
, _wordTypeMask(Word::Default)
, theme(_settingsItems, "theme", "dark")
, themeHue(_settingsItems, "themeHue", 0)
, selectedUser(_settingsItems, "selectedUser", "")
, emoteScale(_settingsItems, "emoteScale", 1.0)
, mouseScrollMultiplier(_settingsItems, "mouseScrollMultiplier", 1.0)
, scaleEmotesByLineHeight(_settingsItems, "scaleEmotesByLineHeight", false)
, showTimestamps(_settingsItems, "showTimestamps", true)
, showTimestampSeconds(_settingsItems, "showTimestampSeconds", false)
, showLastMessageIndicator(_settingsItems, "showLastMessageIndicator", false)
, allowDouplicateMessages(_settingsItems, "allowDouplicateMessages", true)
, linksDoubleClickOnly(_settingsItems, "linksDoubleClickOnly", false)
, hideEmptyInput(_settingsItems, "hideEmptyInput", false)
, showMessageLength(_settingsItems, "showMessageLength", false)
, seperateMessages(_settingsItems, "seperateMessages", false)
, mentionUsersWithAt(_settingsItems, "mentionUsersWithAt", false)
, allowCommandsAtEnd(_settingsItems, "allowCommandsAtEnd", false)
, enableHighlights(_settingsItems, "enableHighlights", true)
, enableHighlightSound(_settingsItems, "enableHighlightSound", true)
, enableHighlightTaskbar(_settingsItems, "enableHighlightTaskbar", true)
, customHighlightSound(_settingsItems, "customHighlightSound", false)
, enableTwitchEmotes(_settingsItems, "enableTwitchEmotes", true)
, enableBttvEmotes(_settingsItems, "enableBttvEmotes", true)
, enableFfzEmotes(_settingsItems, "enableFfzEmotes", true)
, enableEmojis(_settingsItems, "enableEmojis", true)
, enableGifAnimations(_settingsItems, "enableGifAnimations", true)
, enableGifs(_settingsItems, "enableGifs", true)
, inlineWhispers(_settingsItems, "inlineWhispers", true)
, windowTopMost(_settingsItems, "windowTopMost", false)
, hideTabX(_settingsItems, "hideTabX", false)
, hidePreferencesButton(_settingsItems, "hidePreferencesButton", false)
, hideUserButton(_settingsItems, "hideUserButton", false)
, useCustomWindowFrame(_settingsItems, "useCustomWindowFrame", true)
{
this->showTimestamps.valueChanged.connect([this](const auto &) { this->updateWordTypeMask(); });
this->showTimestampSeconds.valueChanged.connect(
[this](const auto &) { this->updateWordTypeMask(); });
this->enableBttvEmotes.valueChanged.connect(
[this](const auto &) { this->updateWordTypeMask(); });
this->enableEmojis.valueChanged.connect([this](const auto &) { this->updateWordTypeMask(); });
this->enableFfzEmotes.valueChanged.connect(
[this](const auto &) { this->updateWordTypeMask(); });
this->enableTwitchEmotes.valueChanged.connect(
[this](const auto &) { this->updateWordTypeMask(); });
}
void SettingsManager::save()
{
for (auto &item : _settingsItems) {
_settings.setValue(item.get().getName(), item.get().getVariant());
}
}
void SettingsManager::load()
{
for (auto &item : _settingsItems) {
qDebug() << "Loading settings for " << item.get().getName();
item.get().setVariant(_settings.value(item.get().getName()));
}
}
Word::Type SettingsManager::getWordTypeMask()
{
return _wordTypeMask;
}
bool SettingsManager::isIgnoredEmote(const QString &)
{
return false;
}
bool SettingsManager::getPortable()
{
return _portable;
}
void SettingsManager::setPortable(bool value)
{
_portable = value;
}
QSettings &SettingsManager::getQSettings()
{
return _settings;
}
void SettingsManager::updateWordTypeMask()
{
uint32_t mask = Word::Text;
if (showTimestamps.get()) {
mask |= showTimestampSeconds.get() ? Word::TimestampWithSeconds : Word::TimestampNoSeconds;
}
mask |= enableTwitchEmotes.get() ? Word::TwitchEmoteImage : Word::TwitchEmoteText;
mask |= enableFfzEmotes.get() ? Word::FfzEmoteImage : Word::FfzEmoteText;
mask |= enableBttvEmotes.get() ? Word::BttvEmoteImage : Word::BttvEmoteText;
mask |=
(enableBttvEmotes.get() && enableGifs.get()) ? Word::BttvEmoteImage : Word::BttvEmoteText;
mask |= enableEmojis.get() ? Word::EmojiImage : Word::EmojiText;
mask |= Word::BitsAmount;
mask |= enableGifs.get() ? Word::BitsAnimated : Word::BitsStatic;
mask |= Word::Badges;
mask |= Word::Username;
Word::Type _mask = (Word::Type)mask;
if (mask != _mask) {
_wordTypeMask = _mask;
emit wordTypeMaskChanged();
}
}
SettingsSnapshot SettingsManager::createSnapshot()
{
SettingsSnapshot snapshot;
for (auto &item : this->_settingsItems) {
snapshot.addItem(item, item.get().getVariant());
}
return snapshot;
}
} // namespace chatterino

View file

@ -9,75 +9,10 @@
namespace chatterino {
class Settings : public QObject
class SettingsManager : public QObject
{
Q_OBJECT
public:
static Settings &
getInstance()
{
return instance;
}
void load();
void save();
messages::Word::Type
getWordTypeMask()
{
return wordTypeMask;
}
bool isIgnoredEmote(const QString &emote);
bool
getPortable()
{
return portable;
}
void
setPortable(bool value)
{
portable = value;
}
QSettings &
getQSettings()
{
return settings;
}
SettingsSnapshot
createSnapshot()
{
SettingsSnapshot snapshot;
for (auto &item : this->settingsItems) {
snapshot.addItem(item, item.get().getVariant());
}
return snapshot;
}
signals:
void wordTypeMaskChanged();
private:
Settings();
static Settings instance;
void updateWordTypeMask();
QSettings settings;
std::vector<std::reference_wrapper<BaseSetting>> settingsItems;
bool portable;
messages::Word::Type wordTypeMask;
public:
Setting<QString> theme;
Setting<float> themeHue;
@ -110,6 +45,41 @@ public:
Setting<bool> hideTabX;
Setting<bool> hidePreferencesButton;
Setting<bool> hideUserButton;
Setting<bool> useCustomWindowFrame;
void load();
void save();
messages::Word::Type getWordTypeMask();
bool isIgnoredEmote(const QString &emote);
bool getPortable();
void setPortable(bool value);
QSettings &getQSettings();
SettingsSnapshot createSnapshot();
signals:
void wordTypeMaskChanged();
private:
SettingsManager();
// variables
QSettings _settings;
std::vector<std::reference_wrapper<BaseSetting>> _settingsItems;
bool _portable;
messages::Word::Type _wordTypeMask;
// methods
void updateWordTypeMask();
public:
static SettingsManager &getInstance()
{
return instance;
}
private:
static SettingsManager instance;
};
} // namespace chatterino

View file

@ -3,34 +3,31 @@
#include "setting.h"
struct SettingsSnapshot {
private:
std::vector<
std::pair<std::reference_wrapper<chatterino::BaseSetting>, QVariant>>
items;
namespace chatterino {
struct SettingsSnapshot {
public:
SettingsSnapshot()
: items()
: _items()
{
}
void
addItem(std::reference_wrapper<chatterino::BaseSetting> setting,
const QVariant &value)
void addItem(std::reference_wrapper<BaseSetting> setting, const QVariant &value)
{
items.push_back(
std::pair<std::reference_wrapper<chatterino::BaseSetting>,
QVariant>(setting.get(), value));
_items.push_back(
std::pair<std::reference_wrapper<BaseSetting>, QVariant>(setting.get(), value));
}
void
apply()
void apply()
{
for (auto &item : this->items) {
for (auto &item : _items) {
item.first.get().setVariant(item.second);
}
}
private:
std::vector<std::pair<std::reference_wrapper<BaseSetting>, QVariant>> _items;
};
}
#endif // SETTINGSSNAPSHOT_H

34
twitch/emotevalue.h Normal file
View file

@ -0,0 +1,34 @@
#ifndef TWITCHEMOTEVALUE_H
#define TWITCHEMOTEVALUE_H
#include "QString"
namespace chatterino {
namespace twitch {
struct EmoteValue {
public:
int getSet()
{
return _set;
}
int getId()
{
return _id;
}
const QString &getChannelName()
{
return _channelName;
}
private:
int _set;
int _id;
QString _channelName;
};
}
}
#endif // TWITCHEMOTEVALUE_H

View file

@ -0,0 +1,347 @@
#include "twitch/twitchmessagebuilder.h"
#include "colorscheme.h"
#include "emojis.h"
#include "emotemanager.h"
#include "ircmanager.h"
#include "resources.h"
using namespace chatterino::messages;
namespace chatterino {
namespace twitch {
TwitchMessageBuilder::TwitchMessageBuilder()
: MessageBuilder()
, messageId()
, userName()
{
}
void TwitchMessageBuilder::appendTwitchBadges(const QStringList &badges)
{
for (QString badge : badges) {
if (badge.startsWith("bits/")) {
long long int cheer = std::strtoll(badge.mid(5).toStdString().c_str(), NULL, 10);
appendWord(Word(EmoteManager::getInstance().getCheerBadge(cheer), Word::BadgeCheer,
QString(), QString("Twitch Cheer" + QString::number(cheer))));
} else if (badge == "staff/1") {
appendWord(Word(Resources::getBadgeStaff(), Word::BadgeStaff, QString(),
QString("Twitch Staff")));
} else if (badge == "admin/1") {
appendWord(Word(Resources::getBadgeAdmin(), Word::BadgeAdmin, QString(),
QString("Twitch Admin")));
} else if (badge == "global_mod/1") {
appendWord(Word(Resources::getBadgeGlobalmod(), Word::BadgeGlobalMod, QString(),
QString("Global Moderator")));
} else if (badge == "moderator/1") {
// TODO: implement this xD
appendWord(Word(Resources::getBadgeTurbo(), Word::BadgeModerator, QString(),
QString("Channel Moderator"))); // custom badge
} else if (badge == "turbo/1") {
appendWord(Word(Resources::getBadgeStaff(), Word::BadgeTurbo, QString(),
QString("Turbo Subscriber")));
} else if (badge == "broadcaster/1") {
appendWord(Word(Resources::getBadgeBroadcaster(), Word::BadgeBroadcaster, QString(),
QString("Channel Broadcaster")));
} else if (badge == "premium/1") {
appendWord(Word(Resources::getBadgePremium(), Word::BadgePremium, QString(),
QString("Twitch Prime")));
}
}
}
SharedMessage TwitchMessageBuilder::parse(const Communi::IrcPrivateMessage *ircMessage,
Channel *channel, const MessageParseArgs &args)
{
TwitchMessageBuilder b;
// timestamp
b.appendTimestamp();
auto tags = ircMessage->tags();
auto iterator = tags.find("id");
if (iterator != tags.end()) {
b.messageId = iterator.value().toString();
}
// timestamps
iterator = tags.find("tmi-sent-ts");
// mod buttons
static QString buttonBanTooltip("Ban user");
static QString buttonTimeoutTooltip("Timeout user");
b.appendWord(Word(Resources::getButtonBan(), Word::ButtonBan, QString(), buttonBanTooltip,
Link(Link::UserBan, ircMessage->account())));
b.appendWord(Word(Resources::getButtonTimeout(), Word::ButtonTimeout, QString(),
buttonTimeoutTooltip, Link(Link::UserTimeout, ircMessage->account())));
// badges
iterator = tags.find("badges");
if (iterator != tags.end()) {
auto badges = iterator.value().toString().split(',');
b.appendTwitchBadges(badges);
}
// color
QColor usernameColor = ColorScheme::getInstance().SystemMessageColor;
iterator = tags.find("color");
if (iterator != tags.end()) {
usernameColor = QColor(iterator.value().toString());
}
// channel name
if (args.includeChannelName) {
QString channelName("#" + channel->getName());
b.appendWord(Word(channelName, Word::Misc, ColorScheme::getInstance().SystemMessageColor,
QString(channelName), QString(),
Link(Link::Url, channel->getName() + "\n" + b.messageId)));
}
// username
b.userName = ircMessage->nick();
if (b.userName.isEmpty()) {
b.userName = tags.value(QLatin1String("login")).toString();
}
QString displayName;
iterator = tags.find("display-name");
if (iterator == tags.end()) {
displayName = ircMessage->account();
} else {
displayName = iterator.value().toString();
}
bool hasLocalizedName = QString::compare(displayName, ircMessage->account()) == 0;
QString userDisplayString =
displayName + (hasLocalizedName ? (" (" + ircMessage->account() + ")") : QString());
if (args.isSentWhisper) {
userDisplayString += IrcManager::getInstance().getUser().getUserName() + " -> ";
}
if (args.isReceivedWhisper) {
userDisplayString += " -> " + IrcManager::getInstance().getUser().getUserName();
}
if (!ircMessage->isAction()) {
userDisplayString += ": ";
}
b.appendWord(
Word(userDisplayString, Word::Username, usernameColor, userDisplayString, QString()));
// highlights
// TODO: implement this xD
// bits
QString bits = "";
iterator = tags.find("bits");
if (iterator != tags.end()) {
bits = iterator.value().toString();
}
// twitch emotes
std::vector<std::pair<long int, LazyLoadedImage *>> twitchEmotes;
iterator = tags.find("emotes");
if (iterator != tags.end()) {
auto emotes = iterator.value().toString().split('/');
for (QString emote : emotes) {
if (!emote.contains(':'))
continue;
QStringList parameters = emote.split(':');
if (parameters.length() < 2)
continue;
long int id = std::stol(parameters.at(0).toStdString(), NULL, 10);
QStringList occurences = parameters.at(1).split(',');
for (QString occurence : occurences) {
QStringList coords = occurence.split('-');
if (coords.length() < 2)
continue;
long int start = std::stol(coords.at(0).toStdString(), NULL, 10);
long int end = std::stol(coords.at(1).toStdString(), NULL, 10);
if (start >= end || start < 0 || end > ircMessage->content().length())
continue;
QString name = ircMessage->content().mid(start, end - start + 1);
twitchEmotes.push_back(std::pair<long int, LazyLoadedImage *>(
start, EmoteManager::getInstance().getTwitchEmoteById(name, id)));
}
}
struct {
bool operator()(const std::pair<long int, messages::LazyLoadedImage *> &a,
const std::pair<long int, messages::LazyLoadedImage *> &b)
{
return a.first < b.first;
}
} customLess;
std::sort(twitchEmotes.begin(), twitchEmotes.end(), customLess);
}
auto currentTwitchEmote = twitchEmotes.begin();
// words
QColor textColor = ircMessage->isAction() ? usernameColor : ColorScheme::getInstance().Text;
QStringList splits = ircMessage->content().split(' ');
long int i = 0;
for (QString split : splits) {
// twitch emote
if (currentTwitchEmote != twitchEmotes.end() && currentTwitchEmote->first == i) {
b.appendWord(Word(currentTwitchEmote->second, Word::TwitchEmoteImage,
currentTwitchEmote->second->getName(),
currentTwitchEmote->second->getName() + QString("\nTwitch Emote")));
b.appendWord(Word(currentTwitchEmote->second->getName(), Word::TwitchEmoteText,
textColor, currentTwitchEmote->second->getName(),
currentTwitchEmote->second->getName() + QString("\nTwitch Emote")));
i += split.length() + 1;
currentTwitchEmote = std::next(currentTwitchEmote);
continue;
}
// split words
std::vector<std::tuple<LazyLoadedImage *, QString>> parsed;
Emojis::parseEmojis(parsed, split);
for (const std::tuple<LazyLoadedImage *, QString> &tuple : parsed) {
LazyLoadedImage *image = std::get<0>(tuple);
if (image == NULL) { // is text
QString string = std::get<1>(tuple);
static QRegularExpression cheerRegex("cheer[1-9][0-9]*");
// cheers
if (!bits.isEmpty() && string.length() >= 6 && cheerRegex.match(string).isValid()) {
auto cheer = string.mid(5).toInt();
QString color;
QColor bitsColor;
if (cheer >= 10000) {
color = "red";
bitsColor = QColor::fromHslF(0, 1, 0.5);
} else if (cheer >= 5000) {
color = "blue";
bitsColor = QColor::fromHslF(0.61, 1, 0.4);
} else if (cheer >= 1000) {
color = "green";
bitsColor = QColor::fromHslF(0.5, 1, 0.5);
} else if (cheer >= 100) {
color = "purple";
bitsColor = QColor::fromHslF(0.8, 1, 0.5);
} else {
color = "gray";
bitsColor = QColor::fromHslF(0.5f, 0.5f, 0.5f);
}
QString bitsLinkAnimated =
QString("http://static-cdn.jtvnw.net/bits/dark/animated/" + color + "/1");
QString bitsLink =
QString("http://static-cdn.jtvnw.net/bits/dark/static/" + color + "/1");
LazyLoadedImage *imageAnimated =
EmoteManager::getInstance().getMiscImageFromCache().getOrAdd(
bitsLinkAnimated,
[&bitsLinkAnimated] { return new LazyLoadedImage(bitsLinkAnimated); });
LazyLoadedImage *image =
EmoteManager::getInstance().getMiscImageFromCache().getOrAdd(
bitsLink, [&bitsLink] { return new LazyLoadedImage(bitsLink); });
b.appendWord(Word(imageAnimated, Word::BitsAnimated, QString("cheer"),
QString("Twitch Cheer"),
Link(Link::Url, QString("https://blog.twitch.tv/"
"introducing-cheering-celebrate-"
"together-da62af41fac6"))));
b.appendWord(Word(image, Word::BitsStatic, QString("cheer"),
QString("Twitch Cheer"),
Link(Link::Url, QString("https://blog.twitch.tv/"
"introducing-cheering-celebrate-"
"together-da62af41fac6"))));
b.appendWord(Word(QString("x" + string.mid(5)), Word::BitsAmount, bitsColor,
QString(string.mid(5)), QString("Twitch Cheer"),
Link(Link::Url, QString("https://blog.twitch.tv/"
"introducing-cheering-celebrate-"
"together-da62af41fac6"))));
continue;
}
// bttv / ffz emotes
LazyLoadedImage *bttvEmote;
// TODO: Implement this (ignored emotes)
if (EmoteManager::getInstance().getBttvEmotes().tryGet(string, bttvEmote) ||
channel->getBttvChannelEmotes().tryGet(string, bttvEmote) ||
EmoteManager::getInstance().getFfzEmotes().tryGet(string, bttvEmote) ||
channel->getFfzChannelEmotes().tryGet(string, bttvEmote) ||
EmoteManager::getInstance().getChatterinoEmotes().tryGet(string, bttvEmote)) {
b.appendWord(Word(bttvEmote, Word::BttvEmoteImage, bttvEmote->getName(),
bttvEmote->getTooltip(),
Link(Link::Url, bttvEmote->getUrl())));
continue;
}
// actually just a word
QString link = b.matchLink(string);
b.appendWord(Word(string, Word::Text, textColor, string, QString(),
link.isEmpty() ? Link() : Link(Link::Url, link)));
} else { // is emoji
static QString emojiTooltip("Emoji");
b.appendWord(Word(image, Word::EmojiImage, image->getName(), emojiTooltip));
Word(image->getName(), Word::EmojiText, textColor, image->getName(), emojiTooltip);
}
}
i += split.length() + 1;
}
// TODO: Implement this xD
// if (!isReceivedWhisper &&
// AppSettings.HighlightIgnoredUsers.ContainsKey(Username))
// {
// HighlightTab = false;
// }
return b.build();
}
// bool
// sortTwitchEmotes(const std::pair<long int, LazyLoadedImage *> &a,
// const std::pair<long int, LazyLoadedImage *> &b)
//{
// return a.first < b.first;
//}
}
}

View file

@ -0,0 +1,31 @@
#ifndef TWITCHMESSAGEBUILDER_H
#define TWITCHMESSAGEBUILDER_H
#include "channel.h"
#include "messages/messagebuilder.h"
#include <QString>
namespace chatterino {
namespace twitch {
class TwitchMessageBuilder : public messages::MessageBuilder
{
public:
TwitchMessageBuilder();
void appendTwitchBadges(const QStringList &badges);
QString messageId;
QString userName;
static messages::SharedMessage parse(const Communi::IrcPrivateMessage *ircMessage,
Channel *channel, const messages::MessageParseArgs &args);
// static bool sortTwitchEmotes(
// const std::pair<long int, messages::LazyLoadedImage *> &a,
// const std::pair<long int, messages::LazyLoadedImage *> &b);
};
}
}
#endif // TWITCHMESSAGEBUILDER_H

View file

@ -0,0 +1,349 @@
//#include "twitchparsemessage.h"
//#include "colorscheme.h"
//#include "emojis.h"
//#include "emotemanager.h"
//#include "ircmanager.h"
//#include "resources.h"
//#include "twitch/twitchmessagebuilder.h"
//
//#include <QRegularExpression>
//
// using namespace chatterino::messages;
//
// namespace chatterino {
// namespace twitch {
// SharedMessage
// twitchParseMessage(const Communi::IrcPrivateMessage *ircMessage,
// Channel *channel, const MessageParseArgs &args)
//{
// TwitchMessageBuilder b;
//
// // timestamp
// b.appendTimestamp();
//
// auto tags = ircMessage->tags();
//
// auto iterator = tags.find("id");
//
// if (iterator != tags.end()) {
// b.messageId = iterator.value().toString();
// }
//
// // timestamps
// iterator = tags.find("tmi-sent-ts");
//
// // mod buttons
// static QString buttonBanTooltip("Ban user");
// static QString buttonTimeoutTooltip("Timeout user");
//
// b.appendWord(Word(Resources::getButtonBan(), Word::ButtonBan, QString(),
// buttonBanTooltip,
// Link(Link::UserBan, ircMessage->account())));
// b.appendWord(Word(Resources::getButtonTimeout(), Word::ButtonTimeout,
// QString(), buttonTimeoutTooltip,
// Link(Link::UserTimeout, ircMessage->account())));
//
// // badges
// iterator = tags.find("badges");
//
// if (iterator != tags.end()) {
// auto badges = iterator.value().toString().split(',');
//
// b.appendTwitchBadges(badges);
// }
//
// // color
// QColor usernameColor = ColorScheme::getInstance().SystemMessageColor;
//
// iterator = tags.find("color");
// if (iterator != tags.end()) {
// usernameColor = QColor(iterator.value().toString());
// }
//
// // channel name
// if (args.includeChannelName) {
// QString channelName("#" + channel->getName());
// b.appendWord(
// Word(channelName, Word::Misc,
// ColorScheme::getInstance().SystemMessageColor,
// QString(channelName), QString(),
// Link(Link::Url, channel->getName() + "\n" + b.messageId)));
// }
//
// // username
// b.userName = ircMessage->nick();
//
// if (b.userName.isEmpty()) {
// b.userName = tags.value(QLatin1String("login")).toString();
// }
//
// QString displayName;
//
// iterator = tags.find("display-name");
// if (iterator == tags.end()) {
// displayName = ircMessage->account();
// } else {
// displayName = iterator.value().toString();
// }
//
// bool hasLocalizedName =
// QString::compare(displayName, ircMessage->account()) == 0;
// QString userDisplayString =
// displayName +
// (hasLocalizedName ? (" (" + ircMessage->account() + ")") : QString());
//
// if (args.isSentWhisper) {
// userDisplayString +=
// IrcManager::getInstance().getUser().getUserName() + " -> ";
// }
//
// if (args.isReceivedWhisper) {
// userDisplayString +=
// " -> " + IrcManager::getInstance().getUser().getUserName();
// }
//
// if (!ircMessage->isAction()) {
// userDisplayString += ": ";
// }
//
// b.appendWord(Word(userDisplayString, Word::Username, usernameColor,
// userDisplayString, QString()));
//
// // highlights
// // TODO: implement this xD
//
// // bits
// QString bits = "";
//
// iterator = tags.find("bits");
// if (iterator != tags.end()) {
// bits = iterator.value().toString();
// }
//
// // twitch emotes
// std::vector<std::pair<long int, LazyLoadedImage *>> twitchEmotes;
//
// iterator = tags.find("emotes");
//
// if (iterator != tags.end()) {
// auto emotes = iterator.value().toString().split('/');
//
// for (QString emote : emotes) {
// if (!emote.contains(':'))
// continue;
//
// QStringList parameters = emote.split(':');
//
// if (parameters.length() < 2)
// continue;
//
// long int id = std::stol(parameters.at(0).toStdString(), NULL, 10);
//
// QStringList occurences = parameters.at(1).split(',');
//
// for (QString occurence : occurences) {
// QStringList coords = occurence.split('-');
//
// if (coords.length() < 2)
// continue;
//
// long int start =
// std::stol(coords.at(0).toStdString(), NULL, 10);
// long int end = std::stol(coords.at(1).toStdString(), NULL,
// 10);
//
// if (start >= end || start < 0 ||
// end > ircMessage->content().length())
// continue;
//
// QString name =
// ircMessage->content().mid(start, end - start + 1);
//
// twitchEmotes.push_back(std::pair<long int, LazyLoadedImage *>(
// start,
// EmoteManager::getInstance().getTwitchEmoteById(name,
// id)));
// }
// }
//
// std::sort(twitchEmotes.begin(), twitchEmotes.end(), sortTwitchEmotes);
// }
//
// auto currentTwitchEmote = twitchEmotes.begin();
//
// // words
// QColor textColor = ircMessage->isAction() ? usernameColor
// :
// ColorScheme::getInstance().Text;
//
// QStringList splits = ircMessage->content().split(' ');
//
// long int i = 0;
//
// for (QString split : splits) {
// // twitch emote
// if (currentTwitchEmote != twitchEmotes.end() &&
// currentTwitchEmote->first == i) {
// b.appendWord(Word(currentTwitchEmote->second,
// Word::TwitchEmoteImage,
// currentTwitchEmote->second->getName(),
// currentTwitchEmote->second->getName() +
// QString("\nTwitch Emote")));
// b.appendWord(Word(currentTwitchEmote->second->getName(),
// Word::TwitchEmoteText, textColor,
// currentTwitchEmote->second->getName(),
// currentTwitchEmote->second->getName() +
// QString("\nTwitch Emote")));
//
// i += split.length() + 1;
// currentTwitchEmote = std::next(currentTwitchEmote);
//
// continue;
// }
//
// // split words
// std::vector<std::tuple<LazyLoadedImage *, QString>> parsed;
//
// Emojis::parseEmojis(parsed, split);
//
// for (const std::tuple<LazyLoadedImage *, QString> &tuple : parsed) {
// LazyLoadedImage *image = std::get<0>(tuple);
//
// if (image == NULL) { // is text
// QString string = std::get<1>(tuple);
//
// static QRegularExpression cheerRegex("cheer[1-9][0-9]*");
//
// // cheers
// if (!bits.isEmpty() && string.length() >= 6 &&
// cheerRegex.match(string).isValid()) {
// auto cheer = string.mid(5).toInt();
//
// QString color;
//
// QColor bitsColor;
//
// if (cheer >= 10000) {
// color = "red";
// bitsColor = QColor::fromHslF(0, 1, 0.5);
// } else if (cheer >= 5000) {
// color = "blue";
// bitsColor = QColor::fromHslF(0.61, 1, 0.4);
// } else if (cheer >= 1000) {
// color = "green";
// bitsColor = QColor::fromHslF(0.5, 1, 0.5);
// } else if (cheer >= 100) {
// color = "purple";
// bitsColor = QColor::fromHslF(0.8, 1, 0.5);
// } else {
// color = "gray";
// bitsColor = QColor::fromHslF(0.5f, 0.5f, 0.5f);
// }
//
// QString bitsLinkAnimated = QString(
// "http://static-cdn.jtvnw.net/bits/dark/animated/" +
// color + "/1");
// QString bitsLink = QString(
// "http://static-cdn.jtvnw.net/bits/dark/static/" +
// color + "/1");
//
// LazyLoadedImage *imageAnimated =
// EmoteManager::getInstance()
// .getMiscImageFromCache()
// .getOrAdd(bitsLinkAnimated, [&bitsLinkAnimated] {
// return new LazyLoadedImage(bitsLinkAnimated);
// });
// LazyLoadedImage *image =
// EmoteManager::getInstance()
// .getMiscImageFromCache()
// .getOrAdd(bitsLink, [&bitsLink] {
// return new LazyLoadedImage(bitsLink);
// });
//
// b.appendWord(
// Word(imageAnimated, Word::BitsAnimated,
// QString("cheer"), QString("Twitch Cheer"),
// Link(Link::Url,
// QString("https://blog.twitch.tv/"
// "introducing-cheering-celebrate-"
// "together-da62af41fac6"))));
// b.appendWord(
// Word(image, Word::BitsStatic, QString("cheer"),
// QString("Twitch Cheer"),
// Link(Link::Url,
// QString("https://blog.twitch.tv/"
// "introducing-cheering-celebrate-"
// "together-da62af41fac6"))));
//
// b.appendWord(
// Word(QString("x" + string.mid(5)), Word::BitsAmount,
// bitsColor, QString(string.mid(5)),
// QString("Twitch Cheer"),
// Link(Link::Url,
// QString("https://blog.twitch.tv/"
// "introducing-cheering-celebrate-"
// "together-da62af41fac6"))));
//
// continue;
// }
//
// // bttv / ffz emotes
// LazyLoadedImage *bttvEmote;
//
// // TODO: Implement this (ignored emotes)
// if (EmoteManager::getInstance().getBttvEmotes().tryGet(
// string, bttvEmote) ||
// channel->getBttvChannelEmotes().tryGet(string, bttvEmote)
// ||
// EmoteManager::getInstance().getFfzEmotes().tryGet(
// string, bttvEmote) ||
// channel->getFfzChannelEmotes().tryGet(string, bttvEmote)
// ||
// EmoteManager::getInstance().getChatterinoEmotes().tryGet(
// string, bttvEmote)) {
// b.appendWord(Word(bttvEmote, Word::BttvEmoteImage,
// bttvEmote->getName(),
// bttvEmote->getTooltip(),
// Link(Link::Url, bttvEmote->getUrl())));
//
// continue;
// }
//
// // actually just a word
// QString link = b.matchLink(string);
//
// b.appendWord(
// Word(string, Word::Text, textColor, string, QString(),
// link.isEmpty() ? Link() : Link(Link::Url, link)));
// } else { // is emoji
// static QString emojiTooltip("Emoji");
//
// b.appendWord(Word(image, Word::EmojiImage, image->getName(),
// emojiTooltip));
// Word(image->getName(), Word::EmojiText, textColor,
// image->getName(), emojiTooltip);
// }
// }
//
// i += split.length() + 1;
// }
//
// // TODO: Implement this xD
// // if (!isReceivedWhisper &&
// // AppSettings.HighlightIgnoredUsers.ContainsKey(Username))
// // {
// // HighlightTab = false;
// // }
//
// return b.build();
//}
//
// bool
// sortTwitchEmotes(const std::pair<long int, LazyLoadedImage *> &a,
// const std::pair<long int, LazyLoadedImage *> &b)
//{
// return a.first < b.first;
//}
//}
//}
//

View file

@ -0,0 +1,13 @@
//#ifndef MESSAGEPARSER_H
//#define MESSAGEPARSER_H
//
//#include "messages/lazyloadedimage.h"
//#include "messages/messagebuilder.h"
//#include "messages/messageparseargs.h"
//
// namespace chatterino {
// namespace twitch {
//}
//}
//
//#endif // MESSAGEPARSER_H

28
twitch/twitchuser.cpp Normal file
View file

@ -0,0 +1,28 @@
#include "twitchuser.h"
namespace chatterino {
namespace twitch {
TwitchUser::TwitchUser(const QString &username, const QString &oauthToken,
const QString &oauthClient)
: IrcUser2(username, username, username, "oauth:" + oauthToken)
{
_oauthClient = oauthClient;
_oauthToken = oauthToken;
}
const QString &TwitchUser::getOAuthClient() const
{
return _oauthClient;
}
const QString &TwitchUser::getOAuthToken() const
{
return _oauthToken;
}
bool TwitchUser::isAnon() const
{
return IrcUser2::getNickName().startsWith("justinfan");
}
}
}

28
twitch/twitchuser.h Normal file
View file

@ -0,0 +1,28 @@
#ifndef ACCOUNT_H
#define ACCOUNT_H
#include "ircuser2.h"
#include <QString>
namespace chatterino {
namespace twitch {
class TwitchUser : public IrcUser2
{
public:
TwitchUser(const QString &username, const QString &oauthToken, const QString &oauthClient);
const QString &getOAuthToken() const;
const QString &getOAuthClient() const;
bool isAnon() const;
private:
QString _oauthClient;
QString _oauthToken;
};
}
}
#endif // ACCOUNT_H

View file

@ -1,35 +0,0 @@
#ifndef TWITCHEMOTEVALUE_H
#define TWITCHEMOTEVALUE_H
#include "QString"
namespace chatterino {
struct TwitchEmoteValue {
public:
int
getSet()
{
return set;
}
int
getId()
{
return id;
}
const QString &
getChannelName()
{
return channelName;
}
private:
int set;
int id;
QString channelName;
};
}
#endif // TWITCHEMOTEVALUE_H

16
usermanager.cpp Normal file
View file

@ -0,0 +1,16 @@
#include "usermanager.h"
namespace chatterino {
AccountManager AccountManager::instance;
AccountManager::AccountManager()
: _anon("justinfan64537", "", "")
{
}
twitch::TwitchUser &AccountManager::getAnon()
{
return _anon;
}
}

27
usermanager.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef ACCOUNTMANAGER_H
#define ACCOUNTMANAGER_H
#include "twitch/twitchuser.h"
namespace chatterino {
class AccountManager
{
public:
static AccountManager &getInstance()
{
return instance;
}
twitch::TwitchUser &getAnon();
private:
static AccountManager instance;
AccountManager();
twitch::TwitchUser _anon;
};
}
#endif // ACCOUNTMANAGER_H

55
util/urlfetch.h Normal file
View file

@ -0,0 +1,55 @@
#ifndef URLFETCH_H
#define URLFETCH_H
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QString>
#include <functional>
namespace chatterino {
namespace util {
static void urlFetch(const QString &url, std::function<void(QNetworkReply &)> successCallback)
{
QNetworkAccessManager *manager = new QNetworkAccessManager();
QUrl requestUrl(url);
QNetworkRequest request(requestUrl);
QNetworkReply *reply = manager->get(request);
QObject::connect(reply, &QNetworkReply::finished, [=] {
if (reply->error() == QNetworkReply::NetworkError::NoError) {
successCallback(*reply);
}
reply->deleteLater();
manager->deleteLater();
});
}
static void urlJsonFetch(const QString &url, std::function<void(QJsonObject &)> successCallback)
{
urlFetch(url, [=](QNetworkReply &reply) {
QByteArray data = reply.readAll();
QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
if (jsonDoc.isNull()) {
return;
}
QJsonObject rootNode = jsonDoc.object();
successCallback(rootNode);
});
}
}
}
#endif // URLFETCH_H

View file

@ -1,7 +1,7 @@
#include "widgets/chatwidget.h"
#include "channels.h"
#include "channelmanager.h"
#include "colorscheme.h"
#include "settings.h"
#include "settingsmanager.h"
#include "widgets/textinputdialog.h"
#include <QDebug>
@ -11,110 +11,167 @@
#include <QVBoxLayout>
#include <boost/signals2.hpp>
using namespace chatterino::messages;
namespace chatterino {
namespace widgets {
ChatWidget::ChatWidget(QWidget *parent)
: QWidget(parent)
, messages()
, channel(Channels::getEmpty())
, channelName(QString())
, vbox(this)
, header(this)
, view(this)
, input(this)
, _messages()
, _channel(ChannelManager::getInstance().getEmpty())
, _channelName(QString())
, _vbox(this)
, _header(this)
, _view(this)
, _input(this)
{
this->vbox.setSpacing(0);
this->vbox.setMargin(1);
this->_vbox.setSpacing(0);
this->_vbox.setMargin(1);
this->vbox.addWidget(&header);
this->vbox.addWidget(&view, 1);
this->vbox.addWidget(&input);
this->_vbox.addWidget(&_header);
this->_vbox.addWidget(&_view, 1);
this->_vbox.addWidget(&_input);
}
ChatWidget::~ChatWidget()
{
}
std::shared_ptr<Channel>
ChatWidget::getChannel() const
{
return _channel;
}
const QString &
ChatWidget::getChannelName() const
{
return _channelName;
}
void
ChatWidget::setChannelName(const QString &name)
{
QString channel = name.trimmed();
if (QString::compare(channel, this->channelName, Qt::CaseInsensitive) ==
0) {
this->channelName = channel;
this->header.updateChannelText();
// return if channel name is the same
if (QString::compare(channel, _channelName, Qt::CaseInsensitive) == 0) {
_channelName = channel;
_header.updateChannelText();
return;
}
if (!this->channelName.isEmpty()) {
Channels::removeChannel(this->channelName);
// remove current channel
if (!_channelName.isEmpty()) {
ChannelManager::getInstance().removeChannel(_channelName);
this->messageAppendedConnection.disconnect();
this->messageRemovedConnection.disconnect();
detachChannel(_channel);
}
this->channelName = channel;
this->header.updateChannelText();
// update members
_channelName = channel;
this->view.layoutMessages();
messages.clear();
// update messages
_messages.clear();
if (channel.isEmpty()) {
this->channel = NULL;
_channel = NULL;
} else {
this->channel = Channels::addChannel(channel);
_channel = ChannelManager::getInstance().addChannel(channel);
this->messageAppendedConnection =
this->channel.get()->messageAppended.connect([this](
std::shared_ptr<messages::Message> &message) {
std::shared_ptr<messages::MessageRef> deleted;
auto messageRef = new messages::MessageRef(message);
this->messages.appendItem(
std::shared_ptr<messages::MessageRef>(messageRef), deleted);
});
this->messageRemovedConnection =
this->channel.get()->messageRemovedFromStart.connect(
[this](std::shared_ptr<messages::Message> &) {});
auto snapshot = this->channel.get()->getMessageSnapshot();
for (int i = 0; i < snapshot.getLength(); i++) {
std::shared_ptr<messages::MessageRef> deleted;
auto messageRef = new messages::MessageRef(snapshot[i]);
this->messages.appendItem(
std::shared_ptr<messages::MessageRef>(messageRef), deleted);
}
attachChannel(_channel);
}
this->view.layoutMessages();
this->view.update();
// update header
_header.updateChannelText();
// update view
_view.layoutMessages();
_view.update();
}
void
ChatWidget::attachChannel(SharedChannel channel)
{
// on new message
_messageAppendedConnection =
channel->messageAppended.connect([this](SharedMessage &message) {
SharedMessageRef deleted;
auto messageRef = new MessageRef(message);
if (_messages.appendItem(SharedMessageRef(messageRef), deleted)) {
qreal value =
std::max(0.0, _view.getScrollbar()->getDesiredValue() - 1);
_view.getScrollbar()->setDesiredValue(value, false);
}
});
// on message removed
_messageRemovedConnection =
_channel->messageRemovedFromStart.connect([this](SharedMessage &) {});
auto snapshot = _channel.get()->getMessageSnapshot();
for (int i = 0; i < snapshot.getLength(); i++) {
SharedMessageRef deleted;
auto messageRef = new MessageRef(snapshot[i]);
_messages.appendItem(SharedMessageRef(messageRef), deleted);
}
}
void
ChatWidget::detachChannel(std::shared_ptr<Channel> channel)
{
// on message added
_messageAppendedConnection.disconnect();
// on message removed
_messageRemovedConnection.disconnect();
}
LimitedQueueSnapshot<SharedMessageRef>
ChatWidget::getMessagesSnapshot()
{
return _messages.getSnapshot();
}
void
ChatWidget::showChangeChannelPopup()
{
// create new input dialog and execute it
TextInputDialog dialog(this);
dialog.setText(this->channelName);
dialog.setText(_channelName);
if (dialog.exec() == QDialog::Accepted) {
setChannelName(dialog.getText());
}
}
void
ChatWidget::layoutMessages()
{
if (_view.layoutMessages()) {
_view.update();
}
}
void
ChatWidget::updateGifEmotes()
{
_view.updateGifEmotes();
}
void
ChatWidget::paintEvent(QPaintEvent *)
{
// color the background of the chat
QPainter painter(this);
painter.fillRect(this->rect(), ColorScheme::getInstance().ChatBackground);
@ -123,7 +180,7 @@ ChatWidget::paintEvent(QPaintEvent *)
void
ChatWidget::load(const boost::property_tree::ptree &tree)
{
// Load tab text
// load tab text
try {
this->setChannelName(
QString::fromStdString(tree.get<std::string>("channelName")));

View file

@ -27,51 +27,35 @@ public:
ChatWidget(QWidget *parent = 0);
~ChatWidget();
ChatWidgetView &
getView()
{
return view;
}
std::shared_ptr<Channel>
getChannel() const
{
return channel;
}
const QString &
getChannelName() const
{
return channelName;
}
SharedChannel getChannel() const;
const QString &getChannelName() const;
void setChannelName(const QString &name);
void showChangeChannelPopup();
messages::LimitedQueueSnapshot<std::shared_ptr<messages::MessageRef>>
getMessagesSnapshot()
{
return messages.getSnapshot();
}
messages::LimitedQueueSnapshot<messages::SharedMessageRef> getMessagesSnapshot();
void layoutMessages();
void updateGifEmotes();
protected:
void paintEvent(QPaintEvent *) override;
private:
messages::LimitedQueue<std::shared_ptr<messages::MessageRef>> messages;
void attachChannel(std::shared_ptr<Channel> _channel);
void detachChannel(std::shared_ptr<Channel> _channel);
std::shared_ptr<Channel> channel;
QString channelName;
messages::LimitedQueue<messages::SharedMessageRef> _messages;
QFont font;
QVBoxLayout vbox;
ChatWidgetHeader header;
ChatWidgetView view;
ChatWidgetInput input;
SharedChannel _channel;
QString _channelName;
boost::signals2::connection messageAppendedConnection;
boost::signals2::connection messageRemovedConnection;
QFont _font;
QVBoxLayout _vbox;
ChatWidgetHeader _header;
ChatWidgetView _view;
ChatWidgetInput _input;
boost::signals2::connection _messageAppendedConnection;
boost::signals2::connection _messageRemovedConnection;
public:
void load(const boost::property_tree::ptree &tree);

View file

@ -13,90 +13,81 @@ namespace widgets {
ChatWidgetHeader::ChatWidgetHeader(ChatWidget *parent)
: QWidget()
, chatWidget(parent)
, dragStart()
, dragging(false)
, leftLabel()
, middleLabel()
, rightLabel()
, leftMenu(this)
, rightMenu(this)
, _chatWidget(parent)
, _dragStart()
, _dragging(false)
, _leftLabel()
, _middleLabel()
, _rightLabel()
, _leftMenu(this)
, _rightMenu(this)
{
setFixedHeight(32);
updateColors();
updateChannelText();
setLayout(&this->hbox);
this->hbox.setMargin(0);
this->hbox.addWidget(&this->leftLabel);
this->hbox.addWidget(&this->middleLabel, 1);
this->hbox.addWidget(&this->rightLabel);
setLayout(&_hbox);
_hbox.setMargin(0);
_hbox.addWidget(&_leftLabel);
_hbox.addWidget(&_middleLabel, 1);
_hbox.addWidget(&_rightLabel);
// left
this->leftLabel.getLabel().setTextFormat(Qt::RichText);
this->leftLabel.getLabel().setText(
"<img src=':/images/tool_moreCollapser_off16.png' />");
_leftLabel.getLabel().setTextFormat(Qt::RichText);
_leftLabel.getLabel().setText("<img src=':/images/tool_moreCollapser_off16.png' />");
QObject::connect(&this->leftLabel, &ChatWidgetHeaderButton::clicked, this,
QObject::connect(&_leftLabel, &ChatWidgetHeaderButton::clicked, this,
&ChatWidgetHeader::leftButtonClicked);
this->leftMenu.addAction("Add new split", this, SLOT(menuAddSplit()),
QKeySequence(tr("Ctrl+T")));
this->leftMenu.addAction("Close split", this, SLOT(menuCloseSplit()),
QKeySequence(tr("Ctrl+W")));
this->leftMenu.addAction("Move split", this, SLOT(menuMoveSplit()));
this->leftMenu.addAction("Popup", this, SLOT(menuPopup()));
this->leftMenu.addSeparator();
this->leftMenu.addAction("Change channel", this, SLOT(menuChangeChannel()),
QKeySequence(tr("Ctrl+R")));
this->leftMenu.addAction("Clear chat", this, SLOT(menuClearChat()));
this->leftMenu.addAction("Open channel", this, SLOT(menuOpenChannel()));
this->leftMenu.addAction("Open pop-out player", this,
SLOT(menuPopupPlayer()));
this->leftMenu.addSeparator();
this->leftMenu.addAction("Reload channel emotes", this,
SLOT(menuReloadChannelEmotes()));
this->leftMenu.addAction("Manual reconnect", this,
SLOT(menuManualReconnect()));
this->leftMenu.addSeparator();
this->leftMenu.addAction("Show changelog", this, SLOT(menuShowChangelog()));
_leftMenu.addAction("Add new split", this, SLOT(menuAddSplit()), QKeySequence(tr("Ctrl+T")));
_leftMenu.addAction("Close split", this, SLOT(menuCloseSplit()), QKeySequence(tr("Ctrl+W")));
_leftMenu.addAction("Move split", this, SLOT(menuMoveSplit()));
_leftMenu.addAction("Popup", this, SLOT(menuPopup()));
_leftMenu.addSeparator();
_leftMenu.addAction("Change channel", this, SLOT(menuChangeChannel()),
QKeySequence(tr("Ctrl+R")));
_leftMenu.addAction("Clear chat", this, SLOT(menuClearChat()));
_leftMenu.addAction("Open channel", this, SLOT(menuOpenChannel()));
_leftMenu.addAction("Open pop-out player", this, SLOT(menuPopupPlayer()));
_leftMenu.addSeparator();
_leftMenu.addAction("Reload channel emotes", this, SLOT(menuReloadChannelEmotes()));
_leftMenu.addAction("Manual reconnect", this, SLOT(menuManualReconnect()));
_leftMenu.addSeparator();
_leftMenu.addAction("Show changelog", this, SLOT(menuShowChangelog()));
// middle
this->middleLabel.setAlignment(Qt::AlignCenter);
_middleLabel.setAlignment(Qt::AlignCenter);
connect(&this->middleLabel, &SignalLabel::mouseDoubleClick, this,
connect(&_middleLabel, &SignalLabel::mouseDoubleClick, this,
&ChatWidgetHeader::mouseDoubleClickEvent);
// connect(&this->middleLabel, &SignalLabel::mouseDown, this,
// &ChatWidgetHeader::mouseDoubleClickEvent);
// right
this->rightLabel.setMinimumWidth(height());
this->rightLabel.getLabel().setTextFormat(Qt::RichText);
this->rightLabel.getLabel().setText("ayy");
_rightLabel.setMinimumWidth(height());
_rightLabel.getLabel().setTextFormat(Qt::RichText);
_rightLabel.getLabel().setText("ayy");
}
void
ChatWidgetHeader::updateColors()
void ChatWidgetHeader::updateColors()
{
QPalette palette;
palette.setColor(QPalette::Foreground, ColorScheme::getInstance().Text);
this->leftLabel.setPalette(palette);
this->middleLabel.setPalette(palette);
this->rightLabel.setPalette(palette);
_leftLabel.setPalette(palette);
_middleLabel.setPalette(palette);
_rightLabel.setPalette(palette);
}
void
ChatWidgetHeader::updateChannelText()
void ChatWidgetHeader::updateChannelText()
{
const QString &c = this->chatWidget->getChannelName();
const QString &c = _chatWidget->getChannelName();
this->middleLabel.setText(c.isEmpty() ? "<no channel>" : c);
_middleLabel.setText(c.isEmpty() ? "<no channel>" : c);
}
void
ChatWidgetHeader::paintEvent(QPaintEvent *)
void ChatWidgetHeader::paintEvent(QPaintEvent *)
{
QPainter painter(this);
@ -105,21 +96,19 @@ ChatWidgetHeader::paintEvent(QPaintEvent *)
painter.drawRect(0, 0, width() - 1, height() - 1);
}
void
ChatWidgetHeader::mousePressEvent(QMouseEvent *event)
void ChatWidgetHeader::mousePressEvent(QMouseEvent *event)
{
this->dragging = true;
_dragging = true;
this->dragStart = event->pos();
_dragStart = event->pos();
}
void
ChatWidgetHeader::mouseMoveEvent(QMouseEvent *event)
void ChatWidgetHeader::mouseMoveEvent(QMouseEvent *event)
{
if (this->dragging) {
if (std::abs(this->dragStart.x() - event->pos().x()) > 12 ||
std::abs(this->dragStart.y() - event->pos().y()) > 12) {
auto chatWidget = this->chatWidget;
if (_dragging) {
if (std::abs(_dragStart.x() - event->pos().x()) > 12 ||
std::abs(_dragStart.y() - event->pos().y()) > 12) {
auto chatWidget = _chatWidget;
auto page = static_cast<NotebookPage *>(chatWidget->parentWidget());
if (page != NULL) {
@ -149,73 +138,58 @@ ChatWidgetHeader::mouseMoveEvent(QMouseEvent *event)
}
}
void
ChatWidgetHeader::mouseDoubleClickEvent(QMouseEvent *event)
void ChatWidgetHeader::mouseDoubleClickEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
this->chatWidget->showChangeChannelPopup();
_chatWidget->showChangeChannelPopup();
}
}
void
ChatWidgetHeader::leftButtonClicked()
void ChatWidgetHeader::leftButtonClicked()
{
this->leftMenu.move(
this->leftLabel.mapToGlobal(QPoint(0, this->leftLabel.height())));
this->leftMenu.show();
_leftMenu.move(_leftLabel.mapToGlobal(QPoint(0, _leftLabel.height())));
_leftMenu.show();
}
void
ChatWidgetHeader::rightButtonClicked()
void ChatWidgetHeader::rightButtonClicked()
{
}
void
ChatWidgetHeader::menuAddSplit()
void ChatWidgetHeader::menuAddSplit()
{
}
void
ChatWidgetHeader::menuCloseSplit()
void ChatWidgetHeader::menuCloseSplit()
{
}
void
ChatWidgetHeader::menuMoveSplit()
void ChatWidgetHeader::menuMoveSplit()
{
}
void
ChatWidgetHeader::menuPopup()
void ChatWidgetHeader::menuPopup()
{
auto widget = new ChatWidget();
widget->setChannelName(this->chatWidget->getChannelName());
widget->setChannelName(_chatWidget->getChannelName());
widget->show();
}
void
ChatWidgetHeader::menuChangeChannel()
void ChatWidgetHeader::menuChangeChannel()
{
this->chatWidget->showChangeChannelPopup();
_chatWidget->showChangeChannelPopup();
}
void
ChatWidgetHeader::menuClearChat()
void ChatWidgetHeader::menuClearChat()
{
}
void
ChatWidgetHeader::menuOpenChannel()
void ChatWidgetHeader::menuOpenChannel()
{
}
void
ChatWidgetHeader::menuPopupPlayer()
void ChatWidgetHeader::menuPopupPlayer()
{
}
void
ChatWidgetHeader::menuReloadChannelEmotes()
void ChatWidgetHeader::menuReloadChannelEmotes()
{
}
void
ChatWidgetHeader::menuManualReconnect()
void ChatWidgetHeader::menuManualReconnect()
{
}
void
ChatWidgetHeader::menuShowChangelog()
void ChatWidgetHeader::menuShowChangelog()
{
}
}

View file

@ -24,10 +24,9 @@ class ChatWidgetHeader : public QWidget
public:
explicit ChatWidgetHeader(ChatWidget *parent);
ChatWidget *
getChatWidget()
ChatWidget *getChatWidget()
{
return chatWidget;
return _chatWidget;
}
void updateColors();
@ -40,19 +39,19 @@ protected:
void mouseDoubleClickEvent(QMouseEvent *event);
private:
ChatWidget *chatWidget;
ChatWidget *_chatWidget;
QPoint dragStart;
bool dragging;
QPoint _dragStart;
bool _dragging;
QHBoxLayout hbox;
QHBoxLayout _hbox;
ChatWidgetHeaderButton leftLabel;
SignalLabel middleLabel;
ChatWidgetHeaderButton rightLabel;
ChatWidgetHeaderButton _leftLabel;
SignalLabel _middleLabel;
ChatWidgetHeaderButton _rightLabel;
QMenu leftMenu;
QMenu rightMenu;
QMenu _leftMenu;
QMenu _rightMenu;
void leftButtonClicked();
void rightButtonClicked();

View file

@ -9,59 +9,55 @@ namespace widgets {
ChatWidgetHeaderButton::ChatWidgetHeaderButton(int spacing)
: QWidget()
, hbox()
, label()
, mouseOver(false)
, mouseDown(false)
, _hbox()
, _label()
, _mouseOver(false)
, _mouseDown(false)
{
setLayout(&hbox);
setLayout(&_hbox);
label.setAlignment(Qt::AlignCenter);
_label.setAlignment(Qt::AlignCenter);
hbox.setMargin(0);
hbox.addSpacing(spacing);
hbox.addWidget(&this->label);
hbox.addSpacing(spacing);
_hbox.setMargin(0);
_hbox.addSpacing(spacing);
_hbox.addWidget(&_label);
_hbox.addSpacing(spacing);
QObject::connect(&this->label, &SignalLabel::mouseUp, this,
QObject::connect(&_label, &SignalLabel::mouseUp, this,
&ChatWidgetHeaderButton::labelMouseUp);
QObject::connect(&this->label, &SignalLabel::mouseDown, this,
QObject::connect(&_label, &SignalLabel::mouseDown, this,
&ChatWidgetHeaderButton::labelMouseDown);
}
void
ChatWidgetHeaderButton::paintEvent(QPaintEvent *)
void ChatWidgetHeaderButton::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QBrush brush(ColorScheme::getInstance().IsLightTheme
? QColor(0, 0, 0, 32)
: QColor(255, 255, 255, 32));
QBrush brush(ColorScheme::getInstance().IsLightTheme ? QColor(0, 0, 0, 32)
: QColor(255, 255, 255, 32));
if (this->mouseDown) {
if (_mouseDown) {
painter.fillRect(rect(), brush);
}
if (this->mouseOver) {
if (_mouseOver) {
painter.fillRect(rect(), brush);
}
}
void
ChatWidgetHeaderButton::mousePressEvent(QMouseEvent *event)
void ChatWidgetHeaderButton::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
this->mouseDown = true;
_mouseDown = true;
update();
}
}
void
ChatWidgetHeaderButton::mouseReleaseEvent(QMouseEvent *event)
void ChatWidgetHeaderButton::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
this->mouseDown = false;
_mouseDown = false;
update();
@ -69,36 +65,32 @@ ChatWidgetHeaderButton::mouseReleaseEvent(QMouseEvent *event)
}
}
void
ChatWidgetHeaderButton::enterEvent(QEvent *)
void ChatWidgetHeaderButton::enterEvent(QEvent *)
{
this->mouseOver = true;
_mouseOver = true;
update();
}
void
ChatWidgetHeaderButton::leaveEvent(QEvent *)
void ChatWidgetHeaderButton::leaveEvent(QEvent *)
{
this->mouseOver = false;
_mouseOver = false;
update();
}
void
ChatWidgetHeaderButton::labelMouseUp()
void ChatWidgetHeaderButton::labelMouseUp()
{
this->mouseDown = false;
_mouseDown = false;
update();
emit clicked();
}
void
ChatWidgetHeaderButton::labelMouseDown()
void ChatWidgetHeaderButton::labelMouseDown()
{
this->mouseDown = true;
_mouseDown = true;
update();
}

View file

@ -18,10 +18,9 @@ class ChatWidgetHeaderButton : public QWidget
public:
explicit ChatWidgetHeaderButton(int spacing = 6);
SignalLabel &
getLabel()
SignalLabel &getLabel()
{
return label;
return _label;
}
signals:
@ -37,11 +36,11 @@ protected:
void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
private:
QHBoxLayout hbox;
SignalLabel label;
QHBoxLayout _hbox;
SignalLabel _label;
bool mouseOver;
bool mouseDown;
bool _mouseOver;
bool _mouseDown;
void labelMouseUp();
void labelMouseDown();

View file

@ -2,7 +2,7 @@
#include "chatwidget.h"
#include "colorscheme.h"
#include "ircmanager.h"
#include "settings.h"
#include "settingsmanager.h"
#include <QCompleter>
#include <QPainter>
@ -12,44 +12,43 @@ namespace chatterino {
namespace widgets {
ChatWidgetInput::ChatWidgetInput(ChatWidget *widget)
: chatWidget(widget)
, hbox()
, vbox()
, editContainer()
, edit()
, textLengthLabel()
, emotesLabel(0)
: _chatWidget(widget)
, _hbox()
, _vbox()
, _editContainer()
, _edit()
, _textLengthLabel()
, _emotesLabel(0)
{
this->setLayout(&this->hbox);
this->setMaximumHeight(150);
this->hbox.setMargin(4);
setLayout(&_hbox);
setMaximumHeight(150);
_hbox.setMargin(4);
this->hbox.addLayout(&this->editContainer);
this->hbox.addLayout(&this->vbox);
_hbox.addLayout(&_editContainer);
_hbox.addLayout(&_vbox);
this->editContainer.addWidget(&this->edit);
this->editContainer.setMargin(4);
_editContainer.addWidget(&_edit);
_editContainer.setMargin(4);
this->vbox.addWidget(&this->textLengthLabel);
this->vbox.addStretch(1);
this->vbox.addWidget(&this->emotesLabel);
_vbox.addWidget(&_textLengthLabel);
_vbox.addStretch(1);
_vbox.addWidget(&_emotesLabel);
this->textLengthLabel.setText("100");
this->textLengthLabel.setAlignment(Qt::AlignRight);
this->emotesLabel.getLabel().setTextFormat(Qt::RichText);
this->emotesLabel.getLabel().setText(
_textLengthLabel.setText("100");
_textLengthLabel.setAlignment(Qt::AlignRight);
_emotesLabel.getLabel().setTextFormat(Qt::RichText);
_emotesLabel.getLabel().setText(
"<img src=':/images/Emoji_Color_1F60A_19.png' width='12' height='12' "
"/>");
QObject::connect(&edit, &ResizingTextEdit::textChanged, this,
QObject::connect(&_edit, &ResizingTextEdit::textChanged, this,
&ChatWidgetInput::editTextChanged);
// QObject::connect(&edit, &ResizingTextEdit::keyPressEvent, this,
// &ChatWidgetInput::editKeyPressed);
this->refreshTheme();
this->setMessageLengthVisisble(
Settings::getInstance().showMessageLength.get());
refreshTheme();
setMessageLengthVisisble(SettingsManager::getInstance().showMessageLength.get());
QStringList list;
list.append("asd");
@ -57,20 +56,20 @@ ChatWidgetInput::ChatWidgetInput(ChatWidget *widget)
list.append("asdg");
list.append("asdh");
QCompleter *completer = new QCompleter(list, &edit);
QCompleter *completer = new QCompleter(list, &_edit);
completer->setWidget(&edit);
completer->setWidget(&_edit);
this->edit.keyPressed.connect([this, completer](QKeyEvent *event) {
_edit.keyPressed.connect([this, completer](QKeyEvent *event) {
if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
auto ptr = this->chatWidget->getChannel();
auto ptr = _chatWidget->getChannel();
Channel *c = ptr.get();
if (c != nullptr) {
IrcManager::send("PRIVMSG #" + c->getName() + ": " +
this->edit.toPlainText());
IrcManager::getInstance().send("PRIVMSG #" + c->getName() + ": " +
_edit.toPlainText());
event->accept();
this->edit.setText(QString());
_edit.setText(QString());
}
}
// else {
@ -97,20 +96,18 @@ ChatWidgetInput::~ChatWidgetInput()
*/
}
void
ChatWidgetInput::refreshTheme()
void ChatWidgetInput::refreshTheme()
{
QPalette palette;
palette.setColor(QPalette::Foreground, ColorScheme::getInstance().Text);
this->textLengthLabel.setPalette(palette);
_textLengthLabel.setPalette(palette);
edit.setStyleSheet(ColorScheme::getInstance().InputStyleSheet);
_edit.setStyleSheet(ColorScheme::getInstance().InputStyleSheet);
}
void
ChatWidgetInput::editTextChanged()
void ChatWidgetInput::editTextChanged()
{
}
@ -124,8 +121,7 @@ ChatWidgetInput::editTextChanged()
// }
//}
void
ChatWidgetInput::paintEvent(QPaintEvent *)
void ChatWidgetInput::paintEvent(QPaintEvent *)
{
QPainter painter(this);
@ -134,13 +130,12 @@ ChatWidgetInput::paintEvent(QPaintEvent *)
painter.drawRect(0, 0, width() - 1, height() - 1);
}
void
ChatWidgetInput::resizeEvent(QResizeEvent *)
void ChatWidgetInput::resizeEvent(QResizeEvent *)
{
if (height() == this->maximumHeight()) {
edit.setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
if (height() == maximumHeight()) {
_edit.setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
} else {
edit.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_edit.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
}
}

View file

@ -31,21 +31,20 @@ protected:
void resizeEvent(QResizeEvent *);
private:
ChatWidget *chatWidget;
ChatWidget *_chatWidget;
QHBoxLayout hbox;
QVBoxLayout vbox;
QHBoxLayout editContainer;
ResizingTextEdit edit;
QLabel textLengthLabel;
ChatWidgetHeaderButton emotesLabel;
QHBoxLayout _hbox;
QVBoxLayout _vbox;
QHBoxLayout _editContainer;
ResizingTextEdit _edit;
QLabel _textLengthLabel;
ChatWidgetHeaderButton _emotesLabel;
private slots:
void refreshTheme();
void
setMessageLengthVisisble(bool value)
void setMessageLengthVisisble(bool value)
{
this->textLengthLabel.setHidden(!value);
_textLengthLabel.setHidden(!value);
}
void editTextChanged();
// void editKeyPressed(QKeyEvent *event);

View file

@ -1,9 +1,10 @@
#include "widgets/chatwidgetview.h"
#include "channels.h"
#include "channelmanager.h"
#include "colorscheme.h"
#include "messages/message.h"
#include "messages/wordpart.h"
#include "settings.h"
#include "settingsmanager.h"
#include "ui_userpopup.h"
#include "widgets/chatwidget.h"
#include <math.h>
@ -18,46 +19,46 @@ namespace widgets {
ChatWidgetView::ChatWidgetView(ChatWidget *parent)
: QWidget()
, chatWidget(parent)
, scrollbar(this)
, onlyUpdateEmotes(false)
, _chatWidget(parent)
, _scrollbar(this)
, _userPopupWidget(_chatWidget->getChannel())
, _onlyUpdateEmotes(false)
, _mouseDown(false)
, _lastPressPosition()
{
this->setAttribute(Qt::WA_OpaquePaintEvent);
this->scrollbar.setSmallChange(5);
_scrollbar.setSmallChange(5);
this->setMouseTracking(true);
QObject::connect(&Settings::getInstance(), &Settings::wordTypeMaskChanged,
this, &ChatWidgetView::wordTypeMaskChanged);
QObject::connect(&SettingsManager::getInstance(), &SettingsManager::wordTypeMaskChanged, this,
&ChatWidgetView::wordTypeMaskChanged);
this->scrollbar.getCurrentValueChanged().connect([this] { update(); });
_scrollbar.getCurrentValueChanged().connect([this] { update(); });
}
ChatWidgetView::~ChatWidgetView()
{
QObject::disconnect(&Settings::getInstance(),
&Settings::wordTypeMaskChanged, this,
&ChatWidgetView::wordTypeMaskChanged);
QObject::disconnect(&SettingsManager::getInstance(), &SettingsManager::wordTypeMaskChanged,
this, &ChatWidgetView::wordTypeMaskChanged);
}
bool
ChatWidgetView::layoutMessages()
bool ChatWidgetView::layoutMessages()
{
auto messages = chatWidget->getMessagesSnapshot();
auto messages = _chatWidget->getMessagesSnapshot();
if (messages.getLength() == 0) {
this->scrollbar.setVisible(false);
_scrollbar.setVisible(false);
return false;
}
bool showScrollbar = false, redraw = false;
int start = this->scrollbar.getCurrentValue();
int start = _scrollbar.getCurrentValue();
// layout the visible messages in the view
if (messages.getLength() > start) {
int y = -(messages[start].get()->getHeight() *
(fmod(this->scrollbar.getCurrentValue(), 1)));
int y = -(messages[start].get()->getHeight() * (fmod(_scrollbar.getCurrentValue(), 1)));
for (int i = start; i < messages.getLength(); ++i) {
auto messagePtr = messages[i];
@ -84,39 +85,47 @@ ChatWidgetView::layoutMessages()
h -= message->getHeight();
if (h < 0) {
this->scrollbar.setLargeChange((messages.getLength() - i) +
(qreal)h / message->getHeight());
this->scrollbar.setDesiredValue(this->scrollbar.getDesiredValue());
_scrollbar.setLargeChange((messages.getLength() - i) + (qreal)h / message->getHeight());
_scrollbar.setDesiredValue(_scrollbar.getDesiredValue());
showScrollbar = true;
break;
}
}
this->scrollbar.setVisible(showScrollbar);
_scrollbar.setVisible(showScrollbar);
if (!showScrollbar) {
this->scrollbar.setDesiredValue(0);
_scrollbar.setDesiredValue(0);
}
this->scrollbar.setMaximum(messages.getLength());
_scrollbar.setMaximum(messages.getLength());
return redraw;
}
void
ChatWidgetView::resizeEvent(QResizeEvent *)
void ChatWidgetView::updateGifEmotes()
{
this->scrollbar.resize(this->scrollbar.width(), height());
this->scrollbar.move(width() - this->scrollbar.width(), 0);
_onlyUpdateEmotes = true;
this->update();
}
ScrollBar *ChatWidgetView::getScrollbar()
{
return &_scrollbar;
}
void ChatWidgetView::resizeEvent(QResizeEvent *)
{
_scrollbar.resize(_scrollbar.width(), height());
_scrollbar.move(width() - _scrollbar.width(), 0);
layoutMessages();
update();
}
void
ChatWidgetView::paintEvent(QPaintEvent *event)
void ChatWidgetView::paintEvent(QPaintEvent *event)
{
QPainter _painter(this);
@ -125,10 +134,10 @@ ChatWidgetView::paintEvent(QPaintEvent *event)
ColorScheme &scheme = ColorScheme::getInstance();
// only update gif emotes
if (onlyUpdateEmotes) {
onlyUpdateEmotes = false;
if (_onlyUpdateEmotes) {
_onlyUpdateEmotes = false;
for (GifEmoteData &item : this->gifEmotes) {
for (GifEmoteData &item : _gifEmotes) {
_painter.fillRect(item.rect, scheme.ChatBackground);
_painter.drawPixmap(item.rect, *item.image->getPixmap());
@ -138,7 +147,7 @@ ChatWidgetView::paintEvent(QPaintEvent *event)
}
// update all messages
gifEmotes.clear();
_gifEmotes.clear();
_painter.fillRect(rect(), scheme.ChatBackground);
@ -175,16 +184,15 @@ ChatWidgetView::paintEvent(QPaintEvent *event)
painter.fillRect(QRect(0, 9, 500, 2), QColor(0, 0, 0));*/
auto messages = chatWidget->getMessagesSnapshot();
auto messages = _chatWidget->getMessagesSnapshot();
int start = this->scrollbar.getCurrentValue();
int start = _scrollbar.getCurrentValue();
if (start >= messages.getLength()) {
return;
}
int y = -(messages[start].get()->getHeight() *
(fmod(this->scrollbar.getCurrentValue(), 1)));
int y = -(messages[start].get()->getHeight() * (fmod(_scrollbar.getCurrentValue(), 1)));
for (int i = start; i < messages.getLength(); ++i) {
messages::MessageRef *messageRef = messages[i].get();
@ -205,20 +213,17 @@ ChatWidgetView::paintEvent(QPaintEvent *event)
QPainter painter(buffer);
painter.fillRect(buffer->rect(), scheme.ChatBackground);
for (messages::WordPart const &wordPart :
messageRef->getWordParts()) {
for (messages::WordPart const &wordPart : messageRef->getWordParts()) {
// image
if (wordPart.getWord().isImage()) {
messages::LazyLoadedImage &lli =
wordPart.getWord().getImage();
messages::LazyLoadedImage &lli = wordPart.getWord().getImage();
const QPixmap *image = lli.getPixmap();
if (image != NULL) {
painter.drawPixmap(
QRect(wordPart.getX(), wordPart.getY(),
wordPart.getWidth(), wordPart.getHeight()),
*image);
painter.drawPixmap(QRect(wordPart.getX(), wordPart.getY(),
wordPart.getWidth(), wordPart.getHeight()),
*image);
}
}
// text
@ -230,10 +235,8 @@ ChatWidgetView::paintEvent(QPaintEvent *event)
painter.setPen(color);
painter.setFont(wordPart.getWord().getFont());
painter.drawText(
QRectF(wordPart.getX(), wordPart.getY(), 10000, 10000),
wordPart.getText(),
QTextOption(Qt::AlignLeft | Qt::AlignTop));
painter.drawText(QRectF(wordPart.getX(), wordPart.getY(), 10000, 10000),
wordPart.getText(), QTextOption(Qt::AlignLeft | Qt::AlignTop));
}
}
@ -248,12 +251,12 @@ ChatWidgetView::paintEvent(QPaintEvent *event)
if (lli.getAnimated()) {
GifEmoteData data;
data.image = &lli;
QRect rect(wordPart.getX(), wordPart.getY() + y,
wordPart.getWidth(), wordPart.getHeight());
QRect rect(wordPart.getX(), wordPart.getY() + y, wordPart.getWidth(),
wordPart.getHeight());
data.rect = rect;
gifEmotes.push_back(data);
_gifEmotes.push_back(data);
}
}
}
@ -269,27 +272,24 @@ ChatWidgetView::paintEvent(QPaintEvent *event)
}
}
for (GifEmoteData &item : this->gifEmotes) {
for (GifEmoteData &item : _gifEmotes) {
_painter.fillRect(item.rect, scheme.ChatBackground);
_painter.drawPixmap(item.rect, *item.image->getPixmap());
}
}
void
ChatWidgetView::wheelEvent(QWheelEvent *event)
void ChatWidgetView::wheelEvent(QWheelEvent *event)
{
if (this->scrollbar.isVisible()) {
this->scrollbar.setDesiredValue(
this->scrollbar.getDesiredValue() -
event->delta() / 10.0 *
Settings::getInstance().mouseScrollMultiplier.get(),
if (_scrollbar.isVisible()) {
_scrollbar.setDesiredValue(
_scrollbar.getDesiredValue() -
event->delta() / 10.0 * SettingsManager::getInstance().mouseScrollMultiplier.get(),
true);
}
}
void
ChatWidgetView::mouseMoveEvent(QMouseEvent *event)
void ChatWidgetView::mouseMoveEvent(QMouseEvent *event)
{
std::shared_ptr<messages::MessageRef> message;
QPoint relativePos;
@ -311,32 +311,86 @@ ChatWidgetView::mouseMoveEvent(QMouseEvent *event)
int index = message->getSelectionIndex(relativePos);
qDebug() << index;
if (hoverWord.getLink().getIsValid()) {
this->setCursor(Qt::PointingHandCursor);
qDebug() << hoverWord.getLink().getValue();
} else {
this->setCursor(Qt::ArrowCursor);
}
}
bool
ChatWidgetView::tryGetMessageAt(QPoint p,
std::shared_ptr<messages::MessageRef> &_message,
QPoint &relativePos)
void ChatWidgetView::mousePressEvent(QMouseEvent *event)
{
auto messages = chatWidget->getMessagesSnapshot();
_mouseDown = true;
_lastPressPosition = event->screenPos();
}
int start = this->scrollbar.getCurrentValue();
static float distanceBetweenPoints(const QPointF &p1, const QPointF &p2)
{
QPointF tmp = p1 - p2;
float distance = 0.f;
distance += tmp.x() * tmp.x();
distance += tmp.y() * tmp.y();
return std::sqrt(distance);
}
void ChatWidgetView::mouseReleaseEvent(QMouseEvent *event)
{
if (!_mouseDown) {
// We didn't grab the mouse press, so we shouldn't be handling the mouse
// release
return;
}
_mouseDown = false;
float distance = distanceBetweenPoints(_lastPressPosition, event->screenPos());
qDebug() << "Distance: " << distance;
if (fabsf(distance) > 15.f) {
// It wasn't a proper click, so we don't care about that here
return;
}
// If you clicked and released less than X pixels away, it counts
// as a click!
// show user thing pajaW
std::shared_ptr<messages::MessageRef> message;
QPoint relativePos;
if (!tryGetMessageAt(event->pos(), message, relativePos)) {
// No message at clicked position
_userPopupWidget.hide();
return;
}
auto _message = message->getMessage();
auto user = _message->getUserName();
qDebug() << "Clicked " << user << "s message";
_userPopupWidget.setName(user);
_userPopupWidget.move(event->screenPos().toPoint());
_userPopupWidget.show();
_userPopupWidget.setFocus();
}
bool ChatWidgetView::tryGetMessageAt(QPoint p, std::shared_ptr<messages::MessageRef> &_message,
QPoint &relativePos)
{
auto messages = _chatWidget->getMessagesSnapshot();
int start = _scrollbar.getCurrentValue();
if (start >= messages.getLength()) {
return false;
}
int y = -(messages[start].get()->getHeight() *
(fmod(this->scrollbar.getCurrentValue(), 1))) +
12;
int y = -(messages[start].get()->getHeight() * (fmod(_scrollbar.getCurrentValue(), 1))) + 12;
for (int i = start; i < messages.getLength(); ++i) {
auto message = messages[i];
@ -352,5 +406,5 @@ ChatWidgetView::tryGetMessageAt(QPoint p,
return false;
}
}
}
} // namespace widgets
} // namespace chatterino

View file

@ -6,6 +6,7 @@
#include "messages/messageref.h"
#include "messages/word.h"
#include "widgets/scrollbar.h"
#include "widgets/userpopupwidget.h"
#include <QPaintEvent>
#include <QScroller>
@ -18,20 +19,14 @@ class ChatWidget;
class ChatWidgetView : public QWidget
{
Q_OBJECT
public:
explicit ChatWidgetView(ChatWidget *parent);
~ChatWidgetView();
bool layoutMessages();
void
updateGifEmotes()
{
this->onlyUpdateEmotes = true;
this->update();
}
void updateGifEmotes();
ScrollBar *getScrollbar();
protected:
void resizeEvent(QResizeEvent *);
@ -40,9 +35,10 @@ protected:
void wheelEvent(QWheelEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
bool tryGetMessageAt(QPoint p,
std::shared_ptr<messages::MessageRef> &message,
bool tryGetMessageAt(QPoint p, std::shared_ptr<messages::MessageRef> &message,
QPoint &relativePos);
private:
@ -51,22 +47,27 @@ private:
QRect rect;
};
std::vector<GifEmoteData> gifEmotes;
std::vector<GifEmoteData> _gifEmotes;
ChatWidget *chatWidget;
ChatWidget *_chatWidget;
ScrollBar scrollbar;
bool onlyUpdateEmotes;
ScrollBar _scrollbar;
UserPopupWidget _userPopupWidget;
bool _onlyUpdateEmotes;
// Mouse event variables
bool _mouseDown;
QPointF _lastPressPosition;
private slots:
void
wordTypeMaskChanged()
void wordTypeMaskChanged()
{
layoutMessages();
update();
}
};
}
}
} // namespace widgets
} // namespace chatterino
#endif // CHATVIEW_H

141
widgets/fancybutton.cpp Normal file
View file

@ -0,0 +1,141 @@
#include "fancybutton.h"
#include <QDebug>
#include <QPainter>
namespace chatterino {
namespace widgets {
FancyButton::FancyButton(QWidget *parent)
: QWidget(parent)
, _selected()
, _mouseOver()
, _mouseDown()
, _mousePos()
, _hoverMultiplier()
, _effectTimer()
, _mouseEffectColor(QColor(255, 255, 255))
{
connect(&_effectTimer, &QTimer::timeout, this, &FancyButton::onMouseEffectTimeout);
_effectTimer.setInterval(20);
_effectTimer.start();
}
void FancyButton::setMouseEffectColor(QColor color)
{
_mouseEffectColor = color;
}
void FancyButton::paintEvent(QPaintEvent *)
{
QPainter painter;
fancyPaint(painter);
}
void FancyButton::fancyPaint(QPainter &painter)
{
QColor &c = _mouseEffectColor;
if (_hoverMultiplier > 0) {
QRadialGradient gradient(_mousePos.x(), _mousePos.y(), 50, _mousePos.x(), _mousePos.y());
gradient.setColorAt(0, QColor(c.red(), c.green(), c.blue(), (int)(24 * _hoverMultiplier)));
gradient.setColorAt(1, QColor(c.red(), c.green(), c.blue(), (int)(12 * _hoverMultiplier)));
painter.fillRect(this->rect(), gradient);
}
for (auto effect : _clickEffects) {
QRadialGradient gradient(effect.position.x(), effect.position.y(),
effect.progress * (float)width() * 2, effect.position.x(),
effect.position.y());
gradient.setColorAt(
0, QColor(c.red(), c.green(), c.blue(), (int)((1 - effect.progress) * 95)));
gradient.setColorAt(
0.9999, QColor(c.red(), c.green(), c.blue(), (int)((1 - effect.progress) * 95)));
gradient.setColorAt(1, QColor(c.red(), c.green(), c.blue(), (int)(0)));
painter.fillRect(this->rect(), gradient);
}
}
void FancyButton::enterEvent(QEvent *)
{
_mouseOver = true;
}
void FancyButton::leaveEvent(QEvent *)
{
_mouseOver = false;
}
void FancyButton::mousePressEvent(QMouseEvent *event)
{
if (event->button() != Qt::LeftButton) {
return;
}
_clickEffects.push_back(ClickEffect(event->pos()));
_mouseDown = true;
}
void FancyButton::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() != Qt::LeftButton) {
return;
}
_mouseDown = false;
}
void FancyButton::mouseMoveEvent(QMouseEvent *event)
{
_mousePos = event->pos();
}
void FancyButton::onMouseEffectTimeout()
{
bool performUpdate = false;
if (_selected) {
if (_hoverMultiplier != 0) {
_hoverMultiplier = std::max(0.0, _hoverMultiplier - 0.1);
performUpdate = true;
}
} else if (_mouseOver) {
if (_hoverMultiplier != 1) {
_hoverMultiplier = std::min(1.0, _hoverMultiplier + 0.5);
performUpdate = true;
}
} else {
if (_hoverMultiplier != 0) {
_hoverMultiplier = std::max(0.0, _hoverMultiplier - 0.3);
performUpdate = true;
}
}
if (_clickEffects.size() != 0) {
performUpdate = true;
for (auto it = _clickEffects.begin(); it != _clickEffects.end();) {
(*it).progress += _mouseDown ? 0.02 : 0.07;
if ((*it).progress >= 1.0) {
it = _clickEffects.erase(it);
} else {
it++;
}
}
}
if (performUpdate) {
update();
}
}
}
}

55
widgets/fancybutton.h Normal file
View file

@ -0,0 +1,55 @@
#ifndef FANCYBUTTON_H
#define FANCYBUTTON_H
#include <QMouseEvent>
#include <QPainter>
#include <QPoint>
#include <QTimer>
#include <QWidget>
namespace chatterino {
namespace widgets {
class FancyButton : public QWidget
{
struct ClickEffect {
float progress;
QPoint position;
ClickEffect(QPoint position)
: progress()
, position(position)
{
}
};
public:
FancyButton(QWidget *parent = nullptr);
void setMouseEffectColor(QColor color);
protected:
void paintEvent(QPaintEvent *) override;
void enterEvent(QEvent *) override;
void leaveEvent(QEvent *) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void fancyPaint(QPainter &painter);
private:
bool _selected;
bool _mouseOver;
bool _mouseDown;
QPoint _mousePos;
float _hoverMultiplier;
QTimer _effectTimer;
std::vector<ClickEffect> _clickEffects;
QColor _mouseEffectColor;
void onMouseEffectTimeout();
};
}
}
#endif // FANCYBUTTON_H

View file

@ -1,24 +1,46 @@
#include "widgets/mainwindow.h"
#include "colorscheme.h"
#include "settingsmanager.h"
#include "widgets/chatwidget.h"
#include "widgets/notebook.h"
#include <QDebug>
#include <QPalette>
#include <QVBoxLayout>
#include <boost/foreach.hpp>
#ifdef USEWINSDK
#include "Windows.h"
#endif
namespace chatterino {
namespace widgets {
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, notebook(this)
: QWidget(parent)
, _notebook(this)
, _loaded(false)
, _titleBar()
{
setCentralWidget(&this->notebook);
QVBoxLayout *layout = new QVBoxLayout(this);
// add titlebar
// if (SettingsManager::getInstance().useCustomWindowFrame.get()) {
// layout->addWidget(&_titleBar);
// }
layout->addWidget(&_notebook);
setLayout(layout);
// set margin
// if (SettingsManager::getInstance().useCustomWindowFrame.get()) {
// layout->setMargin(1);
// } else {
layout->setMargin(0);
// }
QPalette palette;
palette.setColor(QPalette::Background,
ColorScheme::getInstance().TabPanelBackground);
palette.setColor(QPalette::Background, ColorScheme::getInstance().TabPanelBackground);
setPalette(palette);
resize(1280, 800);
@ -28,10 +50,9 @@ MainWindow::~MainWindow()
{
}
void
MainWindow::layoutVisibleChatWidgets(Channel *channel)
void MainWindow::layoutVisibleChatWidgets(Channel *channel)
{
auto *page = notebook.getSelectedPage();
auto *page = _notebook.getSelectedPage();
if (page == NULL) {
return;
@ -43,17 +64,14 @@ MainWindow::layoutVisibleChatWidgets(Channel *channel)
ChatWidget *widget = *it;
if (channel == NULL || channel == widget->getChannel().get()) {
if (widget->getView().layoutMessages()) {
widget->getView().update();
}
widget->layoutMessages();
}
}
}
void
MainWindow::repaintVisibleChatWidgets(Channel *channel)
void MainWindow::repaintVisibleChatWidgets(Channel *channel)
{
auto *page = notebook.getSelectedPage();
auto *page = _notebook.getSelectedPage();
if (page == NULL) {
return;
@ -65,16 +83,14 @@ MainWindow::repaintVisibleChatWidgets(Channel *channel)
ChatWidget *widget = *it;
if (channel == NULL || channel == widget->getChannel().get()) {
widget->getView().layoutMessages();
widget->getView().update();
widget->layoutMessages();
}
}
}
void
MainWindow::repaintGifEmotes()
void MainWindow::repaintGifEmotes()
{
auto *page = notebook.getSelectedPage();
auto *page = _notebook.getSelectedPage();
if (page == NULL) {
return;
@ -85,37 +101,43 @@ MainWindow::repaintGifEmotes()
for (auto it = widgets.begin(); it != widgets.end(); ++it) {
ChatWidget *widget = *it;
widget->getView().updateGifEmotes();
widget->updateGifEmotes();
}
}
void
MainWindow::load(const boost::property_tree::ptree &tree)
void MainWindow::load(const boost::property_tree::ptree &tree)
{
this->notebook.load(tree);
this->_notebook.load(tree);
this->loaded = true;
_loaded = true;
}
boost::property_tree::ptree
MainWindow::save()
boost::property_tree::ptree MainWindow::save()
{
boost::property_tree::ptree child;
child.put("type", "main");
this->notebook.save(child);
_notebook.save(child);
return child;
}
void
MainWindow::loadDefaults()
void MainWindow::loadDefaults()
{
this->notebook.loadDefaults();
_notebook.loadDefaults();
this->loaded = true;
_loaded = true;
}
bool MainWindow::isLoaded() const
{
return _loaded;
}
Notebook &MainWindow::getNotebook()
{
return _notebook;
}
} // namespace widgets
} // namespace chatterino

View file

@ -2,38 +2,39 @@
#define MAINWINDOW_H
#include "widgets/notebook.h"
#include "widgets/titlebar.h"
#include <platform/borderless/qwinwidget.h>
#include <QMainWindow>
#include <boost/property_tree/ptree.hpp>
namespace chatterino {
namespace widgets {
class MainWindow : public QMainWindow
class MainWindow : public QWidget
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
Notebook notebook;
void layoutVisibleChatWidgets(Channel *channel = NULL);
void repaintVisibleChatWidgets(Channel *channel = NULL);
void layoutVisibleChatWidgets(Channel *channel = nullptr);
void repaintVisibleChatWidgets(Channel *channel = nullptr);
void repaintGifEmotes();
void load(const boost::property_tree::ptree &tree);
boost::property_tree::ptree save();
void loadDefaults();
bool
isLoaded() const
{
return this->loaded;
}
bool isLoaded() const;
Notebook &getNotebook();
private:
bool loaded = false;
Notebook _notebook;
bool _loaded;
TitleBar _titleBar;
};
} // namespace widgets

View file

@ -19,106 +19,99 @@ namespace widgets {
Notebook::Notebook(QWidget *parent)
: QWidget(parent)
, addButton(this)
, settingsButton(this)
, userButton(this)
, selectedPage(nullptr)
, _addButton(this)
, _settingsButton(this)
, _userButton(this)
, _selectedPage(nullptr)
{
connect(&this->settingsButton, SIGNAL(clicked()), this,
SLOT(settingsButtonClicked()));
connect(&this->userButton, SIGNAL(clicked()), this,
SLOT(usersButtonClicked()));
connect(&this->addButton, SIGNAL(clicked()), this,
SLOT(addPageButtonClicked()));
connect(&_settingsButton, SIGNAL(clicked()), this, SLOT(settingsButtonClicked()));
connect(&_userButton, SIGNAL(clicked()), this, SLOT(usersButtonClicked()));
connect(&_addButton, SIGNAL(clicked()), this, SLOT(addPageButtonClicked()));
this->settingsButton.resize(24, 24);
this->settingsButton.icon = NotebookButton::IconSettings;
_settingsButton.resize(24, 24);
_settingsButton.icon = NotebookButton::IconSettings;
this->userButton.resize(24, 24);
this->userButton.move(24, 0);
this->userButton.icon = NotebookButton::IconUser;
_userButton.resize(24, 24);
_userButton.move(24, 0);
_userButton.icon = NotebookButton::IconUser;
this->addButton.resize(24, 24);
_addButton.resize(24, 24);
Settings::getInstance().hidePreferencesButton.valueChanged.connect(
[this](const bool &) { this->performLayout(); });
Settings::getInstance().hideUserButton.valueChanged.connect(
[this](const bool &) { this->performLayout(); });
SettingsManager::getInstance().hidePreferencesButton.valueChanged.connect(
[this](const bool &) { performLayout(); });
SettingsManager::getInstance().hideUserButton.valueChanged.connect(
[this](const bool &) { performLayout(); });
}
NotebookPage *
Notebook::addPage(bool select)
NotebookPage *Notebook::addPage(bool select)
{
auto tab = new NotebookTab(this);
auto page = new NotebookPage(this, tab);
tab->show();
if (select || this->pages.count() == 0) {
if (select || _pages.count() == 0) {
this->select(page);
}
this->pages.append(page);
_pages.append(page);
this->performLayout();
performLayout();
return page;
}
void
Notebook::removePage(NotebookPage *page)
void Notebook::removePage(NotebookPage *page)
{
int index = this->pages.indexOf(page);
int index = _pages.indexOf(page);
if (pages.size() == 1) {
this->select(NULL);
} else if (index == pages.count() - 1) {
this->select(pages[index - 1]);
if (_pages.size() == 1) {
select(NULL);
} else if (index == _pages.count() - 1) {
select(_pages[index - 1]);
} else {
this->select(pages[index + 1]);
select(_pages[index + 1]);
}
delete page->tab;
delete page->getTab();
delete page;
this->pages.removeOne(page);
_pages.removeOne(page);
if (this->pages.size() == 0) {
if (_pages.size() == 0) {
addPage();
}
performLayout();
}
void
Notebook::select(NotebookPage *page)
void Notebook::select(NotebookPage *page)
{
if (page == this->selectedPage)
if (page == _selectedPage)
return;
if (page != nullptr) {
page->setHidden(false);
page->tab->setSelected(true);
page->tab->raise();
page->getTab()->setSelected(true);
page->getTab()->raise();
}
if (this->selectedPage != nullptr) {
this->selectedPage->setHidden(true);
this->selectedPage->tab->setSelected(false);
if (_selectedPage != nullptr) {
_selectedPage->setHidden(true);
_selectedPage->getTab()->setSelected(false);
}
this->selectedPage = page;
_selectedPage = page;
performLayout();
}
NotebookPage *
Notebook::tabAt(QPoint point, int &index)
NotebookPage *Notebook::tabAt(QPoint point, int &index)
{
int i = 0;
for (auto *page : pages) {
if (page->tab->getDesiredRect().contains(point)) {
for (auto *page : _pages) {
if (page->getTab()->getDesiredRect().contains(point)) {
index = i;
return page;
}
@ -130,97 +123,87 @@ Notebook::tabAt(QPoint point, int &index)
return nullptr;
}
void
Notebook::rearrangePage(NotebookPage *page, int index)
void Notebook::rearrangePage(NotebookPage *page, int index)
{
pages.move(pages.indexOf(page), index);
_pages.move(_pages.indexOf(page), index);
performLayout();
}
void
Notebook::performLayout(bool animated)
void Notebook::performLayout(bool animated)
{
int x = 0, y = 0;
if (Settings::getInstance().hidePreferencesButton.get()) {
settingsButton.hide();
if (SettingsManager::getInstance().hidePreferencesButton.get()) {
_settingsButton.hide();
} else {
settingsButton.show();
_settingsButton.show();
x += 24;
}
if (Settings::getInstance().hideUserButton.get()) {
userButton.hide();
if (SettingsManager::getInstance().hideUserButton.get()) {
_userButton.hide();
} else {
userButton.move(x, 0);
userButton.show();
_userButton.move(x, 0);
_userButton.show();
x += 24;
}
int tabHeight = 16;
bool first = true;
for (auto &i : this->pages) {
tabHeight = i->tab->height();
for (auto &i : _pages) {
tabHeight = i->getTab()->height();
if (!first &&
(i == this->pages.last() ? tabHeight : 0) + x + i->tab->width() >
width()) {
y += i->tab->height();
i->tab->moveAnimated(QPoint(0, y), animated);
x = i->tab->width();
if (!first && (i == _pages.last() ? tabHeight : 0) + x + i->getTab()->width() > width()) {
y += i->getTab()->height();
i->getTab()->moveAnimated(QPoint(0, y), animated);
x = i->getTab()->width();
} else {
i->tab->moveAnimated(QPoint(x, y), animated);
x += i->tab->width();
i->getTab()->moveAnimated(QPoint(x, y), animated);
x += i->getTab()->width();
}
first = false;
}
this->addButton.move(x, y);
_addButton.move(x, y);
if (this->selectedPage != nullptr) {
this->selectedPage->move(0, y + tabHeight);
this->selectedPage->resize(width(), height() - y - tabHeight);
if (_selectedPage != nullptr) {
_selectedPage->move(0, y + tabHeight);
_selectedPage->resize(width(), height() - y - tabHeight);
}
}
void
Notebook::resizeEvent(QResizeEvent *)
void Notebook::resizeEvent(QResizeEvent *)
{
performLayout(false);
}
void
Notebook::settingsButtonClicked()
void Notebook::settingsButtonClicked()
{
SettingsDialog *a = new SettingsDialog();
a->show();
}
void
Notebook::usersButtonClicked()
void Notebook::usersButtonClicked()
{
}
void
Notebook::addPageButtonClicked()
void Notebook::addPageButtonClicked()
{
addPage(true);
}
void
Notebook::load(const boost::property_tree::ptree &tree)
void Notebook::load(const boost::property_tree::ptree &tree)
{
// Read a list of tabs
try {
BOOST_FOREACH (const boost::property_tree::ptree::value_type &v,
tree.get_child("tabs.")) {
BOOST_FOREACH (const boost::property_tree::ptree::value_type &v, tree.get_child("tabs.")) {
bool select = v.second.get<bool>("selected", false);
auto page = this->addPage(select);
auto tab = page->tab;
auto page = addPage(select);
auto tab = page->getTab();
tab->load(v.second);
page->load(v.second);
}
@ -228,20 +211,19 @@ Notebook::load(const boost::property_tree::ptree &tree)
// can't read tabs
}
if (this->pages.size() == 0) {
if (_pages.size() == 0) {
// No pages saved, show default stuff
this->loadDefaults();
loadDefaults();
}
}
void
Notebook::save(boost::property_tree::ptree &tree)
void Notebook::save(boost::property_tree::ptree &tree)
{
boost::property_tree::ptree tabs;
// Iterate through all tabs and add them to our tabs property thing
for (const auto &page : this->pages) {
boost::property_tree::ptree pTab = page->tab->save();
for (const auto &page : _pages) {
boost::property_tree::ptree pTab = page->getTab()->save();
boost::property_tree::ptree pChats = page->save();
@ -255,10 +237,9 @@ Notebook::save(boost::property_tree::ptree &tree)
tree.add_child("tabs", tabs);
}
void
Notebook::loadDefaults()
void Notebook::loadDefaults()
{
this->addPage();
addPage();
}
} // namespace widgets

View file

@ -26,10 +26,9 @@ public:
void removePage(NotebookPage *page);
void select(NotebookPage *page);
NotebookPage *
getSelectedPage()
NotebookPage *getSelectedPage()
{
return selectedPage;
return _selectedPage;
}
void performLayout(bool animate = true);
@ -48,13 +47,13 @@ public slots:
void addPageButtonClicked();
private:
QList<NotebookPage *> pages;
QList<NotebookPage *> _pages;
NotebookButton addButton;
NotebookButton settingsButton;
NotebookButton userButton;
NotebookButton _addButton;
NotebookButton _settingsButton;
NotebookButton _userButton;
NotebookPage *selectedPage;
NotebookPage *_selectedPage;
public:
void load(const boost::property_tree::ptree &tree);

View file

@ -1,20 +1,22 @@
#include "widgets/notebookbutton.h"
#include "colorscheme.h"
#include "widgets/fancybutton.h"
#include <QMouseEvent>
#include <QPainter>
#include <QPainterPath>
#include <QRadialGradient>
namespace chatterino {
namespace widgets {
NotebookButton::NotebookButton(QWidget *parent)
: QWidget(parent)
: FancyButton(parent)
{
setMouseEffectColor(QColor(0, 0, 0));
}
void
NotebookButton::paintEvent(QPaintEvent *)
void NotebookButton::paintEvent(QPaintEvent *)
{
QPainter painter(this);
@ -23,29 +25,30 @@ NotebookButton::paintEvent(QPaintEvent *)
auto &colorScheme = ColorScheme::getInstance();
if (mouseDown) {
if (_mouseDown) {
background = colorScheme.TabSelectedBackground;
foreground = colorScheme.TabSelectedText;
} else if (mouseOver) {
} else if (_mouseOver) {
background = colorScheme.TabHoverBackground;
foreground = colorScheme.TabSelectedBackground;
} else {
background = colorScheme.TabPanelBackground;
foreground = colorScheme.TabSelectedBackground;
// foreground = colorScheme.TabSelectedBackground;
foreground = QColor(230, 230, 230);
}
painter.setPen(Qt::NoPen);
painter.fillRect(this->rect(), background);
float h = this->height(), w = this->width();
float h = height(), w = width();
if (icon == IconPlus) {
painter.fillRect(QRectF((h / 12) * 2 + 1, (h / 12) * 5 + 1,
w - ((h / 12) * 5), (h / 12) * 1),
foreground);
painter.fillRect(QRectF((h / 12) * 5 + 1, (h / 12) * 2 + 1,
(h / 12) * 1, w - ((h / 12) * 5)),
foreground);
painter.fillRect(
QRectF((h / 12) * 2 + 1, (h / 12) * 5 + 1, w - ((h / 12) * 5), (h / 12) * 1),
foreground);
painter.fillRect(
QRectF((h / 12) * 5 + 1, (h / 12) * 2 + 1, (h / 12) * 1, w - ((h / 12) * 5)),
foreground);
} else if (icon == IconUser) {
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
@ -74,10 +77,8 @@ NotebookButton::paintEvent(QPaintEvent *)
path.arcMoveTo(a, a, 6 * a, 6 * a, 0 - (360 / 32.0));
for (int i = 0; i < 8; i++) {
path.arcTo(a, a, 6 * a, 6 * a, i * (360 / 8.0) - (360 / 32.0),
(360 / 32.0));
path.arcTo(2 * a, 2 * a, 4 * a, 4 * a,
i * (360 / 8.0) + (360 / 32.0), (360 / 32.0));
path.arcTo(a, a, 6 * a, 6 * a, i * (360 / 8.0) - (360 / 32.0), (360 / 32.0));
path.arcTo(2 * a, 2 * a, 4 * a, 4 * a, i * (360 / 8.0) + (360 / 32.0), (360 / 32.0));
}
painter.fillPath(path, foreground);
@ -85,44 +86,21 @@ NotebookButton::paintEvent(QPaintEvent *)
painter.setBrush(background);
painter.drawEllipse(3 * a, 3 * a, 2 * a, 2 * a);
}
fancyPaint(painter);
}
void
NotebookButton::mousePressEvent(QMouseEvent *event)
void NotebookButton::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
mouseDown = true;
_mouseDown = false;
this->update();
}
}
void
NotebookButton::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
mouseDown = false;
this->update();
update();
emit clicked();
}
}
void
NotebookButton::enterEvent(QEvent *)
{
mouseOver = true;
this->update();
}
void
NotebookButton::leaveEvent(QEvent *)
{
mouseOver = false;
this->update();
FancyButton::mouseReleaseEvent(event);
}
}
}

View file

@ -1,14 +1,17 @@
#ifndef NOTEBOOKBUTTON_H
#define NOTEBOOKBUTTON_H
#include "fancybutton.h"
#include <QWidget>
namespace chatterino {
namespace widgets {
class NotebookButton : public QWidget
class NotebookButton : public FancyButton
{
Q_OBJECT
public:
static const int IconPlus = 0;
static const int IconUser = 1;
@ -18,18 +21,17 @@ public:
NotebookButton(QWidget *parent);
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
void enterEvent(QEvent *) Q_DECL_OVERRIDE;
void leaveEvent(QEvent *) Q_DECL_OVERRIDE;
protected:
void paintEvent(QPaintEvent *) override;
void mouseReleaseEvent(QMouseEvent *event) override;
signals:
void clicked();
private:
bool mouseOver = false;
bool mouseDown = false;
bool _mouseOver = false;
bool _mouseDown = false;
QPoint _mousePos;
};
}
}

View file

@ -21,49 +21,60 @@ std::pair<int, int> NotebookPage::dropPosition = std::pair<int, int>(-1, -1);
NotebookPage::NotebookPage(QWidget *parent, NotebookTab *tab)
: QWidget(parent)
, parentbox(this)
, chatWidgets()
, preview(this)
, _tab(tab)
, _parentbox(this)
, _chatWidgets()
, _preview(this)
{
this->tab = tab;
tab->page = this;
setHidden(true);
setAcceptDrops(true);
this->parentbox.addSpacing(2);
this->parentbox.addLayout(&this->hbox);
this->parentbox.setMargin(0);
_parentbox.addSpacing(2);
_parentbox.addLayout(&_hbox);
_parentbox.setMargin(0);
this->hbox.setSpacing(1);
this->hbox.setMargin(0);
_hbox.setSpacing(1);
_hbox.setMargin(0);
}
std::pair<int, int>
NotebookPage::removeFromLayout(ChatWidget *widget)
const std::vector<ChatWidget *> &NotebookPage::getChatWidgets() const
{
for (auto it = this->chatWidgets.begin(); it != this->chatWidgets.end();
++it) {
return _chatWidgets;
}
NotebookTab *NotebookPage::getTab() const
{
return _tab;
}
std::pair<int, int> NotebookPage::removeFromLayout(ChatWidget *widget)
{
// remove from chatWidgets vector
for (auto it = _chatWidgets.begin(); it != _chatWidgets.end(); ++it) {
if (*it == widget) {
this->chatWidgets.erase(it);
_chatWidgets.erase(it);
break;
}
}
for (int i = 0; i < this->hbox.count(); ++i) {
auto vbox = static_cast<QVBoxLayout *>(this->hbox.itemAt(i));
// remove from box and return location
for (int i = 0; i < _hbox.count(); ++i) {
auto vbox = static_cast<QVBoxLayout *>(_hbox.itemAt(i));
for (int j = 0; j < vbox->count(); ++j) {
if (vbox->itemAt(j)->widget() != widget)
if (vbox->itemAt(j)->widget() != widget) {
continue;
}
widget->setParent(NULL);
bool isLastItem = vbox->count() == 0;
if (isLastItem) {
this->hbox.removeItem(vbox);
_hbox.removeItem(vbox);
delete vbox;
}
@ -75,19 +86,17 @@ NotebookPage::removeFromLayout(ChatWidget *widget)
return std::pair<int, int>(-1, -1);
}
void
NotebookPage::addToLayout(
ChatWidget *widget,
std::pair<int, int> position = std::pair<int, int>(-1, -1))
void NotebookPage::addToLayout(ChatWidget *widget,
std::pair<int, int> position = std::pair<int, int>(-1, -1))
{
this->chatWidgets.push_back(widget);
_chatWidgets.push_back(widget);
// add vbox at the end
if (position.first < 0 || position.first >= this->hbox.count()) {
if (position.first < 0 || position.first >= _hbox.count()) {
auto vbox = new QVBoxLayout();
vbox->addWidget(widget);
this->hbox.addLayout(vbox, 1);
_hbox.addLayout(vbox, 1);
return;
}
@ -96,101 +105,91 @@ NotebookPage::addToLayout(
auto vbox = new QVBoxLayout();
vbox->addWidget(widget);
this->hbox.insertLayout(position.first, vbox, 1);
_hbox.insertLayout(position.first, vbox, 1);
return;
}
// add to existing vbox
auto vbox = static_cast<QVBoxLayout *>(this->hbox.itemAt(position.first));
auto vbox = static_cast<QVBoxLayout *>(_hbox.itemAt(position.first));
vbox->insertWidget(std::max(0, std::min(vbox->count(), position.second)),
widget);
vbox->insertWidget(std::max(0, std::min(vbox->count(), position.second)), widget);
}
void
NotebookPage::enterEvent(QEvent *)
void NotebookPage::enterEvent(QEvent *)
{
if (this->hbox.count() == 0) {
if (_hbox.count() == 0) {
setCursor(QCursor(Qt::PointingHandCursor));
} else {
setCursor(QCursor(Qt::ArrowCursor));
}
}
void
NotebookPage::leaveEvent(QEvent *)
void NotebookPage::leaveEvent(QEvent *)
{
}
void
NotebookPage::mouseReleaseEvent(QMouseEvent *event)
void NotebookPage::mouseReleaseEvent(QMouseEvent *event)
{
if (this->hbox.count() == 0 && event->button() == Qt::LeftButton) {
if (_hbox.count() == 0 && event->button() == Qt::LeftButton) {
// "Add Chat" was clicked
this->addToLayout(new ChatWidget(), std::pair<int, int>(-1, -1));
addToLayout(new ChatWidget(), std::pair<int, int>(-1, -1));
setCursor(QCursor(Qt::ArrowCursor));
}
}
void
NotebookPage::dragEnterEvent(QDragEnterEvent *event)
void NotebookPage::dragEnterEvent(QDragEnterEvent *event)
{
if (!event->mimeData()->hasFormat("chatterino/split"))
return;
if (isDraggingSplit) {
this->dropRegions.clear();
return;
}
if (this->hbox.count() == 0) {
this->dropRegions.push_back(
DropRegion(rect(), std::pair<int, int>(-1, -1)));
} else {
for (int i = 0; i < this->hbox.count() + 1; ++i) {
this->dropRegions.push_back(DropRegion(
QRect(((i * 4 - 1) * width() / this->hbox.count()) / 4, 0,
width() / this->hbox.count() / 2 + 1, height() + 1),
_dropRegions.clear();
std::pair<int, int>(i, -1)));
}
for (int i = 0; i < this->hbox.count(); ++i) {
auto vbox = static_cast<QVBoxLayout *>(this->hbox.itemAt(i));
for (int j = 0; j < vbox->count() + 1; ++j) {
this->dropRegions.push_back(DropRegion(
QRect(i * width() / this->hbox.count(),
((j * 2 - 1) * height() / vbox->count()) / 2,
width() / this->hbox.count() + 1,
height() / vbox->count() + 1),
std::pair<int, int>(i, j)));
}
}
if (_hbox.count() == 0) {
_dropRegions.push_back(DropRegion(rect(), std::pair<int, int>(-1, -1)));
} else {
for (int i = 0; i < _hbox.count() + 1; ++i) {
_dropRegions.push_back(DropRegion(QRect(((i * 4 - 1) * width() / _hbox.count()) / 4, 0,
width() / _hbox.count() / 2 + 1, height() + 1),
std::pair<int, int>(i, -1)));
}
setPreviewRect(event->pos());
for (int i = 0; i < _hbox.count(); ++i) {
auto vbox = static_cast<QVBoxLayout *>(_hbox.itemAt(i));
event->acceptProposedAction();
for (int j = 0; j < vbox->count() + 1; ++j) {
_dropRegions.push_back(DropRegion(
QRect(i * width() / _hbox.count(), ((j * 2 - 1) * height() / vbox->count()) / 2,
width() / _hbox.count() + 1, height() / vbox->count() + 1),
std::pair<int, int>(i, j)));
}
}
}
setPreviewRect(event->pos());
event->acceptProposedAction();
}
void
NotebookPage::dragMoveEvent(QDragMoveEvent *event)
void NotebookPage::dragMoveEvent(QDragMoveEvent *event)
{
setPreviewRect(event->pos());
}
void
NotebookPage::setPreviewRect(QPoint mousePos)
void NotebookPage::setPreviewRect(QPoint mousePos)
{
for (DropRegion region : this->dropRegions) {
for (DropRegion region : _dropRegions) {
if (region.rect.contains(mousePos)) {
this->preview.setBounds(region.rect);
_preview.setBounds(region.rect);
if (!this->preview.isVisible()) {
this->preview.show();
this->preview.raise();
if (!_preview.isVisible()) {
_preview.show();
_preview.raise();
}
dropPosition = region.position;
@ -199,17 +198,15 @@ NotebookPage::setPreviewRect(QPoint mousePos)
}
}
this->preview.hide();
_preview.hide();
}
void
NotebookPage::dragLeaveEvent(QDragLeaveEvent *event)
void NotebookPage::dragLeaveEvent(QDragLeaveEvent *event)
{
this->preview.hide();
_preview.hide();
}
void
NotebookPage::dropEvent(QDropEvent *event)
void NotebookPage::dropEvent(QDropEvent *event)
{
if (isDraggingSplit) {
event->acceptProposedAction();
@ -219,33 +216,28 @@ NotebookPage::dropEvent(QDropEvent *event)
addToLayout(NotebookPage::draggingSplit, dropPosition);
}
this->preview.hide();
_preview.hide();
}
void
NotebookPage::paintEvent(QPaintEvent *)
void NotebookPage::paintEvent(QPaintEvent *)
{
QPainter painter(this);
if (this->hbox.count() == 0) {
if (_hbox.count() == 0) {
painter.fillRect(rect(), ColorScheme::getInstance().ChatBackground);
painter.fillRect(0, 0, width(), 2,
ColorScheme::getInstance().TabSelectedBackground);
painter.fillRect(0, 0, width(), 2, ColorScheme::getInstance().TabSelectedBackground);
painter.setPen(ColorScheme::getInstance().Text);
painter.drawText(rect(), "Add Chat", QTextOption(Qt::AlignCenter));
} else {
painter.fillRect(rect(),
ColorScheme::getInstance().TabSelectedBackground);
painter.fillRect(rect(), ColorScheme::getInstance().TabSelectedBackground);
painter.fillRect(0, 0, width(), 2,
ColorScheme::getInstance().TabSelectedBackground);
painter.fillRect(0, 0, width(), 2, ColorScheme::getInstance().TabSelectedBackground);
}
}
static std::pair<int, int>
getWidgetPositionInLayout(QLayout *layout, const ChatWidget *chatWidget)
static std::pair<int, int> getWidgetPositionInLayout(QLayout *layout, const ChatWidget *chatWidget)
{
for (int i = 0; i < layout->count(); ++i) {
printf("xD\n");
@ -254,10 +246,9 @@ getWidgetPositionInLayout(QLayout *layout, const ChatWidget *chatWidget)
return std::make_pair(-1, -1);
}
std::pair<int, int>
NotebookPage::getChatPosition(const ChatWidget *chatWidget)
std::pair<int, int> NotebookPage::getChatPosition(const ChatWidget *chatWidget)
{
auto layout = this->hbox.layout();
auto layout = _hbox.layout();
if (layout == nullptr) {
return std::make_pair(-1, -1);
@ -266,8 +257,7 @@ NotebookPage::getChatPosition(const ChatWidget *chatWidget)
return getWidgetPositionInLayout(layout, chatWidget);
}
void
NotebookPage::load(const boost::property_tree::ptree &tree)
void NotebookPage::load(const boost::property_tree::ptree &tree)
{
try {
int column = 0;
@ -276,7 +266,7 @@ NotebookPage::load(const boost::property_tree::ptree &tree)
for (const auto &innerV : v.second.get_child("")) {
auto widget = new ChatWidget();
widget->load(innerV.second);
this->addToLayout(widget, std::pair<int, int>(column, row));
addToLayout(widget, std::pair<int, int>(column, row));
++row;
}
++column;
@ -286,8 +276,7 @@ NotebookPage::load(const boost::property_tree::ptree &tree)
}
}
static void
saveFromLayout(QLayout *layout, boost::property_tree::ptree &tree)
static void saveFromLayout(QLayout *layout, boost::property_tree::ptree &tree)
{
for (int i = 0; i < layout->count(); ++i) {
auto item = layout->itemAt(i);
@ -323,17 +312,16 @@ saveFromLayout(QLayout *layout, boost::property_tree::ptree &tree)
}
}
boost::property_tree::ptree
NotebookPage::save()
boost::property_tree::ptree NotebookPage::save()
{
boost::property_tree::ptree tree;
auto layout = this->hbox.layout();
auto layout = _hbox.layout();
saveFromLayout(layout, tree);
/*
for (const auto &chat : this->chatWidgets) {
for (const auto &chat : chatWidgets) {
boost::property_tree::ptree child = chat->save();
// Set child position

View file

@ -23,34 +23,31 @@ class NotebookPage : public QWidget
Q_OBJECT
public:
NotebookPage(QWidget *parent, NotebookTab *tab);
NotebookTab *tab;
NotebookPage(QWidget *parent, NotebookTab *_tab);
std::pair<int, int> removeFromLayout(ChatWidget *widget);
void addToLayout(ChatWidget *widget, std::pair<int, int> position);
const std::vector<ChatWidget *> &
getChatWidgets() const
{
return chatWidgets;
}
const std::vector<ChatWidget *> &getChatWidgets() const;
NotebookTab *getTab() const;
static bool isDraggingSplit;
static ChatWidget *draggingSplit;
static std::pair<int, int> dropPosition;
protected:
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
void paintEvent(QPaintEvent *) override;
void enterEvent(QEvent *) override;
void leaveEvent(QEvent *) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) Q_DECL_OVERRIDE;
void dragMoveEvent(QDragMoveEvent *event) Q_DECL_OVERRIDE;
void dragLeaveEvent(QDragLeaveEvent *event) Q_DECL_OVERRIDE;
void dropEvent(QDropEvent *event) Q_DECL_OVERRIDE;
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
void dropEvent(QDropEvent *event) override;
private:
struct DropRegion {
QRect rect;
std::pair<int, int> position;
@ -62,15 +59,16 @@ protected:
}
};
QVBoxLayout parentbox;
QHBoxLayout hbox;
NotebookTab *_tab;
std::vector<ChatWidget *> chatWidgets;
std::vector<DropRegion> dropRegions;
QVBoxLayout _parentbox;
QHBoxLayout _hbox;
NotebookPageDropPreview preview;
std::vector<ChatWidget *> _chatWidgets;
std::vector<DropRegion> _dropRegions;
NotebookPageDropPreview _preview;
private:
void setPreviewRect(QPoint mousePos);
std::pair<int, int> getChatPosition(const ChatWidget *chatWidget);

View file

@ -17,8 +17,7 @@ NotebookPageDropPreview::NotebookPageDropPreview(QWidget *parent)
this->setHidden(true);
}
void
NotebookPageDropPreview::paintEvent(QPaintEvent *)
void NotebookPageDropPreview::paintEvent(QPaintEvent *)
{
QPainter painter(this);
@ -26,14 +25,12 @@ NotebookPageDropPreview::paintEvent(QPaintEvent *)
ColorScheme::getInstance().DropPreviewBackground);
}
void
NotebookPageDropPreview::hideEvent(QHideEvent *)
void NotebookPageDropPreview::hideEvent(QHideEvent *)
{
animate = false;
}
void
NotebookPageDropPreview::setBounds(const QRect &rect)
void NotebookPageDropPreview::setBounds(const QRect &rect)
{
if (rect == this->desiredGeometry) {
return;

View file

@ -1,6 +1,6 @@
#include "widgets/notebooktab.h"
#include "colorscheme.h"
#include "settings.h"
#include "settingsmanager.h"
#include "widgets/notebook.h"
#include <QPainter>
@ -10,75 +10,113 @@ namespace widgets {
NotebookTab::NotebookTab(Notebook *notebook)
: QWidget(notebook)
, posAnimation(this, "pos")
, posAnimated(false)
, posAnimationDesired()
, notebook(notebook)
, title("<no title>")
, selected(false)
, mouseOver(false)
, mouseDown(false)
, mouseOverX(false)
, mouseDownX(false)
, highlightStyle(HighlightNone)
, _posAnimation(this, "pos")
, _posAnimated(false)
, _posAnimationDesired()
, _notebook(notebook)
, _title("<no title>")
, _selected(false)
, _mouseOver(false)
, _mouseDown(false)
, _mouseOverX(false)
, _mouseDownX(false)
, _highlightStyle(HighlightNone)
{
this->calcSize();
this->setAcceptDrops(true);
posAnimation.setEasingCurve(QEasingCurve(QEasingCurve::InCubic));
_posAnimation.setEasingCurve(QEasingCurve(QEasingCurve::InCubic));
this->hideXConnection =
Settings::getInstance().hideTabX.valueChanged.connect(
boost::bind(&NotebookTab::hideTabXChanged, this, _1));
this->_hideXConnection = SettingsManager::getInstance().hideTabX.valueChanged.connect(
boost::bind(&NotebookTab::hideTabXChanged, this, _1));
this->setMouseTracking(true);
}
NotebookTab::~NotebookTab()
{
this->hideXConnection.disconnect();
this->_hideXConnection.disconnect();
}
void
NotebookTab::calcSize()
void NotebookTab::calcSize()
{
if (Settings::getInstance().hideTabX.get()) {
this->resize(this->fontMetrics().width(this->title) + 8, 24);
if (SettingsManager::getInstance().hideTabX.get()) {
resize(fontMetrics().width(_title) + 8, 24);
} else {
this->resize(this->fontMetrics().width(this->title) + 8 + 24, 24);
resize(fontMetrics().width(_title) + 8 + 24, 24);
}
if (this->parent() != nullptr) {
((Notebook *)this->parent())->performLayout(true);
if (parent() != nullptr) {
((Notebook *)parent())->performLayout(true);
}
}
void
NotebookTab::moveAnimated(QPoint pos, bool animated)
const QString &NotebookTab::getTitle() const
{
posAnimationDesired = pos;
return _title;
}
if ((this->window() != NULL && !this->window()->isVisible()) || !animated ||
posAnimated == false) {
void NotebookTab::setTitle(const QString &title)
{
_title = title;
}
bool NotebookTab::getSelected()
{
return _selected;
}
void NotebookTab::setSelected(bool value)
{
_selected = value;
update();
}
NotebookTab::HighlightStyle NotebookTab::getHighlightStyle() const
{
return _highlightStyle;
}
void NotebookTab::setHighlightStyle(HighlightStyle style)
{
_highlightStyle = style;
update();
}
QRect NotebookTab::getDesiredRect() const
{
return QRect(_posAnimationDesired, size());
}
void NotebookTab::hideTabXChanged(bool)
{
calcSize();
update();
}
void NotebookTab::moveAnimated(QPoint pos, bool animated)
{
_posAnimationDesired = pos;
if ((window() != NULL && !window()->isVisible()) || !animated || _posAnimated == false) {
move(pos);
posAnimated = true;
_posAnimated = true;
return;
}
if (this->posAnimation.endValue() == pos) {
if (_posAnimation.endValue() == pos) {
return;
}
this->posAnimation.stop();
this->posAnimation.setDuration(75);
this->posAnimation.setStartValue(this->pos());
this->posAnimation.setEndValue(pos);
this->posAnimation.start();
_posAnimation.stop();
_posAnimation.setDuration(75);
_posAnimation.setStartValue(this->pos());
_posAnimation.setEndValue(pos);
_posAnimation.start();
}
void
NotebookTab::paintEvent(QPaintEvent *)
void NotebookTab::paintEvent(QPaintEvent *)
{
QPainter painter(this);
@ -86,16 +124,16 @@ NotebookTab::paintEvent(QPaintEvent *)
auto &colorScheme = ColorScheme::getInstance();
if (this->selected) {
if (_selected) {
painter.fillRect(rect(), colorScheme.TabSelectedBackground);
fg = colorScheme.TabSelectedText;
} else if (this->mouseOver) {
} else if (_mouseOver) {
painter.fillRect(rect(), colorScheme.TabHoverBackground);
fg = colorScheme.TabHoverText;
} else if (this->highlightStyle == HighlightHighlighted) {
} else if (_highlightStyle == HighlightHighlighted) {
painter.fillRect(rect(), colorScheme.TabHighlightedBackground);
fg = colorScheme.TabHighlightedText;
} else if (this->highlightStyle == HighlightNewMessage) {
} else if (_highlightStyle == HighlightNewMessage) {
painter.fillRect(rect(), colorScheme.TabNewMessageBackground);
fg = colorScheme.TabHighlightedText;
} else {
@ -105,116 +143,105 @@ NotebookTab::paintEvent(QPaintEvent *)
painter.setPen(fg);
QRect rect(0, 0,
width() - (Settings::getInstance().hideTabX.get() ? 0 : 16),
height());
QRect rect(0, 0, width() - (SettingsManager::getInstance().hideTabX.get() ? 0 : 16), height());
painter.drawText(rect, this->title, QTextOption(Qt::AlignCenter));
painter.drawText(rect, _title, QTextOption(Qt::AlignCenter));
if (!Settings::getInstance().hideTabX.get() &&
(this->mouseOver || this->selected)) {
if (this->mouseOverX) {
painter.fillRect(this->getXRect(), QColor(0, 0, 0, 64));
if (!SettingsManager::getInstance().hideTabX.get() && (_mouseOver || _selected)) {
if (_mouseOverX) {
painter.fillRect(getXRect(), QColor(0, 0, 0, 64));
if (this->mouseDownX) {
painter.fillRect(this->getXRect(), QColor(0, 0, 0, 64));
if (_mouseDownX) {
painter.fillRect(getXRect(), QColor(0, 0, 0, 64));
}
}
painter.drawLine(this->getXRect().topLeft() + QPoint(4, 4),
this->getXRect().bottomRight() + QPoint(-4, -4));
painter.drawLine(this->getXRect().topRight() + QPoint(-4, 4),
this->getXRect().bottomLeft() + QPoint(4, -4));
painter.drawLine(getXRect().topLeft() + QPoint(4, 4),
getXRect().bottomRight() + QPoint(-4, -4));
painter.drawLine(getXRect().topRight() + QPoint(-4, 4),
getXRect().bottomLeft() + QPoint(4, -4));
}
}
void
NotebookTab::mousePressEvent(QMouseEvent *event)
void NotebookTab::mousePressEvent(QMouseEvent *event)
{
this->mouseDown = true;
this->mouseDownX = this->getXRect().contains(event->pos());
_mouseDown = true;
_mouseDownX = getXRect().contains(event->pos());
this->update();
update();
this->notebook->select(page);
_notebook->select(page);
}
void
NotebookTab::mouseReleaseEvent(QMouseEvent *event)
void NotebookTab::mouseReleaseEvent(QMouseEvent *event)
{
this->mouseDown = false;
_mouseDown = false;
if (!Settings::getInstance().hideTabX.get() && this->mouseDownX &&
this->getXRect().contains(event->pos())) {
this->mouseDownX = false;
if (!SettingsManager::getInstance().hideTabX.get() && _mouseDownX &&
getXRect().contains(event->pos())) {
_mouseDownX = false;
this->notebook->removePage(this->page);
_notebook->removePage(page);
} else {
update();
}
}
void
NotebookTab::enterEvent(QEvent *)
void NotebookTab::enterEvent(QEvent *)
{
this->mouseOver = true;
_mouseOver = true;
update();
}
void
NotebookTab::leaveEvent(QEvent *)
void NotebookTab::leaveEvent(QEvent *)
{
this->mouseOverX = this->mouseOver = false;
_mouseOverX = _mouseOver = false;
update();
}
void
NotebookTab::dragEnterEvent(QDragEnterEvent *)
void NotebookTab::dragEnterEvent(QDragEnterEvent *)
{
this->notebook->select(page);
_notebook->select(page);
}
void
NotebookTab::mouseMoveEvent(QMouseEvent *event)
void NotebookTab::mouseMoveEvent(QMouseEvent *event)
{
bool overX = this->getXRect().contains(event->pos());
bool overX = getXRect().contains(event->pos());
if (overX != this->mouseOverX) {
this->mouseOverX = overX && !Settings::getInstance().hideTabX.get();
if (overX != _mouseOverX) {
_mouseOverX = overX && !SettingsManager::getInstance().hideTabX.get();
this->update();
update();
}
if (this->mouseDown && !this->getDesiredRect().contains(event->pos())) {
QPoint relPoint = this->mapToParent(event->pos());
if (_mouseDown && !getDesiredRect().contains(event->pos())) {
QPoint relPoint = mapToParent(event->pos());
int index;
NotebookPage *page = notebook->tabAt(relPoint, index);
NotebookPage *page = _notebook->tabAt(relPoint, index);
if (page != nullptr && page != this->page) {
notebook->rearrangePage(this->page, index);
if (page != nullptr && page != page) {
_notebook->rearrangePage(page, index);
}
}
}
void
NotebookTab::load(const boost::property_tree::ptree &tree)
void NotebookTab::load(const boost::property_tree::ptree &tree)
{
// Load tab title
try {
this->setTitle(QString::fromStdString(tree.get<std::string>("title")));
setTitle(QString::fromStdString(tree.get<std::string>("title")));
} catch (boost::property_tree::ptree_error) {
}
}
boost::property_tree::ptree
NotebookTab::save()
boost::property_tree::ptree NotebookTab::save()
{
boost::property_tree::ptree tree;
tree.put("title", this->getTitle().toStdString());
tree.put("title", getTitle().toStdString());
return tree;
}

View file

@ -18,72 +18,27 @@ class NotebookTab : public QWidget
Q_OBJECT
public:
enum HighlightStyle {
HighlightNone,
HighlightHighlighted,
HighlightNewMessage
};
enum HighlightStyle { HighlightNone, HighlightHighlighted, HighlightNewMessage };
explicit NotebookTab(Notebook *notebook);
explicit NotebookTab(Notebook *_notebook);
~NotebookTab();
void calcSize();
NotebookPage *page;
const QString &
getTitle() const
{
return this->title;
}
const QString &getTitle() const;
void setTitle(const QString &title);
bool getSelected();
void setSelected(bool value);
void
setTitle(const QString &title)
{
this->title = title;
}
bool
getSelected()
{
return this->selected;
}
void
setSelected(bool value)
{
this->selected = value;
update();
}
HighlightStyle
getHighlightStyle() const
{
return this->highlightStyle;
}
void
setHighlightStyle(HighlightStyle style)
{
this->highlightStyle = style;
update();
}
HighlightStyle getHighlightStyle() const;
void setHighlightStyle(HighlightStyle style);
void moveAnimated(QPoint pos, bool animated = true);
public:
QRect
getDesiredRect() const
{
return QRect(posAnimationDesired, this->size());
}
void
hideTabXChanged(bool)
{
calcSize();
update();
}
QRect getDesiredRect() const;
void hideTabXChanged(bool);
protected:
void paintEvent(QPaintEvent *) override;
@ -98,26 +53,25 @@ protected:
void mouseMoveEvent(QMouseEvent *event) override;
private:
boost::signals2::connection hideXConnection;
boost::signals2::connection _hideXConnection;
QPropertyAnimation posAnimation;
bool posAnimated;
QPoint posAnimationDesired;
QPropertyAnimation _posAnimation;
bool _posAnimated;
QPoint _posAnimationDesired;
Notebook *notebook;
Notebook *_notebook;
QString title;
QString _title;
bool selected;
bool mouseOver;
bool mouseDown;
bool mouseOverX;
bool mouseDownX;
bool _selected;
bool _mouseOver;
bool _mouseDown;
bool _mouseOverX;
bool _mouseDownX;
HighlightStyle highlightStyle;
HighlightStyle _highlightStyle;
QRect
getXRect()
QRect getXRect()
{
return QRect(this->width() - 20, 4, 16, 16);
}

View file

@ -16,18 +16,15 @@ public:
sizePolicy.setVerticalPolicy(QSizePolicy::Preferred);
this->setSizePolicy(sizePolicy);
QObject::connect(this, &QTextEdit::textChanged, this,
&QWidget::updateGeometry);
QObject::connect(this, &QTextEdit::textChanged, this, &QWidget::updateGeometry);
}
QSize
sizeHint() const override
QSize sizeHint() const override
{
return QSize(this->width(), this->heightForWidth(this->width()));
}
bool
hasHeightForWidth() const override
bool hasHeightForWidth() const override
{
return true;
}
@ -35,17 +32,14 @@ public:
boost::signals2::signal<void(QKeyEvent *)> keyPressed;
protected:
int
heightForWidth(int) const override
int heightForWidth(int) const override
{
auto margins = this->contentsMargins();
return margins.top() + document()->size().height() + margins.bottom() +
5;
return margins.top() + document()->size().height() + margins.bottom() + 5;
}
void
keyPressEvent(QKeyEvent *event)
void keyPressEvent(QKeyEvent *event)
{
event->ignore();

View file

@ -11,35 +11,34 @@ namespace widgets {
ScrollBar::ScrollBar(QWidget *widget)
: QWidget(widget)
, mutex()
, currentValueAnimation(this, "currentValue")
, highlights(NULL)
, mouseOverIndex(-1)
, mouseDownIndex(-1)
, lastMousePosition()
, buttonHeight(16)
, trackHeight(100)
, thumbRect()
, maximum()
, minimum()
, largeChange()
, smallChange()
, desiredValue()
, currentValueChanged()
, currentValue()
, _mutex()
, _currentValueAnimation(this, "currentValue")
, _highlights(NULL)
, _mouseOverIndex(-1)
, _mouseDownIndex(-1)
, _lastMousePosition()
, _buttonHeight(16)
, _trackHeight(100)
, _thumbRect()
, _maximum()
, _minimum()
, _largeChange()
, _smallChange()
, _desiredValue()
, _currentValueChanged()
, _currentValue()
{
this->resize(16, 100);
resize(16, 100);
this->currentValueAnimation.setDuration(300);
this->currentValueAnimation.setEasingCurve(
QEasingCurve(QEasingCurve::OutCubic));
_currentValueAnimation.setDuration(250);
_currentValueAnimation.setEasingCurve(QEasingCurve(QEasingCurve::OutCubic));
this->setMouseTracking(true);
setMouseTracking(true);
}
ScrollBar::~ScrollBar()
{
auto highlight = this->highlights;
auto highlight = _highlights;
while (highlight != NULL) {
auto tmp = highlight->next;
@ -48,18 +47,17 @@ ScrollBar::~ScrollBar()
}
}
void
ScrollBar::removeHighlightsWhere(std::function<bool(ScrollBarHighlight &)> func)
void ScrollBar::removeHighlightsWhere(std::function<bool(ScrollBarHighlight &)> func)
{
this->mutex.lock();
_mutex.lock();
ScrollBarHighlight *last = NULL;
ScrollBarHighlight *current = this->highlights;
ScrollBarHighlight *current = _highlights;
while (current != NULL) {
if (func(*current)) {
if (last == NULL) {
this->highlights = current->next;
_highlights = current->next;
} else {
last->next = current->next;
}
@ -73,150 +71,233 @@ ScrollBar::removeHighlightsWhere(std::function<bool(ScrollBarHighlight &)> func)
}
}
this->mutex.unlock();
_mutex.unlock();
}
void
ScrollBar::addHighlight(ScrollBarHighlight *highlight)
void ScrollBar::addHighlight(ScrollBarHighlight *highlight)
{
this->mutex.lock();
_mutex.lock();
if (this->highlights == NULL) {
this->highlights = highlight;
if (_highlights == NULL) {
_highlights = highlight;
} else {
highlight->next = this->highlights->next;
this->highlights->next = highlight;
highlight->next = _highlights->next;
_highlights->next = highlight;
}
this->mutex.unlock();
_mutex.unlock();
}
void
ScrollBar::paintEvent(QPaintEvent *)
void ScrollBar::setMaximum(qreal value)
{
_maximum = value;
updateScroll();
}
void ScrollBar::setMinimum(qreal value)
{
_minimum = value;
updateScroll();
}
void ScrollBar::setLargeChange(qreal value)
{
_largeChange = value;
updateScroll();
}
void ScrollBar::setSmallChange(qreal value)
{
_smallChange = value;
updateScroll();
}
void ScrollBar::setDesiredValue(qreal value, bool animated)
{
value = std::max(_minimum, std::min(_maximum - _largeChange, value));
if (_desiredValue != value) {
if (animated) {
_currentValueAnimation.stop();
_currentValueAnimation.setStartValue(_currentValue);
_currentValueAnimation.setEndValue(value);
_currentValueAnimation.start();
} else {
if (_currentValueAnimation.state() != QPropertyAnimation::Running) {
// currentValueAnimation.stop();
setCurrentValue(value);
}
}
}
_desiredValue = value;
}
qreal ScrollBar::getMaximum() const
{
return _maximum;
}
qreal ScrollBar::getMinimum() const
{
return _minimum;
}
qreal ScrollBar::getLargeChange() const
{
return _largeChange;
}
qreal ScrollBar::getSmallChange() const
{
return _smallChange;
}
qreal ScrollBar::getDesiredValue() const
{
return _desiredValue;
}
qreal ScrollBar::getCurrentValue() const
{
return _currentValue;
}
boost::signals2::signal<void()> &ScrollBar::getCurrentValueChanged()
{
return _currentValueChanged;
}
void ScrollBar::setCurrentValue(qreal value)
{
value = std::max(_minimum, std::min(_maximum - _largeChange, value));
if (_currentValue != value) {
_currentValue = value;
updateScroll();
_currentValueChanged();
update();
}
}
void ScrollBar::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.fillRect(rect(), ColorScheme::getInstance().ScrollbarBG);
painter.fillRect(QRect(0, 0, width(), this->buttonHeight),
QColor(255, 0, 0));
painter.fillRect(
QRect(0, height() - this->buttonHeight, width(), this->buttonHeight),
QColor(255, 0, 0));
painter.fillRect(QRect(0, 0, width(), _buttonHeight), QColor(255, 0, 0));
painter.fillRect(QRect(0, height() - _buttonHeight, width(), _buttonHeight), QColor(255, 0, 0));
painter.fillRect(this->thumbRect, QColor(0, 255, 255));
painter.fillRect(_thumbRect, QColor(0, 255, 255));
ScrollBarHighlight *highlight = this->highlights;
// ScrollBarHighlight *highlight = highlights;
this->mutex.lock();
_mutex.lock();
// do {
// painter.fillRect();
// } while ((highlight = highlight->next()) != NULL);
this->mutex.unlock();
_mutex.unlock();
}
void
ScrollBar::mouseMoveEvent(QMouseEvent *event)
void ScrollBar::mouseMoveEvent(QMouseEvent *event)
{
if (this->mouseDownIndex == -1) {
if (_mouseDownIndex == -1) {
int y = event->pos().y();
auto oldIndex = this->mouseOverIndex;
auto oldIndex = _mouseOverIndex;
if (y < this->buttonHeight) {
this->mouseOverIndex = 0;
} else if (y < this->thumbRect.y()) {
this->mouseOverIndex = 1;
} else if (this->thumbRect.contains(2, y)) {
this->mouseOverIndex = 2;
} else if (y < height() - this->buttonHeight) {
this->mouseOverIndex = 3;
if (y < _buttonHeight) {
_mouseOverIndex = 0;
} else if (y < _thumbRect.y()) {
_mouseOverIndex = 1;
} else if (_thumbRect.contains(2, y)) {
_mouseOverIndex = 2;
} else if (y < height() - _buttonHeight) {
_mouseOverIndex = 3;
} else {
this->mouseOverIndex = 4;
_mouseOverIndex = 4;
}
if (oldIndex != this->mouseOverIndex) {
this->update();
if (oldIndex != _mouseOverIndex) {
update();
}
} else if (this->mouseDownIndex == 2) {
int delta = event->pos().y() - lastMousePosition.y();
} else if (_mouseDownIndex == 2) {
int delta = event->pos().y() - _lastMousePosition.y();
this->setDesiredValue(this->desiredValue +
(qreal)delta / this->trackHeight * maximum);
setDesiredValue(_desiredValue + (qreal)delta / _trackHeight * _maximum);
}
this->lastMousePosition = event->pos();
_lastMousePosition = event->pos();
}
void
ScrollBar::mousePressEvent(QMouseEvent *event)
void ScrollBar::mousePressEvent(QMouseEvent *event)
{
int y = event->pos().y();
if (y < this->buttonHeight) {
this->mouseDownIndex = 0;
} else if (y < this->thumbRect.y()) {
this->mouseDownIndex = 1;
} else if (this->thumbRect.contains(2, y)) {
this->mouseDownIndex = 2;
} else if (y < height() - this->buttonHeight) {
this->mouseDownIndex = 3;
if (y < _buttonHeight) {
_mouseDownIndex = 0;
} else if (y < _thumbRect.y()) {
_mouseDownIndex = 1;
} else if (_thumbRect.contains(2, y)) {
_mouseDownIndex = 2;
} else if (y < height() - _buttonHeight) {
_mouseDownIndex = 3;
} else {
this->mouseDownIndex = 4;
_mouseDownIndex = 4;
}
}
void
ScrollBar::mouseReleaseEvent(QMouseEvent *event)
void ScrollBar::mouseReleaseEvent(QMouseEvent *event)
{
int y = event->pos().y();
if (y < this->buttonHeight) {
if (this->mouseDownIndex == 0) {
this->setDesiredValue(this->desiredValue - this->smallChange, true);
if (y < _buttonHeight) {
if (_mouseDownIndex == 0) {
setDesiredValue(_desiredValue - _smallChange, true);
}
} else if (y < this->thumbRect.y()) {
if (this->mouseDownIndex == 1) {
this->setDesiredValue(this->desiredValue - this->smallChange, true);
} else if (y < _thumbRect.y()) {
if (_mouseDownIndex == 1) {
setDesiredValue(_desiredValue - _smallChange, true);
}
} else if (this->thumbRect.contains(2, y)) {
} else if (_thumbRect.contains(2, y)) {
// do nothing
} else if (y < height() - this->buttonHeight) {
if (this->mouseDownIndex == 3) {
this->setDesiredValue(this->desiredValue + this->smallChange, true);
} else if (y < height() - _buttonHeight) {
if (_mouseDownIndex == 3) {
setDesiredValue(_desiredValue + _smallChange, true);
}
} else {
if (this->mouseDownIndex == 4) {
this->setDesiredValue(this->desiredValue + this->smallChange, true);
if (_mouseDownIndex == 4) {
setDesiredValue(_desiredValue + _smallChange, true);
}
}
this->mouseDownIndex = -1;
_mouseDownIndex = -1;
update();
}
void
ScrollBar::leaveEvent(QEvent *)
void ScrollBar::leaveEvent(QEvent *)
{
this->mouseOverIndex = -1;
_mouseOverIndex = -1;
update();
}
void
ScrollBar::updateScroll()
void ScrollBar::updateScroll()
{
this->trackHeight = height() - this->buttonHeight - this->buttonHeight -
MIN_THUMB_HEIGHT - 1;
_trackHeight = height() - _buttonHeight - _buttonHeight - MIN_THUMB_HEIGHT - 1;
this->thumbRect =
QRect(0,
(int)(this->currentValue / this->maximum * this->trackHeight) +
1 + this->buttonHeight,
width(),
(int)(this->largeChange / this->maximum * this->trackHeight) +
MIN_THUMB_HEIGHT);
_thumbRect = QRect(0, (int)(_currentValue / _maximum * _trackHeight) + 1 + _buttonHeight,
width(), (int)(_largeChange / _maximum * _trackHeight) + MIN_THUMB_HEIGHT);
update();
}

View file

@ -23,128 +23,29 @@ public:
void removeHighlightsWhere(std::function<bool(ScrollBarHighlight &)> func);
void addHighlight(ScrollBarHighlight *highlight);
Q_PROPERTY(qreal desiredValue READ getDesiredValue WRITE setDesiredValue)
Q_PROPERTY(qreal _desiredValue READ getDesiredValue WRITE setDesiredValue)
void
setMaximum(qreal value)
{
this->maximum = value;
this->updateScroll();
}
void
setMinimum(qreal value)
{
this->minimum = value;
this->updateScroll();
}
void
setLargeChange(qreal value)
{
this->largeChange = value;
this->updateScroll();
}
void
setSmallChange(qreal value)
{
this->smallChange = value;
this->updateScroll();
}
void
setDesiredValue(qreal value, bool animated = false)
{
value = std::max(this->minimum,
std::min(this->maximum - this->largeChange, value));
if (this->desiredValue != value) {
if (animated) {
this->currentValueAnimation.stop();
this->currentValueAnimation.setStartValue(this->currentValue);
this->currentValueAnimation.setEndValue(value);
this->currentValueAnimation.start();
} else {
this->currentValueAnimation.stop();
this->setCurrentValue(value);
}
}
this->desiredValue = value;
}
qreal
getMaximum() const
{
return this->maximum;
}
qreal
getMinimum() const
{
return this->minimum;
}
qreal
getLargeChange() const
{
return this->largeChange;
}
qreal
getSmallChange() const
{
return this->smallChange;
}
qreal
getDesiredValue() const
{
return this->desiredValue;
}
qreal
getCurrentValue() const
{
return this->currentValue;
}
boost::signals2::signal<void()> &
getCurrentValueChanged()
{
return currentValueChanged;
}
void
setCurrentValue(qreal value)
{
value = std::max(this->minimum,
std::min(this->maximum - this->largeChange, value));
if (this->currentValue != value) {
this->currentValue = value;
this->updateScroll();
this->currentValueChanged();
this->update();
}
}
void setMaximum(qreal value);
void setMinimum(qreal value);
void setLargeChange(qreal value);
void setSmallChange(qreal value);
void setDesiredValue(qreal value, bool animated = false);
qreal getMaximum() const;
qreal getMinimum() const;
qreal getLargeChange() const;
qreal getSmallChange() const;
qreal getDesiredValue() const;
qreal getCurrentValue() const;
boost::signals2::signal<void()> &getCurrentValueChanged();
void setCurrentValue(qreal value);
private:
Q_PROPERTY(qreal currentValue READ getCurrentValue WRITE setCurrentValue)
Q_PROPERTY(qreal _currentValue READ getCurrentValue WRITE setCurrentValue)
QMutex mutex;
ScrollBarHighlight *highlights;
QMutex _mutex;
ScrollBarHighlight *_highlights;
QPropertyAnimation currentValueAnimation;
QPropertyAnimation _currentValueAnimation;
void paintEvent(QPaintEvent *);
void mouseMoveEvent(QMouseEvent *event);
@ -152,23 +53,23 @@ private:
void mouseReleaseEvent(QMouseEvent *event);
void leaveEvent(QEvent *);
int mouseOverIndex;
int mouseDownIndex;
QPoint lastMousePosition;
int _mouseOverIndex;
int _mouseDownIndex;
QPoint _lastMousePosition;
int buttonHeight;
int trackHeight;
int _buttonHeight;
int _trackHeight;
QRect thumbRect;
QRect _thumbRect;
qreal maximum;
qreal minimum;
qreal largeChange;
qreal smallChange;
qreal desiredValue;
qreal currentValue;
qreal _maximum;
qreal _minimum;
qreal _largeChange;
qreal _smallChange;
qreal _desiredValue;
qreal _currentValue;
boost::signals2::signal<void()> currentValueChanged;
boost::signals2::signal<void()> _currentValueChanged;
void updateScroll();
};

View file

@ -4,13 +4,11 @@
namespace chatterino {
namespace widgets {
ScrollBarHighlight::ScrollBarHighlight(float position, int colorIndex,
Style style, QString tag)
: position(position)
, colorIndex(std::max(
0, std::min(ColorScheme::getInstance().HighlightColorCount, colorIndex)))
, style(style)
, tag(tag)
ScrollBarHighlight::ScrollBarHighlight(float position, int colorIndex, Style style, QString tag)
: _position(position)
, _colorIndex(std::max(0, std::min(ColorScheme::getInstance().HighlightColorCount, colorIndex)))
, _style(style)
, _tag(tag)
, next(NULL)
{
}

Some files were not shown because too many files have changed in this diff Show more