move around files

This commit is contained in:
Rasmus Karlsson
2017-06-06 14:48:14 +02:00
parent 71f64cd6ff
commit ccf8e3bd83
148 changed files with 115 additions and 107 deletions
+1
View File
@@ -0,0 +1 @@
../.clang-format
+74
View File
@@ -0,0 +1,74 @@
#include "accountmanager.h"
namespace chatterino {
namespace {
inline QString getEnvString(const char *target)
{
char *val = std::getenv(target);
if (val == nullptr) {
return QString();
}
return QString(val);
}
} // namespace
AccountManager::AccountManager()
: _twitchAnon("justinfan64537", "", "")
, _twitchUsers()
, _twitchUsersMutex()
{
QString envUsername = getEnvString("CHATTERINO2_USERNAME");
QString envOauthToken = getEnvString("CHATTERINO2_OAUTH");
if (!envUsername.isEmpty() && !envOauthToken.isEmpty()) {
this->addTwitchUser(twitch::TwitchUser(envUsername, envOauthToken, ""));
}
}
twitch::TwitchUser &AccountManager::getTwitchAnon()
{
return _twitchAnon;
}
twitch::TwitchUser &AccountManager::getTwitchUser()
{
std::lock_guard<std::mutex> lock(_twitchUsersMutex);
if (_twitchUsers.size() == 0) {
return _twitchAnon;
}
return _twitchUsers.front();
}
std::vector<twitch::TwitchUser> AccountManager::getTwitchUsers()
{
return std::vector<twitch::TwitchUser>(_twitchUsers);
}
bool AccountManager::removeTwitchUser(const QString &userName)
{
std::lock_guard<std::mutex> lock(_twitchUsersMutex);
for (auto it = _twitchUsers.begin(); it != _twitchUsers.end(); it++) {
if ((*it).getUserName() == userName) {
_twitchUsers.erase(it);
return true;
}
}
return false;
}
void AccountManager::addTwitchUser(const twitch::TwitchUser &user)
{
std::lock_guard<std::mutex> lock(_twitchUsersMutex);
_twitchUsers.push_back(user);
}
} // namespace chatterino
+39
View File
@@ -0,0 +1,39 @@
#ifndef ACCOUNTMANAGER_H
#define ACCOUNTMANAGER_H
#include "twitch/twitchuser.h"
#include <mutex>
#include <vector>
namespace chatterino {
class AccountManager
{
public:
static AccountManager &getInstance()
{
static AccountManager instance;
return instance;
}
twitch::TwitchUser &getTwitchAnon();
// Returns first user from _twitchUsers, or _twitchAnon if _twitchUsers is empty
twitch::TwitchUser &getTwitchUser();
std::vector<twitch::TwitchUser> getTwitchUsers();
bool removeTwitchUser(const QString &userName);
void addTwitchUser(const twitch::TwitchUser &user);
private:
AccountManager();
twitch::TwitchUser _twitchAnon;
std::vector<twitch::TwitchUser> _twitchUsers;
std::mutex _twitchUsersMutex;
};
} // namespace chatterino
#endif // ACCOUNTMANAGER_H
+28
View File
@@ -0,0 +1,28 @@
#include "appdatapath.h"
#include <QDebug>
#include <QDir>
#include <QStandardPaths>
QString Path::appdataPath;
std::mutex Path::appdataPathMutex;
const QString &Path::getAppdataPath()
{
std::lock_guard<std::mutex> lock(appdataPathMutex);
if (appdataPath.isEmpty()) {
#ifdef PORTABLE
QString path = QCoreApplication::applicationDirPath();
#else
QString path =
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/Chatterino2/";
#endif
QDir(QDir::root()).mkdir(path);
appdataPath = path;
}
return appdataPath;
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef APPDATAPATH_H
#define APPDATAPATH_H
#include <QString>
#include <atomic>
#include <mutex>
class Path
{
public:
static const QString &getAppdataPath();
private:
static QString appdataPath;
static std::mutex appdataPathMutex;
};
#endif // APPDATAPATH_H
+28
View File
@@ -0,0 +1,28 @@
#ifndef ASYNCEXEC_H
#define ASYNCEXEC_H
#include "qcoreapplication.h"
#include <QRunnable>
#include <QThreadPool>
#include <functional>
#define async_exec(a) QThreadPool::globalInstance()->start(new LambdaRunnable(a));
class LambdaRunnable : public QRunnable {
public:
LambdaRunnable(std::function<void()> action)
{
this->action = action;
}
void run()
{
this->action();
}
private:
std::function<void()> action;
};
#endif // ASYNCEXEC_H
+202
View File
@@ -0,0 +1,202 @@
#include "channel.h"
#include "emotemanager.h"
#include "ircmanager.h"
#include "logging/loggingmanager.h"
#include "messages/message.h"
#include "util/urlfetch.h"
#include "windowmanager.h"
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#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))
{
qDebug() << "Open channel:" << channel << ". Name: " << _name;
printf("Channel pointer: %p\n", this);
reloadChannelEmotes();
}
//
// properties
//
ConcurrentMap<QString, messages::LazyLoadedImage *> &Channel::getBttvChannelEmotes()
{
return _bttvChannelEmotes;
}
ConcurrentMap<QString, messages::LazyLoadedImage *> &Channel::getFfzChannelEmotes()
{
return _ffzChannelEmotes;
}
bool Channel::isEmpty() const
{
return _name.isEmpty();
}
const QString &Channel::getName() const
{
return _name;
}
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);
WindowManager::getInstance().repaintVisibleChatWidgets(this);
}
// private methods
void Channel::reloadChannelEmotes()
{
reloadBttvEmotes();
reloadFfzEmotes();
}
void Channel::sendMessage(const QString &message)
{
qDebug() << "Channel send message: " << message;
IrcManager &instance = IrcManager::getInstance();
instance.sendMessage(_name, message);
}
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
+82
View File
@@ -0,0 +1,82 @@
#ifndef CHANNEL_H
#define CHANNEL_H
#include "concurrentmap.h"
#include "logging/loggingchannel.h"
#include "messages/lazyloadedimage.h"
#include "messages/limitedqueue.h"
#include <QMap>
#include <QMutex>
#include <QString>
#include <QVector>
#include <boost/signals2.hpp>
#include <memory>
namespace chatterino {
namespace messages {
class Message;
}
class ChannelManager;
typedef std::shared_ptr<Channel> SharedChannel;
class Channel
{
public:
Channel(const QString &channel);
boost::signals2::signal<void(messages::SharedMessage &)> messageRemovedFromStart;
boost::signals2::signal<void(messages::SharedMessage &)> messageAppended;
// properties
ConcurrentMap<QString, messages::LazyLoadedImage *> &getBttvChannelEmotes();
ConcurrentMap<QString, messages::LazyLoadedImage *> &getFfzChannelEmotes();
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
void addMessage(messages::SharedMessage message);
void reloadChannelEmotes();
void sendMessage(const QString &message);
private:
// variabeles
messages::LimitedQueue<messages::SharedMessage> _messages;
QString _name;
int _roomID;
ConcurrentMap<QString, messages::LazyLoadedImage *> _bttvChannelEmotes;
ConcurrentMap<QString, messages::LazyLoadedImage *> _ffzChannelEmotes;
QString _subLink;
QString _channelLink;
QString _popoutPlayerLink;
bool _isLive;
int _streamViewerCount;
QString _streamStatus;
QString _streamGame;
// std::shared_ptr<logging::Channel> _loggingChannel;
// methods
void reloadBttvEmotes();
void reloadFfzEmotes();
};
} // namespace chatterino
#endif // CHANNEL_H
+122
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
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
+174
View File
@@ -0,0 +1,174 @@
#define LOOKUP_COLOR_COUNT 360
#include "colorscheme.h"
#include "settingsmanager.h"
#include "windowmanager.h"
#include <QColor>
namespace chatterino {
void ColorScheme::init()
{
static bool initiated = false;
if (!initiated) {
initiated = true;
ColorScheme::getInstance().update();
SettingsManager::getInstance().theme.valueChanged.connect(
[](const QString &) { ColorScheme::getInstance().update(); });
SettingsManager::getInstance().themeHue.valueChanged.connect(
[](const float &) { ColorScheme::getInstance().update(); });
ColorScheme::getInstance().updated.connect(
[] { WindowManager::getInstance().repaintVisibleChatWidgets(); });
}
}
void ColorScheme::update()
{
QString theme = SettingsManager::getInstance().theme.get();
theme = theme.toLower();
qreal hue = SettingsManager::getInstance().themeHue.get();
if (theme == "light") {
setColors(hue, 0.8);
} else if (theme == "white") {
setColors(hue, 1);
} else if (theme == "black") {
setColors(hue, -1);
} else {
setColors(hue, -0.8);
}
}
// hue: theme color (0 - 1)
// multiplyer: 1 = white, 0.8 = light, -0.8 dark, -1 black
void ColorScheme::setColors(float hue, float multiplyer)
{
IsLightTheme = multiplyer > 0;
bool hasDarkBorder = false;
SystemMessageColor = QColor(140, 127, 127);
auto getColor = [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.6);
Text = TextCaret = IsLightTheme ? QColor(0, 0, 0) : QColor(255, 255, 255);
// tab
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);
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);
// Chat
ChatBackground = getColor(0, 0.1, 1);
ChatHeaderBackground = getColor(0, 0.1, 0.9);
ChatHeaderBorder = getColor(0, 0.1, 0.85);
ChatInputBackground = getColor(0, 0.1, 0.95);
ChatInputBorder = getColor(0, 0.1, 0.9);
ScrollbarBG = ChatBackground;
// generate color lookuptable
fillLookupTableValues(this->middleLookupTable, 0.000, 0.166, 0.66, 0.5);
fillLookupTableValues(this->middleLookupTable, 0.166, 0.333, 0.5, 0.55);
fillLookupTableValues(this->middleLookupTable, 0.333, 0.500, 0.55, 0.45);
fillLookupTableValues(this->middleLookupTable, 0.500, 0.666, 0.45, 0.80);
fillLookupTableValues(this->middleLookupTable, 0.666, 0.833, 0.80, 0.61);
fillLookupTableValues(this->middleLookupTable, 0.833, 1.000, 0.61, 0.66);
fillLookupTableValues(this->minLookupTable, 0.000, 0.166, 0.33, 0.23);
fillLookupTableValues(this->minLookupTable, 0.166, 0.333, 0.23, 0.27);
fillLookupTableValues(this->minLookupTable, 0.333, 0.500, 0.27, 0.23);
fillLookupTableValues(this->minLookupTable, 0.500, 0.666, 0.23, 0.50);
fillLookupTableValues(this->minLookupTable, 0.666, 0.833, 0.50, 0.30);
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();
updated();
}
void ColorScheme::fillLookupTableValues(qreal (&array)[360], qreal from, qreal to, qreal fromValue,
qreal toValue)
{
qreal diff = toValue - fromValue;
int start = from * LOOKUP_COLOR_COUNT;
int end = to * LOOKUP_COLOR_COUNT;
int length = end - start;
for (int i = 0; i < length; i++) {
array[start + i] = fromValue + (diff * ((qreal)i / length));
}
}
void ColorScheme::normalizeColor(QColor &color)
{
// qreal l = color.lightnessF();
// qreal s = color.saturationF();
// qreal x = this->colorLookupTable[std::max(0, color.hue())];
// qreal newL = (l - 1) * x + 1;
// newL = s * newL + (1 - s) * l;
// newL = newL > 0.5 ? newL : newL / 2 + 0.25;
// color.setHslF(color.hueF(), s, newL);
qreal l = color.lightnessF();
qreal s = color.saturationF();
int h = std::max(0, color.hue());
qreal x = this->middleLookupTable[h];
x = s * 0.5 + (1 - s) * x;
qreal min = this->minLookupTable[h];
min = (1 - s) * 0.5 + s * min;
qreal newL;
if (l < x) {
newL = l * ((x - min) / x) + min;
// newL = (l * ((x - min) / x) + min);
// newL = (1 - s) * newL + s * l;
} else {
newL = l;
}
color.setHslF(color.hueF(), s, newL);
// qreal newL = (l - 1) * x + 1;
// newL = s * newL + (1 - s) * l;
// newL = newL > 0.5 ? newL : newL / 2 + 0.25;
// color.setHslF(color.hueF(), s, newL);
}
} // namespace chatterino
+94
View File
@@ -0,0 +1,94 @@
#ifndef COLORSCHEME_H
#define COLORSCHEME_H
#include <QBrush>
#include <QColor>
#include <boost/signals2.hpp>
namespace chatterino {
class ColorScheme
{
public:
bool IsLightTheme;
QString InputStyleSheet;
QColor SystemMessageColor;
QColor DropPreviewBackground;
QColor TooltipBackground;
QColor TooltipText;
QColor ChatSeperator;
QColor ChatBackground;
QColor ChatBackgroundHighlighted;
QColor ChatBackgroundResub;
QColor ChatBackgroundWhisper;
QColor ChatHeaderBorder;
QColor ChatHeaderBackground;
QColor ChatInputBackground;
QColor ChatInputBorder;
QColor ChatMessageSeperatorBorder;
QColor ChatMessageSeperatorBorderInner;
QColor ChatBorder;
QColor ChatBorderFocused;
QColor Text;
QColor TextCaret;
QColor TextLink;
QColor TextFocused;
QColor Menu;
QColor MenuBorder;
QColor ScrollbarBG;
QColor ScrollbarThumb;
QColor ScrollbarThumbSelected;
QColor ScrollbarArrow;
QColor TabPanelBackground;
QColor TabBackground;
QColor TabHoverBackground;
QColor TabSelectedBackground;
QColor TabHighlightedBackground;
QBrush TabNewMessageBackground;
QColor TabText;
QColor TabHoverText;
QColor TabSelectedText;
QColor TabHighlightedText;
const int HighlightColorCount = 3;
QColor HighlightColors[3];
static ColorScheme &getInstance()
{
static ColorScheme instance;
return instance;
}
void init();
void normalizeColor(QColor &color);
void update();
boost::signals2::signal<void()> updated;
private:
ColorScheme()
: updated()
{
}
void setColors(float hue, float multiplyer);
qreal middleLookupTable[360] = {};
qreal minLookupTable[360] = {};
void fillLookupTableValues(qreal (&array)[360], qreal from, qreal to, qreal fromValue,
qreal toValue);
};
}
#endif // COLORSCHEME_H
+17
View File
@@ -0,0 +1,17 @@
#include <QString>
#ifndef COMMON_H
#define COMMON_H
#define STRINGISE_IMPL(x) #x
#define STRINGISE(x) STRINGISE_IMPL(x)
// Use: #pragma message WARN("My message")
#if _MSC_VER
#define FILE_LINE_LINK __FILE__ "(" STRINGISE(__LINE__) ") : "
#define WARN(exp) (FILE_LINE_LINK "WARNING: " exp)
#else //__GNUC__ - may need other defines for different compilers
#define WARN(exp) ("WARNING: " exp)
#endif
#endif // COMMON_H
+16
View File
@@ -0,0 +1,16 @@
/*--------------------------------------------------------------------
* Precompiled header source file used by Visual Studio.NET to generate
* the .pch file.
*
* Due to issues with the dependencies checker within the IDE, it
* sometimes fails to recompile the PCH file, if we force the IDE to
* create the PCH file directly from the header file.
*
* This file is auto-generated by qmake since no PRECOMPILED_SOURCE was
* specified, and is used as the common stdafx.cpp. The file is only
* generated when creating .vcxproj project files, and is not used for
* command line compilations by nmake.
*
* WARNING: All changes made in this file will be lost.
--------------------------------------------------------------------*/
#include "common.h"
+70
View File
@@ -0,0 +1,70 @@
#ifndef CONCURRENTMAP_H
#define CONCURRENTMAP_H
#include <QMap>
#include <QMutex>
#include <QMutexLocker>
#include <functional>
namespace chatterino {
template <typename TKey, typename TValue>
class ConcurrentMap
{
public:
ConcurrentMap()
: _map()
{
}
bool tryGet(const TKey &name, TValue &value) const
{
QMutexLocker lock(&_mutex);
auto a = _map.find(name);
if (a == _map.end()) {
return false;
}
value = a.value();
return true;
}
TValue getOrAdd(const TKey &name, std::function<TValue()> addLambda)
{
QMutexLocker lock(&_mutex);
auto a = _map.find(name);
if (a == _map.end()) {
TValue value = addLambda();
_map.insert(name, value);
return value;
}
return a.value();
}
void clear()
{
QMutexLocker lock(&_mutex);
_map.clear();
}
void insert(const TKey &name, const TValue &value)
{
QMutexLocker lock(&_mutex);
_map.insert(name, value);
}
private:
mutable QMutex _mutex;
QMap<TKey, TValue> _map;
};
} // namespace chatterino
#endif // CONCURRENTMAP_H
+117
View File
@@ -0,0 +1,117 @@
#include "emojis.h"
#include "emotemanager.h"
#include <QFile>
#include <QStringBuilder>
#include <QTextStream>
namespace chatterino {
QRegularExpression Emojis::findShortCodesRegex(":([-+\\w]+):");
QMap<QString, Emojis::EmojiData> Emojis::shortCodeToEmoji;
QMap<QString, QString> Emojis::emojiToShortCode;
QMap<QChar, QMap<QString, QString>> Emojis::firstEmojiChars;
ConcurrentMap<QString, messages::LazyLoadedImage *> Emojis::imageCache;
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)
{
long lastSlice = 0;
for (auto i = 0; i < text.length() - 1; i++) {
if (!text.at(i).isLowSurrogate()) {
auto iter = firstEmojiChars.find(text.at(i));
if (iter != firstEmojiChars.end()) {
for (auto j = std::min(8, text.length() - i); j > 0; j--) {
QString emojiString = text.mid(i, 2);
auto emojiIter = iter.value().find(emojiString);
if (emojiIter != iter.value().end()) {
QString url = "https://cdnjs.cloudflare.com/ajax/libs/"
"emojione/2.2.6/assets/png/" +
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>(
imageCache.getOrAdd(
url, [&url] { return new messages::LazyLoadedImage(url, 0.35); }),
QString()));
i += j - 1;
lastSlice = i + 1;
break;
}
}
}
}
}
if (lastSlice < text.length()) {
vector.push_back(
std::tuple<messages::LazyLoadedImage *, QString>(NULL, text.mid(lastSlice)));
}
}
void Emojis::loadEmojis()
{
QFile file(":/emojidata.txt");
file.open(QFile::ReadOnly);
QTextStream in(&file);
uint emotes[4];
while (!in.atEnd()) {
QString line = in.readLine();
if (line.length() < 3 || line.at(0) == '#')
continue;
QStringList a = line.split(' ');
if (a.length() < 2)
continue;
QStringList b = a.at(1).split('-');
if (b.length() < 1)
continue;
int i = 0;
for (const QString &item : b) {
emotes[i++] = QString(item).toUInt(nullptr, 16);
}
shortCodeToEmoji.insert(a.at(0), Emojis::EmojiData{QString::fromUcs4(emotes, i), a.at(1)});
}
for (auto const &emoji : shortCodeToEmoji.toStdMap()) {
emojiToShortCode.insert(emoji.second.value, emoji.first);
}
for (auto const &emoji : shortCodeToEmoji.toStdMap()) {
auto iter = firstEmojiChars.find(emoji.first.at(0));
if (iter != firstEmojiChars.end()) {
iter.value().insert(emoji.second.value, emoji.second.value);
continue;
}
firstEmojiChars.insert(emoji.first.at(0),
QMap<QString, QString>{{emoji.second.value, emoji.second.code}});
}
}
}
+44
View File
@@ -0,0 +1,44 @@
#ifndef EMOJIS_H
#define EMOJIS_H
#include "concurrentmap.h"
#include "messages/lazyloadedimage.h"
#include <QObject>
#include <QRegularExpression>
#include <QString>
#include <unordered_map>
namespace chatterino {
class Emojis
{
public:
static void parseEmojis(std::vector<std::tuple<messages::LazyLoadedImage *, QString>> &vector,
const QString &text);
static void loadEmojis();
static QString replaceShortCodes(const QString &text);
struct EmojiData {
QString value;
QString code;
};
private:
static QRegularExpression findShortCodesRegex;
static QMap<QString, EmojiData> shortCodeToEmoji;
static QMap<QString, QString> emojiToShortCode;
static QMap<QChar, QMap<QString, QString>> firstEmojiChars;
static ConcurrentMap<QString, messages::LazyLoadedImage *> imageCache;
Emojis()
{
}
};
}
#endif // EMOJIS_H
+210
View File
@@ -0,0 +1,210 @@
#include "emotemanager.h"
#include "resources.h"
#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <memory>
#define TWITCH_EMOTE_TEMPLATE "https://static-cdn.jtvnw.net/emoticons/v1/{id}/{scale}.0"
using namespace chatterino::messages;
namespace chatterino {
EmoteManager EmoteManager::instance;
EmoteManager::EmoteManager()
: _twitchEmotes()
, _bttvEmotes()
, _ffzEmotes()
, _chatterinoEmotes()
, _bttvChannelEmoteFromCaches()
, _ffzChannelEmoteFromCaches()
, _twitchEmoteFromCache()
, _miscImageFromCache()
, _gifUpdateTimerSignal()
, _gifUpdateTimer()
, _gifUpdateTimerInitiated(false)
, _generation(0)
{
}
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 EmoteManager::loadBttvEmotes()
{
// bttv
QNetworkAccessManager *manager = new QNetworkAccessManager();
QUrl url("https://api.betterttv.net/2/emotes");
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 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 = linkTemplate;
tmp.detach();
QString url = tmp.replace("{{id}}", id).replace("{{image}}", "1x");
EmoteManager::getBttvEmotes().insert(
code, new LazyLoadedImage(url, 1, code, code + "\nGlobal Bttv Emote"));
}
}
reply->deleteLater();
manager->deleteLater();
});
}
void EmoteManager::loadFfzEmotes()
{
// ffz
QNetworkAccessManager *manager = new QNetworkAccessManager();
QUrl url("https://api.frankerfacez.com/v1/set/global");
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();
EmoteManager::getBttvEmotes().insert(
code, new LazyLoadedImage(url1, 1, code, code + "\nGlobal Ffz Emote"));
}
}
}
reply->deleteLater();
manager->deleteLater();
});
}
LazyLoadedImage *EmoteManager::getTwitchEmoteById(const QString &name, long id)
{
return EmoteManager::_twitchEmoteFromCache.getOrAdd(id, [&name, &id] {
qDebug() << "added twitch emote: " << id;
qreal scale;
QString url = getTwitchEmoteLink(id, scale);
return new LazyLoadedImage(url, scale, name, name + "\nTwitch Emote");
});
}
QString EmoteManager::getTwitchEmoteLink(long id, qreal &scale)
{
scale = .5;
QString value = TWITCH_EMOTE_TEMPLATE;
value.detach();
return value.replace("{id}", QString::number(id)).replace("{scale}", "2");
}
LazyLoadedImage *EmoteManager::getCheerImage(long long amount, bool animated)
{
// TODO: fix this xD
return getCheerBadge(amount);
}
LazyLoadedImage *EmoteManager::getCheerBadge(long long amount)
{
if (amount >= 100000) {
return Resources::getCheerBadge100000();
} else if (amount >= 10000) {
return Resources::getCheerBadge10000();
} else if (amount >= 5000) {
return Resources::getCheerBadge5000();
} else if (amount >= 1000) {
return Resources::getCheerBadge1000();
} else if (amount >= 100) {
return Resources::getCheerBadge100();
} else {
return Resources::getCheerBadge1();
}
}
} // namespace chatterino
+98
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::getInstance().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;
boost::signals2::signal<void()> _gifUpdateTimerSignal;
QTimer _gifUpdateTimer;
bool _gifUpdateTimerInitiated;
int _generation;
// methods
static QString getTwitchEmoteLink(long id, qreal &scale);
void loadFfzEmotes();
void loadBttvEmotes();
};
} // namespace chatterino
#endif // EMOTES_H
+62
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
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
+33
View File
@@ -0,0 +1,33 @@
#include "ircaccount.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;
}
}
+28
View File
@@ -0,0 +1,28 @@
#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;
};
} // namespace chatterino
#endif // IRCUSER_H
+326
View File
@@ -0,0 +1,326 @@
#include "ircmanager.h"
#include "accountmanager.h"
#include "asyncexec.h"
#include "channel.h"
#include "channelmanager.h"
#include "messages/messageparseargs.h"
#include "twitch/twitchmessagebuilder.h"
#include "twitch/twitchparsemessage.h"
#include "twitch/twitchuser.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 {
IrcManager IrcManager::instance;
const QString IrcManager::defaultClientId("7ue61iz46fz11y3cugd0l3tawb4taal");
IrcManager::IrcManager()
: _account(AccountManager::getInstance().getTwitchUser())
, _connection()
, _connectionMutex()
, _connectionGeneration(0)
, _twitchBlockedUsers()
, _twitchBlockedUsersMutex()
, _accessManager()
{
}
const twitch::TwitchUser &IrcManager::getUser() const
{
return _account;
}
void IrcManager::setUser(const twitch::TwitchUser &account)
{
_account = account;
}
void IrcManager::connect()
{
disconnect();
async_exec([this] { beginConnecting(); });
}
void IrcManager::beginConnecting()
{
int generation = ++IrcManager::_connectionGeneration;
Communi::IrcConnection *c = new Communi::IrcConnection;
QObject::connect(c, &Communi::IrcConnection::messageReceived, this,
&IrcManager::messageReceived);
QObject::connect(c, &Communi::IrcConnection::privateMessageReceived, this,
&IrcManager::privateMessageReceived);
QString username = _account.getUserName();
QString oauthClient = _account.getOAuthClient();
QString oauthToken = _account.getOAuthToken();
c->setUserName(username);
c->setNickName(username);
c->setRealName(username);
if (!_account.isAnon()) {
c->setPassword(oauthToken);
// fetch ignored users
{
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, [=] {
_twitchBlockedUsersMutex.lock();
_twitchBlockedUsers.clear();
_twitchBlockedUsersMutex.unlock();
QByteArray data = reply->readAll();
QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
QJsonObject root = jsonDoc.object();
// nextLink =
// root.value("_links").toObject().value("next").toString();
auto blocks = root.value("blocks").toArray();
_twitchBlockedUsersMutex.lock();
for (QJsonValue block : blocks) {
QJsonObject user = block.toObject().value("user").toObject();
// display_name
_twitchBlockedUsers.insert(user.value("name").toString().toLower(), true);
}
_twitchBlockedUsersMutex.unlock();
manager->deleteLater();
});
}
}
// fetch available twitch emtoes
{
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();
QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
QJsonObject root = jsonDoc.object();
// nextLink =
// root.value("_links").toObject().value("next").toString();
auto blocks = root.value("blocks").toArray();
_twitchBlockedUsersMutex.lock();
for (QJsonValue block : blocks) {
QJsonObject user = block.toObject().value("user").toObject();
// display_name
_twitchBlockedUsers.insert(user.value("name").toString().toLower(), true);
}
_twitchBlockedUsersMutex.unlock();
});
}
c->setHost("irc.chat.twitch.tv");
c->setPort(6667);
c->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/commands"));
c->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/tags"));
QMutexLocker locker(&_connectionMutex);
if (generation == _connectionGeneration) {
c->moveToThread(QCoreApplication::instance()->thread());
_connection = std::shared_ptr<Communi::IrcConnection>(c);
for (auto &channel : ChannelManager::getInstance().getItems()) {
c->sendRaw("JOIN #" + channel->getName());
}
c->open();
} else {
delete c;
}
}
void IrcManager::disconnect()
{
_connectionMutex.lock();
auto c = _connection;
if (_connection.get() != NULL) {
_connection = std::shared_ptr<Communi::IrcConnection>();
}
_connectionMutex.unlock();
}
void IrcManager::send(QString raw)
{
_connectionMutex.lock();
_connection->sendRaw(raw);
_connectionMutex.unlock();
}
void IrcManager::sendJoin(const QString &channel)
{
_connectionMutex.lock();
if (_connection.get() != NULL) {
_connection->sendRaw("JOIN #" + channel);
}
_connectionMutex.unlock();
}
void IrcManager::sendMessage(const QString &channelName, const QString &message)
{
_connectionMutex.lock();
if (_connection.get() != nullptr) {
qDebug() << "IRC Manager send message " << message << " to channel " << channelName;
QString xd = "PRIVMSG #" + channelName + " :" + message;
qDebug() << xd;
_connection->sendRaw(xd);
}
_connectionMutex.unlock();
}
void IrcManager::partChannel(const QString &channel)
{
_connectionMutex.lock();
if (_connection.get() != NULL) {
_connection.get()->sendRaw("PART #" + channel);
}
_connectionMutex.unlock();
}
void IrcManager::messageReceived(Communi::IrcMessage * /*message*/)
{
// qInfo(message->command().toStdString().c_str());
/*
const QString &command = message->command();
if (command == "CLEARCHAT") {
} else if (command == "ROOMSTATE") {
} else if (command == "USERSTATE") {
} else if (command == "WHISPER") {
} else if (command == "USERNOTICE") {
}
*/
}
void IrcManager::privateMessageReceived(Communi::IrcPrivateMessage *message)
{
auto c = ChannelManager::getInstance().getChannel(message->target().mid(1));
if (c != NULL) {
messages::MessageParseArgs args;
c->addMessage(twitch::TwitchMessageBuilder::parse(message, c.get(), args));
}
}
bool IrcManager::isTwitchBlockedUser(QString const &username)
{
QMutexLocker locker(&_twitchBlockedUsersMutex);
auto iterator = _twitchBlockedUsers.find(username);
return iterator != _twitchBlockedUsers.end();
}
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());
QNetworkRequest request(url);
auto reply = _accessManager.put(request, QByteArray());
reply->waitForReadyRead(10000);
if (reply->error() == QNetworkReply::NoError) {
_twitchBlockedUsersMutex.lock();
_twitchBlockedUsers.insert(username, true);
_twitchBlockedUsersMutex.unlock();
return true;
}
reply->deleteLater();
errorMessage = "Error while ignoring user \"" + username + "\": " + reply->errorString();
return false;
}
void IrcManager::addIgnoredUser(QString const &username)
{
QString errorMessage;
if (!tryAddIgnoredUser(username, errorMessage)) {
// TODO: Implement IrcManager::addIgnoredUser
}
}
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());
QNetworkRequest request(url);
auto reply = _accessManager.deleteResource(request);
reply->waitForReadyRead(10000);
if (reply->error() == QNetworkReply::NoError) {
_twitchBlockedUsersMutex.lock();
_twitchBlockedUsers.remove(username);
_twitchBlockedUsersMutex.unlock();
return true;
}
reply->deleteLater();
errorMessage = "Error while unignoring user \"" + username + "\": " + reply->errorString();
return false;
}
void IrcManager::removeIgnoredUser(QString const &username)
{
QString errorMessage;
if (!tryRemoveIgnoredUser(username, errorMessage)) {
// TODO: Implement IrcManager::removeIgnoredUser
}
}
QNetworkAccessManager &IrcManager::getAccessManager()
{
return _accessManager;
}
} // namespace chatterino
+76
View File
@@ -0,0 +1,76 @@
#ifndef IRCMANAGER_H
#define IRCMANAGER_H
#define TWITCH_MAX_MESSAGELENGTH 500
#include "messages/message.h"
#include "twitch/twitchuser.h"
#include <IrcMessage>
#include <QMap>
#include <QMutex>
#include <QNetworkAccessManager>
#include <QString>
#include <memory>
namespace chatterino {
class IrcManager : public QObject
{
Q_OBJECT
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);
QNetworkAccessManager &getAccessManager();
void sendJoin(const QString &channel);
void sendMessage(const QString &channelName, const QString &message);
void partChannel(const QString &channel);
const twitch::TwitchUser &getUser() const;
void setUser(const twitch::TwitchUser &account);
private:
static IrcManager instance;
IrcManager();
// variables
twitch::TwitchUser _account;
std::shared_ptr<Communi::IrcConnection> _connection;
QMutex _connectionMutex;
long _connectionGeneration;
QMap<QString, bool> _twitchBlockedUsers;
QMutex _twitchBlockedUsersMutex;
QNetworkAccessManager _accessManager;
// methods
void beginConnecting();
void messageReceived(Communi::IrcMessage *message);
void privateMessageReceived(Communi::IrcPrivateMessage *message);
};
} // namespace chatterino
#endif // IRCMANAGER_H
+76
View File
@@ -0,0 +1,76 @@
#include "loggingchannel.h"
#include "loggingmanager.h"
#include <QDir>
#include <ctime>
namespace chatterino {
namespace logging {
Channel::Channel(const QString &_channelName, const QString &_baseDirectory)
: channelName(_channelName)
, baseDirectory(_baseDirectory)
{
QDateTime now = QDateTime::currentDateTime();
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.open(QIODevice::Append);
this->appendLine(this->generateOpeningString(now));
}
Channel::~Channel()
{
this->appendLine(this->generateClosingString());
this->fileHandle.close();
}
void Channel::append(std::shared_ptr<messages::Message> message)
{
QDateTime now = QDateTime::currentDateTime();
QString str;
str.append('[');
str.append(now.toString("HH:mm:ss"));
str.append("] ");
str.append(message->getUserName());
str.append(": ");
str.append(message->getContent());
str.append('\n');
this->appendLine(str);
}
QString Channel::generateOpeningString(const QDateTime &now) const
{
QString ret = QLatin1Literal("# Start logging at ");
ret.append(now.toString("yyyy-MM-dd HH:mm:ss "));
ret.append(now.timeZoneAbbreviation());
ret.append('\n');
return ret;
}
QString Channel::generateClosingString(const QDateTime &now) const
{
QString ret = QLatin1Literal("# Stop logging at ");
ret.append(now.toString("yyyy-MM-dd HH:mm:ss"));
ret.append(now.timeZoneAbbreviation());
ret.append('\n');
return ret;
}
void Channel::appendLine(const QString &line)
{
this->fileHandle.write(line.toUtf8());
this->fileHandle.flush();
}
} // namespace logging
} // namespace chatterino
+39
View File
@@ -0,0 +1,39 @@
#ifndef LOGGINGCHANNEL_H
#define LOGGINGCHANNEL_H
#include "messages/message.h"
#include <QDateTime>
#include <QFile>
#include <QString>
#include <memory>
namespace chatterino {
namespace logging {
class Channel
{
public:
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;
void appendLine(const QString &line);
private:
QString channelName;
const QString &baseDirectory;
QString fileName;
QFile fileHandle;
};
} // namespace logging
} // namespace chatterino
#endif // LOGGINGCHANNEL_H
+100
View File
@@ -0,0 +1,100 @@
#include "loggingmanager.h"
#include <QDir>
#include <QStandardPaths>
#include <unordered_map>
namespace chatterino {
namespace logging {
static QString logBasePath;
static QString channelBasePath;
static QString whispersBasePath;
static QString mentionsBasePath;
std::unordered_map<std::string, std::weak_ptr<Channel>> channels;
void init()
{
// Make sure all folders are properly created
logBasePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
QDir::separator() + "Logs";
channelBasePath = logBasePath + QDir::separator() + "Channels";
whispersBasePath = logBasePath + QDir::separator() + "Whispers";
mentionsBasePath = logBasePath + QDir::separator() + "Mentions";
{
QDir dir(logBasePath);
if (!dir.exists()) {
dir.mkpath(".");
}
}
{
QDir dir(channelBasePath);
if (!dir.exists()) {
dir.mkpath(".");
}
}
{
QDir dir(whispersBasePath);
if (!dir.exists()) {
dir.mkpath(".");
}
}
{
QDir dir(mentionsBasePath);
if (!dir.exists()) {
dir.mkpath(".");
}
}
}
static const QString &getBaseDirectory(const QString &channelName)
{
if (channelName == "/whispers") {
return whispersBasePath;
} else if (channelName == "/mentions") {
return mentionsBasePath;
} else {
return channelBasePath;
}
}
std::shared_ptr<Channel> get(const QString &channelName)
{
if (channelName.isEmpty()) {
return nullptr;
}
const QString &baseDirectory = getBaseDirectory(channelName);
auto channel = channels.find(channelName.toStdString());
if (channel == std::end(channels)) {
// This channel is definitely not logged yet.
// Create shared_ptr that we will return, and store a weak_ptr reference
std::shared_ptr<Channel> ret =
std::shared_ptr<Channel>(new Channel(channelName, baseDirectory));
channels[channelName.toStdString()] = std::weak_ptr<Channel>(ret);
return ret;
}
if (auto ret = channels[channelName.toStdString()].lock()) {
return ret;
}
std::shared_ptr<Channel> ret =
std::shared_ptr<Channel>(new Channel(channelName, baseDirectory));
channels[channelName.toStdString()] = std::weak_ptr<Channel>(ret);
return ret;
}
} // namespace logging
} // namespace chatterino
+17
View File
@@ -0,0 +1,17 @@
#ifndef LOGGINGMANAGER_H
#define LOGGINGMANAGER_H
#include "loggingchannel.h"
#include <memory>
namespace chatterino {
namespace logging {
void init();
std::shared_ptr<Channel> get(const QString &channelName);
} // namespace logging
} // namespace chatterino
#endif // LOGGINGMANAGER_H
+96
View File
@@ -0,0 +1,96 @@
#include "channelmanager.h"
#include "colorscheme.h"
#include "common.h"
#include "emojis.h"
#include "emotemanager.h"
#include "ircmanager.h"
#include "logging/loggingmanager.h"
#include "resources.h"
#include "settingsmanager.h"
#include "widgets/mainwindow.h"
#include "windowmanager.h"
#include <QApplication>
#include <QClipboard>
#include <QDir>
#include <QStandardPaths>
#include <boost/signals2.hpp>
#include <pajlada/settings/settingmanager.hpp>
using namespace chatterino;
using namespace chatterino::widgets;
namespace {
inline bool initSettings(bool portable)
{
QString settingsPath;
if (portable) {
settingsPath.append(QDir::currentPath());
} else {
// Get settings path
settingsPath.append(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
if (settingsPath.isEmpty()) {
printf("Error finding writable location for settings\n");
return false;
}
}
if (!QDir().mkpath(settingsPath)) {
printf("Error creating directories for settings: %s\n", qPrintable(settingsPath));
return false;
}
settingsPath.append("/settings.json");
pajlada::Settings::SettingManager::load(qPrintable(settingsPath));
return true;
}
} // namespace
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// Options
bool portable = false;
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "portable") == 0) {
portable = true;
}
}
// Initialize settings
if (!initSettings(portable)) {
printf("Error initializing settings\n");
return 1;
}
chatterino::logging::init();
SettingsManager::getInstance().load();
Resources::load();
Emojis::loadEmojis();
EmoteManager::getInstance().loadGlobalEmotes();
ColorScheme::getInstance().init();
WindowManager::getInstance().load();
MainWindow &w = WindowManager::getInstance().getMainWindow();
w.show();
IrcManager::getInstance().connect();
int ret = a.exec();
SettingsManager::getInstance().save();
// Save settings
pajlada::Settings::SettingManager::save();
WindowManager::getInstance().save();
return ret;
}
+1
View File
@@ -0,0 +1 @@
../.clang-format
+107
View File
@@ -0,0 +1,107 @@
#include "messages/lazyloadedimage.h"
#include "asyncexec.h"
#include "emotemanager.h"
#include "ircmanager.h"
#include "util/urlfetch.h"
#include "windowmanager.h"
#include <QBuffer>
#include <QImageReader>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QTimer>
#include <functional>
namespace chatterino {
namespace messages {
LazyLoadedImage::LazyLoadedImage(const QString &url, qreal scale, const QString &name,
const QString &tooltip, const QMargins &margin, bool isHat)
: _currentPixmap(NULL)
, _currentFrame(0)
, _currentFrameOffset(0)
, _url(url)
, _name(name)
, _tooltip(tooltip)
, _animated(false)
, _margin(margin)
, _ishat(isHat)
, _scale(scale)
, _isLoading(false)
{
}
LazyLoadedImage::LazyLoadedImage(QPixmap *image, qreal scale, const QString &name,
const QString &tooltip, const QMargins &margin, bool isHat)
: _currentPixmap(image)
, _currentFrame(0)
, _currentFrameOffset(0)
, _name(name)
, _tooltip(tooltip)
, _animated(false)
, _margin(margin)
, _ishat(isHat)
, _scale(scale)
, _isLoading(true)
{
}
void LazyLoadedImage::loadImage()
{
util::urlFetch(_url, [=](QNetworkReply &reply) {
QByteArray array = reply.readAll();
QBuffer buffer(&array);
buffer.open(QIODevice::ReadOnly);
QImage image;
QImageReader reader(&buffer);
bool first = true;
for (int index = 0; index < reader.imageCount(); ++index) {
if (reader.read(&image)) {
auto pixmap = new QPixmap(QPixmap::fromImage(image));
if (first) {
first = false;
_currentPixmap = pixmap;
}
FrameData data;
data.duration = std::max(20, reader.nextImageDelay());
data.image = pixmap;
_allFrames.push_back(data);
}
}
if (_allFrames.size() > 1) {
_animated = true;
EmoteManager::getInstance().getGifUpdateSignal().connect([this] { gifUpdateTimout(); });
}
EmoteManager::getInstance().incGeneration();
WindowManager::getInstance().layoutVisibleChatWidgets();
});
}
void LazyLoadedImage::gifUpdateTimout()
{
_currentFrameOffset += GIF_FRAME_LENGTH;
while (true) {
if (_currentFrameOffset > _allFrames.at(_currentFrame).duration) {
_currentFrameOffset -= _allFrames.at(_currentFrame).duration;
_currentFrame = (_currentFrame + 1) % _allFrames.size();
} else {
break;
}
}
_currentPixmap = _allFrames[_currentFrame].image;
}
} // namespace messages
} // namespace chatterino
+109
View File
@@ -0,0 +1,109 @@
#ifndef LAZYLOADEDIMAGE_H
#define LAZYLOADEDIMAGE_H
#include <QPixmap>
#include <QString>
namespace chatterino {
namespace messages {
class LazyLoadedImage : QObject
{
public:
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(),
bool isHat = false);
const QPixmap *getPixmap()
{
if (!_isLoading) {
_isLoading = true;
loadImage();
}
return _currentPixmap;
}
qreal getScale() const
{
return _scale;
}
const QString &getUrl() const
{
return _url;
}
const QString &getName() const
{
return _name;
}
const QString &getTooltip() const
{
return _tooltip;
}
const QMargins &getMargin() const
{
return _margin;
}
bool getAnimated() const
{
return _animated;
}
bool isHat() const
{
return _ishat;
}
int getWidth() const
{
if (_currentPixmap == NULL) {
return 16;
}
return _currentPixmap->width();
}
int getHeight() const
{
if (_currentPixmap == NULL) {
return 16;
}
return _currentPixmap->height();
}
private:
struct FrameData {
QPixmap *image;
int duration;
};
QPixmap *_currentPixmap;
std::vector<FrameData> _allFrames;
int _currentFrame;
int _currentFrameOffset;
QString _url;
QString _name;
QString _tooltip;
bool _animated;
QMargins _margin;
bool _ishat;
qreal _scale;
bool _isLoading;
void loadImage();
void gifUpdateTimout();
};
} // namespace messages
} // namespace chatterino
#endif // LAZYLOADEDIMAGE_H
+100
View File
@@ -0,0 +1,100 @@
#ifndef LIMITEDQUEUE_H
#define LIMITEDQUEUE_H
#include "messages/limitedqueuesnapshot.h"
#include <memory>
#include <mutex>
#include <vector>
namespace chatterino {
namespace messages {
template <typename T>
class LimitedQueue
{
public:
LimitedQueue(int limit = 100, int buffer = 25)
: _offset(0)
, _limit(limit)
, _buffer(buffer)
{
_vector = std::make_shared<std::vector<T>>();
_vector->reserve(_limit + _buffer);
}
void clear()
{
std::lock_guard<std::mutex> lock(_mutex);
_vector = std::make_shared<std::vector<T>>();
_vector->reserve(_limit + _buffer);
_offset = 0;
}
// return true if an item was deleted
// deleted will be set if the item was deleted
bool appendItem(const T &item, T &deleted)
{
std::lock_guard<std::mutex> lock(_mutex);
if (_vector->size() >= _limit) {
// vector is full
if (_offset == _buffer) {
deleted = _vector->at(_offset);
// create new vector
auto newVector = std::make_shared<std::vector<T>>();
newVector->reserve(_limit + _buffer);
for (unsigned int i = 0; i < _limit - 1; i++) {
newVector->push_back(_vector->at(i + _offset));
}
newVector->push_back(item);
_offset = 0;
_vector = newVector;
return true;
} else {
deleted = _vector->at(_offset);
// append item and increment offset("deleting" first element)
_vector->push_back(item);
_offset++;
return true;
}
} else {
// append item
_vector->push_back(item);
return false;
}
}
messages::LimitedQueueSnapshot<T> getSnapshot()
{
std::lock_guard<std::mutex> lock(_mutex);
if (_vector->size() < _limit) {
return LimitedQueueSnapshot<T>(_vector, _offset, _vector->size());
} else {
return LimitedQueueSnapshot<T>(_vector, _offset, _limit);
}
}
private:
std::shared_ptr<std::vector<T>> _vector;
std::mutex _mutex;
unsigned int _offset;
unsigned int _limit;
unsigned int _buffer;
};
} // namespace messages
} // namespace chatterino
#endif // LIMITEDQUEUE_H
+40
View File
@@ -0,0 +1,40 @@
#ifndef LIMITEDQUEUESNAPSHOT_H
#define LIMITEDQUEUESNAPSHOT_H
#include <memory>
#include <vector>
namespace chatterino {
namespace messages {
template <typename T>
class LimitedQueueSnapshot
{
public:
LimitedQueueSnapshot(std::shared_ptr<std::vector<T>> vector, int offset, int size)
: _vector(vector)
, _offset(offset)
, _length(size)
{
}
int getSize()
{
return _length;
}
T const &operator[](int index) const
{
return _vector->at(index + _offset);
}
private:
std::shared_ptr<std::vector<T>> _vector;
int _offset;
int _length;
};
} // namespace messages
} // namespace chatterino
#endif // LIMITEDQUEUESNAPSHOT_H
+18
View File
@@ -0,0 +1,18 @@
#include "messages/link.h"
namespace chatterino {
namespace messages {
Link::Link()
: type(None)
, value(QString())
{
}
Link::Link(Type type, const QString &value)
: type(type)
, value(value)
{
}
}
}
+48
View File
@@ -0,0 +1,48 @@
#ifndef LINK_H
#define LINK_H
#include <QString>
namespace chatterino {
namespace messages {
class Link
{
public:
enum Type {
None,
Url,
CloseCurrentSplit,
UserInfo,
UserTimeout,
UserBan,
InsertText,
ShowMessage,
};
Link();
Link(Type getType, const QString &getValue);
bool isValid() const
{
return type != None;
}
Type getType() const
{
return type;
}
const QString &getValue() const
{
return value;
}
private:
Type type;
QString value;
};
}
}
#endif // LINK_H
+85
View File
@@ -0,0 +1,85 @@
#include "messages/message.h"
#include "channel.h"
#include "colorscheme.h"
#include "emojis.h"
#include "emotemanager.h"
#include "fontmanager.h"
#include "ircmanager.h"
#include "messages/link.h"
#include "qcolor.h"
#include "resources.h"
#include "settingsmanager.h"
#include <QObjectUserData>
#include <QStringList>
#include <ctime>
#include <list>
#include <tuple>
namespace chatterino {
namespace messages {
Message::Message(const QString &text)
: _words()
{
_words.push_back(
Word(text, Word::Text, ColorScheme::getInstance().SystemMessageColor, text, QString()));
}
Message::Message(const std::vector<Word> &words)
: _words(words)
{
}
bool Message::getCanHighlightTab() const
{
return _highlightTab;
}
const QString &Message::getTimeoutUser() const
{
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
} // namespace chatterino
+69
View File
@@ -0,0 +1,69 @@
#ifndef MESSAGE_H
#define MESSAGE_H
#include "messages/message.h"
#include "messages/messageparseargs.h"
#include "messages/word.h"
#include "messages/wordpart.h"
#include <IrcMessage>
#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 std::vector<messages::Word> &words);
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;
static LazyLoadedImage *badgeAdmin;
static LazyLoadedImage *badgeGlobalmod;
static LazyLoadedImage *badgeModerator;
static LazyLoadedImage *badgeTurbo;
static LazyLoadedImage *badgeBroadcaster;
static LazyLoadedImage *badgePremium;
static QRegularExpression *cheerRegex;
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 = "";
std::vector<Word> _words;
};
} // namespace messages
} // namespace chatterino
#endif // MESSAGE_H
+54
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();
}
}
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef MESSAGEBUILDER_H
#define MESSAGEBUILDER_H
#include "messages/message.h"
#include <ctime>
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
+17
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
+320
View File
@@ -0,0 +1,320 @@
#include "messageref.h"
#include "emotemanager.h"
#include "settingsmanager.h"
#include <QDebug>
#define MARGIN_LEFT 8
#define MARGIN_RIGHT 8
#define MARGIN_TOP 8
#define MARGIN_BOTTOM 8
using namespace chatterino::messages;
namespace chatterino {
namespace messages {
MessageRef::MessageRef(SharedMessage message)
: _message(message)
, _wordParts()
{
}
Message *MessageRef::getMessage()
{
return _message.get();
}
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 =
FontManager::getInstance().getFontMetrics(FontManager::Medium).height();
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();
// calculate word sizes
if (!redraw && !recalculateImages && !recalculateText && !newWordTypes) {
return false;
}
_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;
int right = width - MARGIN_RIGHT;
int lineNumber = 0;
int lineStart = 0;
int lineHeight = 0;
bool first = true;
_wordParts.clear();
uint32_t flags = SettingsManager::getInstance().getWordTypeMask();
for (auto it = _message->getWords().begin(); it != _message->getWords().end(); ++it) {
Word &word = *it;
if ((word.getType() & flags) == Word::None) {
continue;
}
int xOffset = 0, yOffset = 0;
if (enableEmoteMargins) {
if (word.isImage() && word.getImage().isHat()) {
xOffset = -word.getWidth() + 2;
} else {
xOffset = word.getXOffset();
yOffset = word.getYOffset();
}
}
// word wrapping
if (word.isText() && word.getWidth() + MARGIN_LEFT > right) {
alignWordParts(lineStart, lineHeight);
y += lineHeight;
const QString &text = word.getText();
int start = 0;
QFontMetrics &metrics = word.getFontMetrics();
int width = 0;
std::vector<short> &charWidths = word.getCharacterWidthCache();
if (charWidths.size() == 0) {
for (int i = 0; i < text.length(); i++) {
charWidths.push_back(metrics.charWidth(text, i));
}
}
for (int i = 2; i <= text.length(); i++) {
if ((width = width + charWidths[i - 1]) + MARGIN_LEFT > right) {
QString mid = text.mid(start, i - start - 1);
_wordParts.push_back(WordPart(word, MARGIN_LEFT, y, width, word.getHeight(),
lineNumber, mid, mid));
y += metrics.height();
start = i - 1;
width = 0;
lineNumber++;
}
}
QString mid(text.mid(start));
width = metrics.width(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 = _wordParts.size() - 1;
first = false;
} else if (first || x + word.getWidth() + xOffset <= right) {
// fits in the line
_wordParts.push_back(
WordPart(word, x, y - word.getHeight(), lineNumber, word.getCopyText()));
x += word.getWidth() + xOffset;
x += spaceWidth;
lineHeight = std::max(word.getHeight(), lineHeight);
first = false;
} else {
// doesn't fit in the line
alignWordParts(lineStart, lineHeight);
y += lineHeight;
_wordParts.push_back(
WordPart(word, MARGIN_LEFT, y - word.getHeight(), lineNumber, word.getCopyText()));
lineStart = _wordParts.size() - 1;
lineHeight = word.getHeight();
x = word.getWidth() + MARGIN_LEFT;
x += spaceWidth;
lineNumber++;
}
}
alignWordParts(lineStart, lineHeight);
if (_height != y + lineHeight) {
sizeChanged = true;
_height = y + lineHeight;
}
if (sizeChanged) {
buffer = nullptr;
}
updateBuffer = true;
return true;
}
const std::vector<WordPart> &MessageRef::getWordParts() const
{
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, Word &word)
{
// 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;
}
}
return false;
}
int MessageRef::getSelectionIndex(QPoint position)
{
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 < _wordParts.size(); i++) {
WordPart &part = _wordParts[i];
if (part.getLineNumber() != 0 && position.y() < part.getY()) {
break;
}
if (part.getLineNumber() != lineNumber) {
lineStart = i - 1;
lineNumber = part.getLineNumber();
}
lineEnd = part.getLineNumber() == 0 ? i : i + 1;
}
// count up to the cursor
int index = 0;
for (int i = 0; i < lineStart; i++) {
WordPart &part = _wordParts[i];
index += part.getWord().isImage() ? 2 : part.getText().length() + 1;
}
for (int i = lineStart; i < lineEnd; i++) {
WordPart &part = _wordParts[i];
// curser is left of the word part
if (position.x() < part.getX()) {
break;
}
// cursor is right of the word part
if (position.x() > part.getX() + part.getWidth()) {
index += part.getWord().isImage() ? 2 : part.getText().length() + 1;
continue;
}
// cursor is over the word part
if (part.getWord().isImage()) {
index++;
} else {
auto text = part.getWord().getText();
int x = part.getX();
for (int j = 0; j < text.length(); j++) {
if (x > position.x()) {
break;
}
index++;
x = part.getX() + part.getWord().getFontMetrics().width(text, j + 1);
}
}
break;
}
return index;
}
} // namespace messages
} // namespace chatterino
+54
View File
@@ -0,0 +1,54 @@
#ifndef MESSAGEREF_H
#define MESSAGEREF_H
#include "messages/message.h"
#include <QPixmap>
#include <memory>
namespace chatterino {
namespace messages {
class MessageRef;
typedef std::shared_ptr<MessageRef> SharedMessageRef;
class MessageRef
{
public:
MessageRef(SharedMessage message);
Message *getMessage();
int getHeight() const;
bool layout(int width, bool enableEmoteMargins = true);
const std::vector<WordPart> &getWordParts() const;
std::shared_ptr<QPixmap> buffer = nullptr;
bool updateBuffer = false;
bool tryGetWordPart(QPoint point, messages::Word &word);
int getSelectionIndex(QPoint position);
private:
// variables
SharedMessage _message;
std::vector<messages::WordPart> _wordParts;
int _height = 0;
int _currentLayoutWidth = -1;
int _fontGeneration = -1;
int _emoteGeneration = -1;
Word::Type _currentWordTypes = Word::None;
// methods
void alignWordParts(int lineStart, int lineHeight);
};
} // namespace messages
} // namespace chatterino
#endif // MESSAGEREF_H
+134
View File
@@ -0,0 +1,134 @@
#include "messages/word.h"
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()
, _isImage(true)
, _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)
, _isImage(false)
, _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
+120
View File
@@ -0,0 +1,120 @@
#ifndef WORD_H
#define WORD_H
#include "fontmanager.h"
#include "messages/lazyloadedimage.h"
#include "messages/link.h"
#include <stdint.h>
#include <QRect>
#include <QString>
namespace chatterino {
namespace messages {
class Word
{
public:
enum Type : uint32_t {
None = 0,
Misc = (1 << 0),
Text = (1 << 1),
TimestampNoSeconds = (1 << 2),
TimestampWithSeconds = (1 << 3),
TwitchEmoteImage = (1 << 4),
TwitchEmoteText = (1 << 5),
BttvEmoteImage = (1 << 6),
BttvEmoteText = (1 << 7),
BttvGifEmoteImage = (1 << 8),
BttvGifEmoteText = (1 << 9),
FfzEmoteImage = (1 << 10),
FfzEmoteText = (1 << 11),
EmoteImages = TwitchEmoteImage | BttvEmoteImage | BttvGifEmoteImage | FfzEmoteImage,
BitsStatic = (1 << 12),
BitsAnimated = (1 << 13),
BadgeStaff = (1 << 14),
BadgeAdmin = (1 << 15),
BadgeGlobalMod = (1 << 16),
BadgeModerator = (1 << 17),
BadgeTurbo = (1 << 18),
BadgeBroadcaster = (1 << 19),
BadgePremium = (1 << 20),
BadgeChatterino = (1 << 21),
BadgeCheer = (1 << 22),
Badges = BadgeStaff | BadgeAdmin | BadgeGlobalMod | BadgeModerator | BadgeTurbo |
BadgeBroadcaster | BadgePremium | BadgeChatterino | BadgeCheer,
Username = (1 << 23),
BitsAmount = (1 << 24),
ButtonBan = (1 << 25),
ButtonTimeout = (1 << 26),
EmojiImage = (1 << 27),
EmojiText = (1 << 28),
Default = TimestampNoSeconds | Badges | Username | BitsStatic | FfzEmoteImage |
BttvEmoteImage | BttvGifEmoteImage | TwitchEmoteImage | BitsAmount | Text |
ButtonBan | ButtonTimeout
};
Word()
{
}
explicit Word(LazyLoadedImage *_image, Type getType, const QString &copytext,
const QString &getTooltip, const Link &getLink = Link());
explicit Word(const QString &_text, Type getType, const QColor &getColor,
const QString &copytext, const QString &getTooltip, const Link &getLink = Link());
LazyLoadedImage &getImage() const;
const QString &getText() const;
int getWidth() const;
int getHeight() const;
void setSize(int _width, int _height);
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);
std::vector<short> &getCharacterWidthCache() const;
private:
LazyLoadedImage *_image;
QString _text;
QColor _color;
bool _isImage;
Type _type;
QString _copyText;
QString _tooltip;
int _width = 16;
int _height = 16;
int _xOffset = 0;
int _yOffset = 0;
bool _hasTrailingSpace;
FontManager::Type _font = FontManager::Medium;
Link _link;
mutable std::vector<short> _characterWidthCache;
};
} // namespace messages
} // namespace chatterino
#endif // WORD_H
+106
View File
@@ -0,0 +1,106 @@
#include "messages/wordpart.h"
#include "messages/word.h"
namespace chatterino {
namespace messages {
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)
: _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;
}
}
}
+54
View File
@@ -0,0 +1,54 @@
#ifndef WORDPART_H
#define WORDPART_H
#include <QRect>
#include <QString>
namespace chatterino {
namespace messages {
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 getWidth, int getHeight, int _lineNumber,
const QString &getCopyText, const QString &customText, bool allowTrailingSpace = true);
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 &_word;
QString _copyText;
QString _text;
int _x;
int _y;
int _width;
int _height;
int _lineNumber;
bool _trailingSpace;
};
}
}
#endif // WORDPART_H
+21
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.
+555
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;
}
+109
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
+11
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);
}
+28
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
+235
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;
}
+53
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
+59
View File
@@ -0,0 +1,59 @@
#include "resources.h"
#include <QPixmap>
namespace chatterino {
messages::LazyLoadedImage *Resources::badgeStaff(NULL);
messages::LazyLoadedImage *Resources::badgeAdmin(NULL);
messages::LazyLoadedImage *Resources::badgeModerator(NULL);
messages::LazyLoadedImage *Resources::badgeGlobalmod(NULL);
messages::LazyLoadedImage *Resources::badgeTurbo(NULL);
messages::LazyLoadedImage *Resources::badgeBroadcaster(NULL);
messages::LazyLoadedImage *Resources::badgePremium(NULL);
messages::LazyLoadedImage *Resources::cheerBadge100000(NULL);
messages::LazyLoadedImage *Resources::cheerBadge10000(NULL);
messages::LazyLoadedImage *Resources::cheerBadge5000(NULL);
messages::LazyLoadedImage *Resources::cheerBadge1000(NULL);
messages::LazyLoadedImage *Resources::cheerBadge100(NULL);
messages::LazyLoadedImage *Resources::cheerBadge1(NULL);
messages::LazyLoadedImage *Resources::buttonBan(NULL);
messages::LazyLoadedImage *Resources::buttonTimeout(NULL);
Resources::Resources()
{
}
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::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"));
// 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"));
// 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);
}
}
+113
View File
@@ -0,0 +1,113 @@
#ifndef RESOURCES_H
#define RESOURCES_H
#include "messages/lazyloadedimage.h"
namespace chatterino {
class Resources
{
public:
static void load();
// badges
static messages::LazyLoadedImage *getBadgeStaff()
{
return badgeStaff;
}
static messages::LazyLoadedImage *getBadgeAdmin()
{
return badgeAdmin;
}
static messages::LazyLoadedImage *getBadgeGlobalmod()
{
return badgeGlobalmod;
}
static messages::LazyLoadedImage *getBadgeModerator()
{
return badgeModerator;
}
static messages::LazyLoadedImage *getBadgeTurbo()
{
return badgeTurbo;
}
static messages::LazyLoadedImage *getBadgeBroadcaster()
{
return badgeBroadcaster;
}
static messages::LazyLoadedImage *getBadgePremium()
{
return badgePremium;
}
// cheer badges
static messages::LazyLoadedImage *getCheerBadge100000()
{
return cheerBadge100000;
}
static messages::LazyLoadedImage *getCheerBadge10000()
{
return cheerBadge10000;
}
static messages::LazyLoadedImage *getCheerBadge5000()
{
return cheerBadge5000;
}
static messages::LazyLoadedImage *getCheerBadge1000()
{
return cheerBadge1000;
}
static messages::LazyLoadedImage *getCheerBadge100()
{
return cheerBadge100;
}
static messages::LazyLoadedImage *getCheerBadge1()
{
return cheerBadge1;
}
static messages::LazyLoadedImage *getButtonBan()
{
return buttonBan;
}
static messages::LazyLoadedImage *getButtonTimeout()
{
return buttonTimeout;
}
private:
Resources();
static messages::LazyLoadedImage *badgeStaff;
static messages::LazyLoadedImage *badgeAdmin;
static messages::LazyLoadedImage *badgeGlobalmod;
static messages::LazyLoadedImage *badgeModerator;
static messages::LazyLoadedImage *badgeTurbo;
static messages::LazyLoadedImage *badgeBroadcaster;
static messages::LazyLoadedImage *badgePremium;
static messages::LazyLoadedImage *cheerBadge100000;
static messages::LazyLoadedImage *cheerBadge10000;
static messages::LazyLoadedImage *cheerBadge5000;
static messages::LazyLoadedImage *cheerBadge1000;
static messages::LazyLoadedImage *cheerBadge100;
static messages::LazyLoadedImage *cheerBadge1;
static messages::LazyLoadedImage *buttonBan;
static messages::LazyLoadedImage *buttonTimeout;
};
} // namespace chatterino
#endif // RESOURCES_H
+77
View File
@@ -0,0 +1,77 @@
#ifndef SETTING_H
#define SETTING_H
#include <QSettings>
#include <QString>
#include <boost/signals2.hpp>
namespace chatterino {
class BaseSetting
{
public:
BaseSetting(const QString &_name)
: _name(_name)
{
}
virtual QVariant getVariant() = 0;
virtual void setVariant(QVariant value) = 0;
const QString &getName() const
{
return _name;
}
private:
QString _name;
};
template <typename T>
class Setting : public BaseSetting
{
public:
Setting(std::vector<std::reference_wrapper<BaseSetting>> &settingItems, const QString &_name,
const T &defaultValue)
: BaseSetting(_name)
, _value(defaultValue)
{
settingItems.push_back(*this);
}
const T &get() const
{
return _value;
}
void set(const T &newValue)
{
if (_value != newValue) {
_value = newValue;
valueChanged(newValue);
}
}
virtual QVariant getVariant() final
{
return QVariant::fromValue(_value);
}
virtual void setVariant(QVariant value) final
{
if (value.isValid()) {
assert(value.canConvert<T>());
set(value.value<T>());
}
}
boost::signals2::signal<void(const T &newValue)> valueChanged;
private:
T _value;
};
} // namespace chatterino
#endif // SETTING_H
+129
View File
@@ -0,0 +1,129 @@
#include "settingsmanager.h"
#include "appdatapath.h"
#include <QDebug>
#include <QDir>
#include <QStandardPaths>
using namespace chatterino::messages;
namespace chatterino {
SettingsManager::SettingsManager()
: _settings(Path::getAppdataPath() + "settings.ini", QSettings::IniFormat)
, 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) {
item.get().setVariant(_settings.value(item.get().getName()));
}
}
Word::Type SettingsManager::getWordTypeMask()
{
return _wordTypeMask;
}
bool SettingsManager::isIgnoredEmote(const QString &)
{
return false;
}
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
+86
View File
@@ -0,0 +1,86 @@
#ifndef APPSETTINGS_H
#define APPSETTINGS_H
#include "messages/word.h"
#include "setting.h"
#include "settingssnapshot.h"
#include <QSettings>
namespace chatterino {
class SettingsManager : public QObject
{
Q_OBJECT
public:
void load();
void save();
messages::Word::Type getWordTypeMask();
bool isIgnoredEmote(const QString &emote);
QSettings &getQSettings();
SettingsSnapshot createSnapshot();
signals:
void wordTypeMaskChanged();
private:
SettingsManager();
// variables
QSettings _settings;
std::vector<std::reference_wrapper<BaseSetting>> _settingsItems;
messages::Word::Type _wordTypeMask = messages::Word::Default;
// methods
void updateWordTypeMask();
public:
// Settings
Setting<QString> theme;
Setting<float> themeHue;
Setting<QString> selectedUser;
Setting<float> emoteScale;
Setting<float> mouseScrollMultiplier;
Setting<bool> scaleEmotesByLineHeight;
Setting<bool> showTimestamps;
Setting<bool> showTimestampSeconds;
Setting<bool> showLastMessageIndicator;
Setting<bool> allowDouplicateMessages;
Setting<bool> linksDoubleClickOnly;
Setting<bool> hideEmptyInput;
Setting<bool> showMessageLength;
Setting<bool> seperateMessages;
Setting<bool> mentionUsersWithAt;
Setting<bool> allowCommandsAtEnd;
Setting<bool> enableHighlights;
Setting<bool> enableHighlightSound;
Setting<bool> enableHighlightTaskbar;
Setting<bool> customHighlightSound;
Setting<bool> enableTwitchEmotes;
Setting<bool> enableBttvEmotes;
Setting<bool> enableFfzEmotes;
Setting<bool> enableEmojis;
Setting<bool> enableGifAnimations;
Setting<bool> enableGifs;
Setting<bool> inlineWhispers;
Setting<bool> windowTopMost;
Setting<bool> hideTabX;
Setting<bool> hidePreferencesButton;
Setting<bool> hideUserButton;
Setting<bool> useCustomWindowFrame;
public:
static SettingsManager &getInstance()
{
static SettingsManager instance;
return instance;
}
private:
};
} // namespace chatterino
#endif // APPSETTINGS_H
+33
View File
@@ -0,0 +1,33 @@
#ifndef SETTINGSSNAPSHOT_H
#define SETTINGSSNAPSHOT_H
#include "setting.h"
namespace chatterino {
struct SettingsSnapshot {
public:
SettingsSnapshot()
: _items()
{
}
void addItem(std::reference_wrapper<BaseSetting> setting, const QVariant &value)
{
_items.push_back(
std::pair<std::reference_wrapper<BaseSetting>, QVariant>(setting.get(), value));
}
void apply()
{
for (auto &item : _items) {
item.first.get().setVariant(item.second);
}
}
private:
std::vector<std::pair<std::reference_wrapper<BaseSetting>, QVariant>> _items;
};
} // namespace chatterino
#endif // SETTINGSSNAPSHOT_H
+1
View File
@@ -0,0 +1 @@
../.clang-format
+34
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
+347
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(), Link(Link::UserInfo, b.userName)));
// 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;
//}
}
}
+31
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
+349
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;
//}
//}
//}
//
+13
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
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");
}
}
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef ACCOUNT_H
#define ACCOUNT_H
#include "ircaccount.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;
};
} // namespace twitch
} // namespace chatterino
#endif // ACCOUNT_H
+1
View File
@@ -0,0 +1 @@
../.clang-format
+22
View File
@@ -0,0 +1,22 @@
#ifndef DISTANCEBETWEENPOINTS_H
#define DISTANCEBETWEENPOINTS_H
#include <math.h>
namespace chatterino {
namespace util {
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 sqrt(distance);
}
}
}
#endif // DISTANCEBETWEENPOINTS_H
+55
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
+1
View File
@@ -0,0 +1 @@
../.clang-format
+41
View File
@@ -0,0 +1,41 @@
#include "widgets/accountpopup.h"
#include "channel.h"
#include "ui_accountpopupform.h"
#include <QDebug>
namespace chatterino {
namespace widgets {
AccountPopupWidget::AccountPopupWidget(SharedChannel &channel)
: QWidget(nullptr)
, _ui(new Ui::AccountPopup)
, _channel(channel)
{
_ui->setupUi(this);
resize(0, 0);
setWindowFlags(Qt::FramelessWindowHint);
// Close button
connect(_ui->btnClose, &QPushButton::clicked, [=]() {
hide(); //
});
connect(_ui->btnPurge, &QPushButton::clicked, [=]() {
qDebug() << "xD: " << _channel->getName();
printf("Channel pointer in dialog: %p\n", _channel.get());
//_channel->sendMessage(QString(".timeout %1 0").arg(_ui->lblUsername->text()));
_channel->sendMessage("xD");
});
}
void AccountPopupWidget::setName(const QString &name)
{
_ui->lblUsername->setText(name);
}
} // namespace widgets
} // namespace chatterino
+35
View File
@@ -0,0 +1,35 @@
#ifndef USERPOPUPWIDGET_H
#define USERPOPUPWIDGET_H
#include <QWidget>
#include <memory>
namespace Ui {
class AccountPopup;
}
namespace chatterino {
class Channel;
namespace widgets {
class AccountPopupWidget : public QWidget
{
Q_OBJECT
public:
AccountPopupWidget(std::shared_ptr<Channel> &channel);
void setName(const QString &name);
private:
Ui::AccountPopup *_ui;
std::shared_ptr<Channel> &_channel;
};
} // namespace widgets
} // namespace chatterino
#endif // USERPOPUPWIDGET_H
+197
View File
@@ -0,0 +1,197 @@
#include "widgets/chatwidget.h"
#include "channelmanager.h"
#include "colorscheme.h"
#include "settingsmanager.h"
#include "widgets/textinputdialog.h"
#include <QDebug>
#include <QFont>
#include <QFontDatabase>
#include <QPainter>
#include <QVBoxLayout>
#include <boost/signals2.hpp>
using namespace chatterino::messages;
namespace chatterino {
namespace widgets {
ChatWidget::ChatWidget(QWidget *parent)
: QWidget(parent)
, _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.addWidget(&_header);
this->_vbox.addWidget(&_view, 1);
this->_vbox.addWidget(&_input);
}
ChatWidget::~ChatWidget()
{
detachChannel();
}
std::shared_ptr<Channel> ChatWidget::getChannel() const
{
return _channel;
}
std::shared_ptr<Channel> &ChatWidget::getChannelRef()
{
return _channel;
}
const QString &ChatWidget::getChannelName() const
{
return _channelName;
}
void ChatWidget::setChannelName(const QString &name)
{
QString channelName = name.trimmed();
// return if channel name is the same
if (QString::compare(channelName, _channelName, Qt::CaseInsensitive) == 0) {
_channelName = channelName;
_header.updateChannelText();
return;
}
// remove current channel
if (!_channelName.isEmpty()) {
ChannelManager::getInstance().removeChannel(_channelName);
detachChannel();
}
// update members
_channelName = channelName;
// update messages
_messages.clear();
printf("Set channel name xD %s\n", qPrintable(name));
if (channelName.isEmpty()) {
_channel = NULL;
} else {
_channel = ChannelManager::getInstance().addChannel(channelName);
printf("Created channel FeelsGoodMan %p\n", _channel.get());
attachChannel(_channel);
}
// 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([](SharedMessage &) {
//
});
auto snapshot = _channel.get()->getMessageSnapshot();
for (int i = 0; i < snapshot.getSize(); i++) {
SharedMessageRef deleted;
auto messageRef = new MessageRef(snapshot[i]);
_messages.appendItem(SharedMessageRef(messageRef), deleted);
}
}
void ChatWidget::detachChannel()
{
// 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(_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);
}
void ChatWidget::load(const boost::property_tree::ptree &tree)
{
// load tab text
try {
this->setChannelName(QString::fromStdString(tree.get<std::string>("channelName")));
} catch (boost::property_tree::ptree_error) {
}
}
boost::property_tree::ptree ChatWidget::save()
{
boost::property_tree::ptree tree;
tree.put("channelName", this->getChannelName().toStdString());
return tree;
}
} // namespace widgets
} // namespace chatterino
+69
View File
@@ -0,0 +1,69 @@
#ifndef CHATWIDGET_H
#define CHATWIDGET_H
#include "channel.h"
#include "messages/limitedqueuesnapshot.h"
#include "messages/messageref.h"
#include "messages/word.h"
#include "messages/wordpart.h"
#include "widgets/chatwidgetheader.h"
#include "widgets/chatwidgetinput.h"
#include "widgets/chatwidgetview.h"
#include <QFont>
#include <QVBoxLayout>
#include <QWidget>
#include <boost/property_tree/ptree.hpp>
#include <boost/signals2/connection.hpp>
namespace chatterino {
namespace widgets {
class ChatWidget : public QWidget
{
Q_OBJECT
public:
ChatWidget(QWidget *parent = 0);
~ChatWidget();
SharedChannel getChannel() const;
SharedChannel &getChannelRef();
const QString &getChannelName() const;
void setChannelName(const QString &name);
void showChangeChannelPopup();
messages::LimitedQueueSnapshot<messages::SharedMessageRef> getMessagesSnapshot();
void layoutMessages();
void updateGifEmotes();
protected:
void paintEvent(QPaintEvent *) override;
private:
void attachChannel(std::shared_ptr<Channel> _channel);
void detachChannel();
messages::LimitedQueue<messages::SharedMessageRef> _messages;
SharedChannel _channel;
QString _channelName;
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);
boost::property_tree::ptree save();
};
} // namespace widgets
} // namespace chatterino
#endif // CHATWIDGET_H
+199
View File
@@ -0,0 +1,199 @@
#include "widgets/chatwidgetheader.h"
#include "colorscheme.h"
#include "widgets/chatwidget.h"
#include "widgets/notebookpage.h"
#include <QByteArray>
#include <QDrag>
#include <QMimeData>
#include <QPainter>
namespace chatterino {
namespace widgets {
ChatWidgetHeader::ChatWidgetHeader(ChatWidget *parent)
: QWidget()
, _chatWidget(parent)
, _dragStart()
, _dragging(false)
, _leftLabel()
, _middleLabel()
, _rightLabel()
, _leftMenu(this)
, _rightMenu(this)
{
setFixedHeight(32);
updateColors();
updateChannelText();
setLayout(&_hbox);
_hbox.setMargin(0);
_hbox.addWidget(&_leftLabel);
_hbox.addWidget(&_middleLabel, 1);
_hbox.addWidget(&_rightLabel);
// left
_leftLabel.getLabel().setTextFormat(Qt::RichText);
_leftLabel.getLabel().setText("<img src=':/images/tool_moreCollapser_off16.png' />");
QObject::connect(&_leftLabel, &ChatWidgetHeaderButton::clicked, this,
&ChatWidgetHeader::leftButtonClicked);
_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
_middleLabel.setAlignment(Qt::AlignCenter);
connect(&_middleLabel, &SignalLabel::mouseDoubleClick, this,
&ChatWidgetHeader::mouseDoubleClickEvent);
// connect(&this->middleLabel, &SignalLabel::mouseDown, this,
// &ChatWidgetHeader::mouseDoubleClickEvent);
// right
_rightLabel.setMinimumWidth(height());
_rightLabel.getLabel().setTextFormat(Qt::RichText);
_rightLabel.getLabel().setText("ayy");
}
void ChatWidgetHeader::updateColors()
{
QPalette palette;
palette.setColor(QPalette::Foreground, ColorScheme::getInstance().Text);
_leftLabel.setPalette(palette);
_middleLabel.setPalette(palette);
_rightLabel.setPalette(palette);
}
void ChatWidgetHeader::updateChannelText()
{
const QString &c = _chatWidget->getChannelName();
_middleLabel.setText(c.isEmpty() ? "<no channel>" : c);
}
void ChatWidgetHeader::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.fillRect(rect(), ColorScheme::getInstance().ChatHeaderBackground);
painter.setPen(ColorScheme::getInstance().ChatHeaderBorder);
painter.drawRect(0, 0, width() - 1, height() - 1);
}
void ChatWidgetHeader::mousePressEvent(QMouseEvent *event)
{
_dragging = true;
_dragStart = event->pos();
}
void ChatWidgetHeader::mouseMoveEvent(QMouseEvent *event)
{
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) {
NotebookPage::isDraggingSplit = true;
NotebookPage::draggingSplit = chatWidget;
auto originalLocation = page->removeFromLayout(chatWidget);
// page->update();
QDrag *drag = new QDrag(chatWidget);
QMimeData *mimeData = new QMimeData;
mimeData->setData("chatterino/split", "xD");
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->exec(Qt::MoveAction);
if (dropAction == Qt::IgnoreAction) {
page->addToLayout(chatWidget, originalLocation);
}
NotebookPage::isDraggingSplit = false;
}
}
}
}
void ChatWidgetHeader::mouseDoubleClickEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
_chatWidget->showChangeChannelPopup();
}
}
void ChatWidgetHeader::leftButtonClicked()
{
_leftMenu.move(_leftLabel.mapToGlobal(QPoint(0, _leftLabel.height())));
_leftMenu.show();
}
void ChatWidgetHeader::rightButtonClicked()
{
}
void ChatWidgetHeader::menuAddSplit()
{
auto page = static_cast<NotebookPage *>(_chatWidget->parentWidget());
page->addChat();
}
void ChatWidgetHeader::menuCloseSplit()
{
printf("Close split\n");
}
void ChatWidgetHeader::menuMoveSplit()
{
}
void ChatWidgetHeader::menuPopup()
{
auto widget = new ChatWidget();
widget->setChannelName(_chatWidget->getChannelName());
widget->show();
}
void ChatWidgetHeader::menuChangeChannel()
{
_chatWidget->showChangeChannelPopup();
}
void ChatWidgetHeader::menuClearChat()
{
}
void ChatWidgetHeader::menuOpenChannel()
{
}
void ChatWidgetHeader::menuPopupPlayer()
{
}
void ChatWidgetHeader::menuReloadChannelEmotes()
{
}
void ChatWidgetHeader::menuManualReconnect()
{
}
void ChatWidgetHeader::menuShowChangelog()
{
}
}
}
+75
View File
@@ -0,0 +1,75 @@
#ifndef CHATWIDGETHEADER_H
#define CHATWIDGETHEADER_H
#include "signallabel.h"
#include "widgets/chatwidgetheaderbutton.h"
#include <QAction>
#include <QHBoxLayout>
#include <QLabel>
#include <QMenu>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QPoint>
#include <QWidget>
namespace chatterino {
namespace widgets {
class ChatWidget;
class ChatWidgetHeader : public QWidget
{
Q_OBJECT
public:
explicit ChatWidgetHeader(ChatWidget *parent);
ChatWidget *getChatWidget() const
{
return _chatWidget;
}
void updateColors();
void updateChannelText();
protected:
void paintEvent(QPaintEvent *);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event);
private:
ChatWidget * const _chatWidget;
QPoint _dragStart;
bool _dragging;
QHBoxLayout _hbox;
ChatWidgetHeaderButton _leftLabel;
SignalLabel _middleLabel;
ChatWidgetHeaderButton _rightLabel;
QMenu _leftMenu;
QMenu _rightMenu;
void leftButtonClicked();
void rightButtonClicked();
private slots:
void menuAddSplit();
void menuCloseSplit();
void menuMoveSplit();
void menuPopup();
void menuChangeChannel();
void menuClearChat();
void menuOpenChannel();
void menuPopupPlayer();
void menuReloadChannelEmotes();
void menuManualReconnect();
void menuShowChangelog();
};
}
}
#endif // CHATWIDGETHEADER_H
+98
View File
@@ -0,0 +1,98 @@
#include "widgets/chatwidgetheaderbutton.h"
#include "colorscheme.h"
#include <QBrush>
#include <QPainter>
namespace chatterino {
namespace widgets {
ChatWidgetHeaderButton::ChatWidgetHeaderButton(int spacing)
: QWidget()
, _hbox()
, _label()
, _mouseOver(false)
, _mouseDown(false)
{
setLayout(&_hbox);
_label.setAlignment(Qt::AlignCenter);
_hbox.setMargin(0);
_hbox.addSpacing(spacing);
_hbox.addWidget(&_label);
_hbox.addSpacing(spacing);
QObject::connect(&_label, &SignalLabel::mouseUp, this,
&ChatWidgetHeaderButton::labelMouseUp);
QObject::connect(&_label, &SignalLabel::mouseDown, this,
&ChatWidgetHeaderButton::labelMouseDown);
}
void ChatWidgetHeaderButton::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QBrush brush(ColorScheme::getInstance().IsLightTheme ? QColor(0, 0, 0, 32)
: QColor(255, 255, 255, 32));
if (_mouseDown) {
painter.fillRect(rect(), brush);
}
if (_mouseOver) {
painter.fillRect(rect(), brush);
}
}
void ChatWidgetHeaderButton::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
_mouseDown = true;
update();
}
}
void ChatWidgetHeaderButton::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
_mouseDown = false;
update();
emit clicked();
}
}
void ChatWidgetHeaderButton::enterEvent(QEvent *)
{
_mouseOver = true;
update();
}
void ChatWidgetHeaderButton::leaveEvent(QEvent *)
{
_mouseOver = false;
update();
}
void ChatWidgetHeaderButton::labelMouseUp()
{
_mouseDown = false;
update();
emit clicked();
}
void ChatWidgetHeaderButton::labelMouseDown()
{
_mouseDown = true;
update();
}
}
}
+51
View File
@@ -0,0 +1,51 @@
#ifndef CHATWIDGETHEADERBUTTON_H
#define CHATWIDGETHEADERBUTTON_H
#include "widgets/signallabel.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QPaintEvent>
#include <QWidget>
namespace chatterino {
namespace widgets {
class ChatWidgetHeaderButton : public QWidget
{
Q_OBJECT
public:
explicit ChatWidgetHeaderButton(int spacing = 6);
SignalLabel &getLabel()
{
return _label;
}
signals:
void clicked();
protected:
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
void enterEvent(QEvent *) Q_DECL_OVERRIDE;
void leaveEvent(QEvent *) Q_DECL_OVERRIDE;
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
private:
QHBoxLayout _hbox;
SignalLabel _label;
bool _mouseOver;
bool _mouseDown;
void labelMouseUp();
void labelMouseDown();
};
}
}
#endif // CHATWIDGETHEADERBUTTON_H
+141
View File
@@ -0,0 +1,141 @@
#include "widgets/chatwidgetinput.h"
#include "chatwidget.h"
#include "colorscheme.h"
#include "ircmanager.h"
#include "settingsmanager.h"
#include <QCompleter>
#include <QPainter>
#include <boost/signals2.hpp>
namespace chatterino {
namespace widgets {
ChatWidgetInput::ChatWidgetInput(ChatWidget *widget)
: _chatWidget(widget)
, _hbox()
, _vbox()
, _editContainer()
, _edit()
, _textLengthLabel()
, _emotesLabel(0)
{
setLayout(&_hbox);
setMaximumHeight(150);
_hbox.setMargin(4);
_hbox.addLayout(&_editContainer);
_hbox.addLayout(&_vbox);
_editContainer.addWidget(&_edit);
_editContainer.setMargin(4);
_vbox.addWidget(&_textLengthLabel);
_vbox.addStretch(1);
_vbox.addWidget(&_emotesLabel);
_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,
&ChatWidgetInput::editTextChanged);
// QObject::connect(&edit, &ResizingTextEdit::keyPressEvent, this,
// &ChatWidgetInput::editKeyPressed);
refreshTheme();
setMessageLengthVisisble(SettingsManager::getInstance().showMessageLength.get());
QStringList list;
list.append("asd");
list.append("asdf");
list.append("asdg");
list.append("asdh");
QCompleter *completer = new QCompleter(list, &_edit);
completer->setWidget(&_edit);
_edit.keyPressed.connect([this/*, completer*/](QKeyEvent *event) {
if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
auto c = _chatWidget->getChannel();
if (c == nullptr) {
return;
}
c->sendMessage(_edit.toPlainText());
event->accept();
_edit.setText(QString());
}
// else {
// completer->setCompletionPrefix("asdf");
// completer->complete();
// // completer->popup();
// }
});
/* XXX(pajlada): FIX THIS
QObject::connect(&Settings::getInstance().showMessageLength,
&BoolSetting::valueChanged, this,
&ChatWidgetInput::setMessageLengthVisisble);
*/
}
ChatWidgetInput::~ChatWidgetInput()
{
/* XXX(pajlada): FIX THIS
QObject::disconnect(
&Settings::getInstance().getShowMessageLength(),
&BoolSetting::valueChanged, this,
&ChatWidgetInput::setMessageLengthVisisble);
*/
}
void ChatWidgetInput::refreshTheme()
{
QPalette palette;
palette.setColor(QPalette::Foreground, ColorScheme::getInstance().Text);
_textLengthLabel.setPalette(palette);
_edit.setStyleSheet(ColorScheme::getInstance().InputStyleSheet);
}
void ChatWidgetInput::editTextChanged()
{
}
// void
// ChatWidgetInput::editKeyPressed(QKeyEvent *event)
//{
// if (event->key() == Qt::Key_Enter) {
// event->accept();
// IrcManager::send("PRIVMSG #" + edit.toPlainText();
// edit.setText(QString());
// }
//}
void ChatWidgetInput::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.fillRect(rect(), ColorScheme::getInstance().ChatInputBackground);
painter.setPen(ColorScheme::getInstance().ChatInputBorder);
painter.drawRect(0, 0, width() - 1, height() - 1);
}
void ChatWidgetInput::resizeEvent(QResizeEvent *)
{
if (height() == maximumHeight()) {
_edit.setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
} else {
_edit.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
}
}
}
+55
View File
@@ -0,0 +1,55 @@
#ifndef CHATWIDGETINPUT_H
#define CHATWIDGETINPUT_H
#include "resizingtextedit.h"
#include "widgets/chatwidgetheaderbutton.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPaintEvent>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QWidget>
namespace chatterino {
namespace widgets {
class ChatWidget;
class ChatWidgetInput : public QWidget
{
Q_OBJECT
public:
ChatWidgetInput(ChatWidget *parent);
~ChatWidgetInput();
protected:
void paintEvent(QPaintEvent *);
void resizeEvent(QResizeEvent *);
private:
ChatWidget *_chatWidget;
QHBoxLayout _hbox;
QVBoxLayout _vbox;
QHBoxLayout _editContainer;
ResizingTextEdit _edit;
QLabel _textLengthLabel;
ChatWidgetHeaderButton _emotesLabel;
private slots:
void refreshTheme();
void setMessageLengthVisisble(bool value)
{
_textLengthLabel.setHidden(!value);
}
void editTextChanged();
// void editKeyPressed(QKeyEvent *event);
};
} // namespace widgets
} // namespace chatterino
#endif // CHATWIDGETINPUT_H
+407
View File
@@ -0,0 +1,407 @@
#include "widgets/chatwidgetview.h"
#include "channelmanager.h"
#include "colorscheme.h"
#include "messages/message.h"
#include "messages/wordpart.h"
#include "settingsmanager.h"
#include "ui_accountpopupform.h"
#include "util/distancebetweenpoints.h"
#include "widgets/chatwidget.h"
#include <math.h>
#include <QDebug>
#include <QGraphicsBlurEffect>
#include <QPainter>
#include <chrono>
#include <functional>
namespace chatterino {
namespace widgets {
ChatWidgetView::ChatWidgetView(ChatWidget *parent)
: QWidget()
, _chatWidget(parent)
, _scrollbar(this)
, _userPopupWidget(_chatWidget->getChannelRef())
, _onlyUpdateEmotes(false)
, _mouseDown(false)
, _lastPressPosition()
{
setAttribute(Qt::WA_OpaquePaintEvent);
_scrollbar.setSmallChange(5);
setMouseTracking(true);
QObject::connect(&SettingsManager::getInstance(), &SettingsManager::wordTypeMaskChanged, this,
&ChatWidgetView::wordTypeMaskChanged);
_scrollbar.getCurrentValueChanged().connect([this] { update(); });
}
ChatWidgetView::~ChatWidgetView()
{
QObject::disconnect(&SettingsManager::getInstance(), &SettingsManager::wordTypeMaskChanged,
this, &ChatWidgetView::wordTypeMaskChanged);
}
bool ChatWidgetView::layoutMessages()
{
auto messages = _chatWidget->getMessagesSnapshot();
if (messages.getSize() == 0) {
_scrollbar.setVisible(false);
return false;
}
bool showScrollbar = false, redraw = false;
int start = _scrollbar.getCurrentValue();
int layoutWidth = _scrollbar.isVisible() ? width() - _scrollbar.width() : width();
// layout the visible messages in the view
if (messages.getSize() > start) {
int y = -(messages[start]->getHeight() * (fmod(_scrollbar.getCurrentValue(), 1)));
for (int i = start; i < messages.getSize(); ++i) {
auto message = messages[i];
redraw |= message->layout(layoutWidth, true);
y += message->getHeight();
if (y >= height()) {
break;
}
}
}
// layout the messages at the bottom to determine the scrollbar thumb size
int h = height() - 8;
for (int i = messages.getSize() - 1; i >= 0; i--) {
auto *message = messages[i].get();
message->layout(layoutWidth, true);
h -= message->getHeight();
if (h < 0) {
_scrollbar.setLargeChange((messages.getSize() - i) + (qreal)h / message->getHeight());
_scrollbar.setDesiredValue(_scrollbar.getDesiredValue());
showScrollbar = true;
break;
}
}
_scrollbar.setVisible(showScrollbar);
if (!showScrollbar) {
_scrollbar.setDesiredValue(0);
}
_scrollbar.setMaximum(messages.getSize());
return redraw;
}
void ChatWidgetView::updateGifEmotes()
{
_onlyUpdateEmotes = true;
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)
{
QPainter _painter(this);
_painter.setRenderHint(QPainter::SmoothPixmapTransform);
ColorScheme &scheme = ColorScheme::getInstance();
// only update gif emotes
if (_onlyUpdateEmotes) {
_onlyUpdateEmotes = false;
for (GifEmoteData &item : _gifEmotes) {
_painter.fillRect(item.rect, scheme.ChatBackground);
_painter.drawPixmap(item.rect, *item.image->getPixmap());
}
return;
}
// update all messages
_gifEmotes.clear();
_painter.fillRect(rect(), scheme.ChatBackground);
// code for tesing colors
/*
QColor color;
static ConcurrentMap<qreal, QImage *> imgCache;
std::function<QImage *(qreal)> getImg = [&scheme](qreal light) {
return imgCache.getOrAdd(light, [&scheme, &light] {
QImage *img = new QImage(150, 50, QImage::Format_RGB32);
QColor color;
for (int j = 0; j < 50; j++) {
for (qreal i = 0; i < 150; i++) {
color = QColor::fromHslF(i / 150.0, light, j / 50.0);
scheme.normalizeColor(color);
img->setPixelColor(i, j, color);
}
}
return img;
});
};
for (qreal k = 0; k < 4.8; k++) {
auto img = getImg(k / 5);
painter.drawImage(QRect(k * 150, 0, 150, 150), *img);
}
painter.fillRect(QRect(0, 9, 500, 2), QColor(0, 0, 0));*/
auto messages = _chatWidget->getMessagesSnapshot();
int start = _scrollbar.getCurrentValue();
if (start >= messages.getSize()) {
return;
}
int y = -(messages[start].get()->getHeight() * (fmod(_scrollbar.getCurrentValue(), 1)));
for (int i = start; i < messages.getSize(); ++i) {
messages::MessageRef *messageRef = messages[i].get();
std::shared_ptr<QPixmap> bufferPtr = messageRef->buffer;
QPixmap *buffer = bufferPtr.get();
bool updateBuffer = messageRef->updateBuffer;
if (buffer == nullptr) {
buffer = new QPixmap(width(), messageRef->getHeight());
bufferPtr = std::shared_ptr<QPixmap>(buffer);
updateBuffer = true;
}
// update messages that have been changed
if (updateBuffer) {
QPainter painter(buffer);
painter.fillRect(buffer->rect(), scheme.ChatBackground);
for (messages::WordPart const &wordPart : messageRef->getWordParts()) {
// image
if (wordPart.getWord().isImage()) {
messages::LazyLoadedImage &lli = wordPart.getWord().getImage();
const QPixmap *image = lli.getPixmap();
if (image != nullptr) {
painter.drawPixmap(QRect(wordPart.getX(), wordPart.getY(),
wordPart.getWidth(), wordPart.getHeight()),
*image);
}
}
// text
else {
QColor color = wordPart.getWord().getColor();
ColorScheme::getInstance().normalizeColor(color);
painter.setPen(color);
painter.setFont(wordPart.getWord().getFont());
painter.drawText(QRectF(wordPart.getX(), wordPart.getY(), 10000, 10000),
wordPart.getText(), QTextOption(Qt::AlignLeft | Qt::AlignTop));
}
}
messageRef->updateBuffer = false;
}
// get gif emotes
for (messages::WordPart const &wordPart : messageRef->getWordParts()) {
if (wordPart.getWord().isImage()) {
messages::LazyLoadedImage &lli = wordPart.getWord().getImage();
if (lli.getAnimated()) {
GifEmoteData data;
data.image = &lli;
QRect rect(wordPart.getX(), wordPart.getY() + y, wordPart.getWidth(),
wordPart.getHeight());
data.rect = rect;
_gifEmotes.push_back(data);
}
}
}
messageRef->buffer = bufferPtr;
_painter.drawPixmap(0, y, *buffer);
y += messageRef->getHeight();
if (y > height()) {
break;
}
}
for (GifEmoteData &item : _gifEmotes) {
_painter.fillRect(item.rect, scheme.ChatBackground);
_painter.drawPixmap(item.rect, *item.image->getPixmap());
}
}
void ChatWidgetView::wheelEvent(QWheelEvent *event)
{
if (_scrollbar.isVisible()) {
auto mouseMultiplier = SettingsManager::getInstance().mouseScrollMultiplier.get();
_scrollbar.setDesiredValue(
_scrollbar.getDesiredValue() - event->delta() / 10.0 * mouseMultiplier, true);
}
}
void ChatWidgetView::mouseMoveEvent(QMouseEvent *event)
{
std::shared_ptr<messages::MessageRef> message;
QPoint relativePos;
if (!tryGetMessageAt(event->pos(), message, relativePos)) {
setCursor(Qt::ArrowCursor);
return;
}
// int index = message->getSelectionIndex(relativePos);
// qDebug() << index;
messages::Word hoverWord;
if (!message->tryGetWordPart(relativePos, hoverWord)) {
setCursor(Qt::ArrowCursor);
return;
}
if (hoverWord.getLink().isValid()) {
setCursor(Qt::PointingHandCursor);
} else {
setCursor(Qt::ArrowCursor);
}
}
void ChatWidgetView::mousePressEvent(QMouseEvent *event)
{
_mouseDown = true;
_lastPressPosition = event->screenPos();
}
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 = util::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;
}
messages::Word hoverWord;
if (!message->tryGetWordPart(relativePos, hoverWord)) {
return;
}
auto &link = hoverWord.getLink();
switch (link.getType()) {
case messages::Link::UserInfo:
auto user = message->getMessage()->getUserName();
_userPopupWidget.setName(user);
_userPopupWidget.move(event->screenPos().toPoint());
_userPopupWidget.show();
_userPopupWidget.setFocus();
qDebug() << "Clicked " << user << "s message";
break;
}
}
bool ChatWidgetView::tryGetMessageAt(QPoint p, std::shared_ptr<messages::MessageRef> &_message,
QPoint &relativePos)
{
auto messages = _chatWidget->getMessagesSnapshot();
int start = _scrollbar.getCurrentValue();
if (start >= messages.getSize()) {
return false;
}
int y = -(messages[start]->getHeight() * (fmod(_scrollbar.getCurrentValue(), 1)));
for (int i = start; i < messages.getSize(); ++i) {
auto message = messages[i];
if (p.y() < y + message->getHeight()) {
relativePos = QPoint(p.x(), p.y() - y);
_message = message;
return true;
}
y += message->getHeight();
}
return false;
}
} // namespace widgets
} // namespace chatterino
+73
View File
@@ -0,0 +1,73 @@
#ifndef CHATVIEW_H
#define CHATVIEW_H
#include "channel.h"
#include "messages/lazyloadedimage.h"
#include "messages/messageref.h"
#include "messages/word.h"
#include "widgets/accountpopup.h"
#include "widgets/scrollbar.h"
#include <QPaintEvent>
#include <QScroller>
#include <QWheelEvent>
#include <QWidget>
namespace chatterino {
namespace widgets {
class ChatWidget;
class ChatWidgetView : public QWidget
{
public:
explicit ChatWidgetView(ChatWidget *parent);
~ChatWidgetView();
bool layoutMessages();
void updateGifEmotes();
ScrollBar *getScrollbar();
protected:
void resizeEvent(QResizeEvent *) override;
void paintEvent(QPaintEvent *) override;
void wheelEvent(QWheelEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
bool tryGetMessageAt(QPoint p, std::shared_ptr<messages::MessageRef> &message,
QPoint &relativePos);
private:
struct GifEmoteData {
messages::LazyLoadedImage *image;
QRect rect;
};
std::vector<GifEmoteData> _gifEmotes;
ChatWidget *_chatWidget;
ScrollBar _scrollbar;
AccountPopupWidget _userPopupWidget;
bool _onlyUpdateEmotes;
// Mouse event variables
bool _mouseDown;
QPointF _lastPressPosition;
private slots:
void wordTypeMaskChanged()
{
layoutMessages();
update();
}
};
} // namespace widgets
} // namespace chatterino
#endif // CHATVIEW_H
+141
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
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
+154
View File
@@ -0,0 +1,154 @@
#include "widgets/mainwindow.h"
#include "colorscheme.h"
#include "settingsmanager.h"
#include "widgets/chatwidget.h"
#include "widgets/notebook.h"
#include "widgets/settingsdialog.h"
#include <QDebug>
#include <QPalette>
#include <QShortcut>
#include <QVBoxLayout>
#include <boost/foreach.hpp>
#ifdef USEWINSDK
#include "Windows.h"
#endif
namespace chatterino {
namespace widgets {
MainWindow::MainWindow(QWidget *parent)
: QWidget(parent)
, _notebook(this)
, _loaded(false)
, _titleBar()
{
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);
setPalette(palette);
resize(1280, 800);
// Initialize program-wide hotkeys
{
// CTRL+P: Open Settings Dialog
auto shortcut = new QShortcut(QKeySequence("CTRL+P"), this);
connect(shortcut, &QShortcut::activated, []() {
SettingsDialog::showDialog(); //
});
}
}
MainWindow::~MainWindow()
{
}
void MainWindow::layoutVisibleChatWidgets(Channel *channel)
{
auto *page = _notebook.getSelectedPage();
if (page == NULL) {
return;
}
const std::vector<ChatWidget *> &widgets = page->getChatWidgets();
for (auto it = widgets.begin(); it != widgets.end(); ++it) {
ChatWidget *widget = *it;
if (channel == NULL || channel == widget->getChannel().get()) {
widget->layoutMessages();
}
}
}
void MainWindow::repaintVisibleChatWidgets(Channel *channel)
{
auto *page = _notebook.getSelectedPage();
if (page == NULL) {
return;
}
const std::vector<ChatWidget *> &widgets = page->getChatWidgets();
for (auto it = widgets.begin(); it != widgets.end(); ++it) {
ChatWidget *widget = *it;
if (channel == NULL || channel == widget->getChannel().get()) {
widget->layoutMessages();
}
}
}
void MainWindow::repaintGifEmotes()
{
auto *page = _notebook.getSelectedPage();
if (page == NULL) {
return;
}
const std::vector<ChatWidget *> &widgets = page->getChatWidgets();
for (auto it = widgets.begin(); it != widgets.end(); ++it) {
ChatWidget *widget = *it;
widget->updateGifEmotes();
}
}
void MainWindow::load(const boost::property_tree::ptree &tree)
{
this->_notebook.load(tree);
_loaded = true;
}
boost::property_tree::ptree MainWindow::save()
{
boost::property_tree::ptree child;
child.put("type", "main");
_notebook.save(child);
return child;
}
void MainWindow::loadDefaults()
{
_notebook.loadDefaults();
_loaded = true;
}
bool MainWindow::isLoaded() const
{
return _loaded;
}
Notebook &MainWindow::getNotebook()
{
return _notebook;
}
} // namespace widgets
} // namespace chatterino
+46
View File
@@ -0,0 +1,46 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "widgets/notebook.h"
#include "widgets/titlebar.h"
#ifdef USEWINSDK
#include <platform/borderless/qwinwidget.h>
#endif
#include <QMainWindow>
#include <boost/property_tree/ptree.hpp>
namespace chatterino {
namespace widgets {
class MainWindow : public QWidget
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
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;
Notebook &getNotebook();
private:
Notebook _notebook;
bool _loaded;
TitleBar _titleBar;
};
} // namespace widgets
} // namespace chatterino
#endif // MAINWINDOW_H
+259
View File
@@ -0,0 +1,259 @@
#include "widgets/notebook.h"
#include "colorscheme.h"
#include "widgets/notebookbutton.h"
#include "widgets/notebookpage.h"
#include "widgets/notebooktab.h"
#include "widgets/settingsdialog.h"
#include <QDebug>
#include <QFile>
#include <QFormLayout>
#include <QLayout>
#include <QList>
#include <QShortcut>
#include <QStandardPaths>
#include <QWidget>
#include <boost/foreach.hpp>
namespace chatterino {
namespace widgets {
Notebook::Notebook(QWidget *parent)
: QWidget(parent)
, _addButton(this)
, _settingsButton(this)
, _userButton(this)
, _selectedPage(nullptr)
{
connect(&_settingsButton, SIGNAL(clicked()), this, SLOT(settingsButtonClicked()));
connect(&_userButton, SIGNAL(clicked()), this, SLOT(usersButtonClicked()));
connect(&_addButton, SIGNAL(clicked()), this, SLOT(addPageButtonClicked()));
_settingsButton.resize(24, 24);
_settingsButton.icon = NotebookButton::IconSettings;
_userButton.resize(24, 24);
_userButton.move(24, 0);
_userButton.icon = NotebookButton::IconUser;
_addButton.resize(24, 24);
SettingsManager::getInstance().hidePreferencesButton.valueChanged.connect(
[this](const bool &) { performLayout(); });
SettingsManager::getInstance().hideUserButton.valueChanged.connect(
[this](const bool &) { performLayout(); });
// Initialize notebook hotkeys
{
// CTRL+T: Create new split (Add page)
auto shortcut = new QShortcut(QKeySequence("CTRL+T"), this);
connect(shortcut, &QShortcut::activated, [this]() {
printf("ctrL+t pressed\n"); //
if (this->_selectedPage == nullptr) {
return;
}
this->_selectedPage->addChat();
});
}
}
NotebookPage *Notebook::addPage(bool select)
{
auto tab = new NotebookTab(this);
auto page = new NotebookPage(this, tab);
tab->show();
if (select || _pages.count() == 0) {
this->select(page);
}
_pages.append(page);
performLayout();
return page;
}
void Notebook::removePage(NotebookPage *page)
{
int index = _pages.indexOf(page);
if (_pages.size() == 1) {
select(NULL);
} else if (index == _pages.count() - 1) {
select(_pages[index - 1]);
} else {
select(_pages[index + 1]);
}
delete page->getTab();
delete page;
_pages.removeOne(page);
if (_pages.size() == 0) {
addPage();
}
performLayout();
}
void Notebook::select(NotebookPage *page)
{
if (page == _selectedPage)
return;
if (page != nullptr) {
page->setHidden(false);
page->getTab()->setSelected(true);
page->getTab()->raise();
}
if (_selectedPage != nullptr) {
_selectedPage->setHidden(true);
_selectedPage->getTab()->setSelected(false);
}
_selectedPage = page;
performLayout();
}
NotebookPage *Notebook::tabAt(QPoint point, int &index)
{
int i = 0;
for (auto *page : _pages) {
if (page->getTab()->getDesiredRect().contains(point)) {
index = i;
return page;
}
i++;
}
index = -1;
return nullptr;
}
void Notebook::rearrangePage(NotebookPage *page, int index)
{
_pages.move(_pages.indexOf(page), index);
performLayout();
}
void Notebook::performLayout(bool animated)
{
int x = 0, y = 0;
if (SettingsManager::getInstance().hidePreferencesButton.get()) {
_settingsButton.hide();
} else {
_settingsButton.show();
x += 24;
}
if (SettingsManager::getInstance().hideUserButton.get()) {
_userButton.hide();
} else {
_userButton.move(x, 0);
_userButton.show();
x += 24;
}
int tabHeight = 16;
bool first = true;
for (auto &i : _pages) {
tabHeight = i->getTab()->height();
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->getTab()->moveAnimated(QPoint(x, y), animated);
x += i->getTab()->width();
}
first = false;
}
_addButton.move(x, y);
if (_selectedPage != nullptr) {
_selectedPage->move(0, y + tabHeight);
_selectedPage->resize(width(), height() - y - tabHeight);
}
}
void Notebook::resizeEvent(QResizeEvent *)
{
performLayout(false);
}
void Notebook::settingsButtonClicked()
{
SettingsDialog::showDialog();
}
void Notebook::usersButtonClicked()
{
}
void Notebook::addPageButtonClicked()
{
addPage(true);
}
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.")) {
bool select = v.second.get<bool>("selected", false);
auto page = addPage(select);
auto tab = page->getTab();
tab->load(v.second);
page->load(v.second);
}
} catch (boost::property_tree::ptree_error &) {
// can't read tabs
}
if (_pages.size() == 0) {
// No pages saved, show default stuff
loadDefaults();
}
}
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 : _pages) {
boost::property_tree::ptree pTab = page->getTab()->save();
boost::property_tree::ptree pChats = page->save();
if (pChats.size() > 0) {
pTab.add_child("columns", pChats);
}
tabs.push_back(std::make_pair("", pTab));
}
tree.add_child("tabs", tabs);
}
void Notebook::loadDefaults()
{
addPage();
}
} // namespace widgets
} // namespace chatterino
+67
View File
@@ -0,0 +1,67 @@
#ifndef NOTEBOOK_H
#define NOTEBOOK_H
#include "widgets/notebookbutton.h"
#include "widgets/notebookpage.h"
#include "widgets/notebooktab.h"
#include <QList>
#include <QWidget>
#include <boost/property_tree/ptree.hpp>
namespace chatterino {
namespace widgets {
class Notebook : public QWidget
{
Q_OBJECT
public:
enum HighlightType { none, highlighted, newMessage };
Notebook(QWidget *parent);
NotebookPage *addPage(bool select = false);
void removePage(NotebookPage *page);
void select(NotebookPage *page);
NotebookPage *getSelectedPage()
{
return _selectedPage;
}
void performLayout(bool animate = true);
NotebookPage *tabAt(QPoint point, int &index);
void rearrangePage(NotebookPage *page, int index);
protected:
void resizeEvent(QResizeEvent *);
void settingsButtonMouseReleased(QMouseEvent *event);
public slots:
void settingsButtonClicked();
void usersButtonClicked();
void addPageButtonClicked();
private:
QList<NotebookPage *> _pages;
NotebookButton _addButton;
NotebookButton _settingsButton;
NotebookButton _userButton;
NotebookPage *_selectedPage;
public:
void load(const boost::property_tree::ptree &tree);
void save(boost::property_tree::ptree &tree);
void loadDefaults();
};
} // namespace widgets
} // namespace chatterino
#endif // NOTEBOOK_H
+106
View File
@@ -0,0 +1,106 @@
#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)
: FancyButton(parent)
{
setMouseEffectColor(QColor(0, 0, 0));
}
void NotebookButton::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QColor background;
QColor foreground;
auto &colorScheme = ColorScheme::getInstance();
if (_mouseDown) {
background = colorScheme.TabSelectedBackground;
foreground = colorScheme.TabSelectedText;
} else if (_mouseOver) {
background = colorScheme.TabHoverBackground;
foreground = colorScheme.TabSelectedBackground;
} else {
background = colorScheme.TabPanelBackground;
// foreground = colorScheme.TabSelectedBackground;
foreground = QColor(230, 230, 230);
}
painter.setPen(Qt::NoPen);
painter.fillRect(this->rect(), background);
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);
} else if (icon == IconUser) {
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
auto a = w / 8;
QPainterPath path;
path.arcMoveTo(a, 4 * a, 6 * a, 6 * a, 0);
path.arcTo(a, 4 * a, 6 * a, 6 * a, 0, 180);
painter.fillPath(path, foreground);
painter.setBrush(background);
painter.drawEllipse(2 * a, 1 * a, 4 * a, 4 * a);
painter.setBrush(foreground);
painter.drawEllipse(2.5 * a, 1.5 * a, 3 * a + 1, 3 * a);
} else // IconSettings
{
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
auto a = w / 8;
QPainterPath path;
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));
}
painter.fillPath(path, foreground);
painter.setBrush(background);
painter.drawEllipse(3 * a, 3 * a, 2 * a, 2 * a);
}
fancyPaint(painter);
}
void NotebookButton::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
_mouseDown = false;
update();
emit clicked();
}
FancyButton::mouseReleaseEvent(event);
}
}
}
+40
View File
@@ -0,0 +1,40 @@
#ifndef NOTEBOOKBUTTON_H
#define NOTEBOOKBUTTON_H
#include "fancybutton.h"
#include <QWidget>
namespace chatterino {
namespace widgets {
class NotebookButton : public FancyButton
{
Q_OBJECT
public:
static const int IconPlus = 0;
static const int IconUser = 1;
static const int IconSettings = 2;
int icon = 0;
NotebookButton(QWidget *parent);
protected:
void paintEvent(QPaintEvent *) override;
void mouseReleaseEvent(QMouseEvent *event) override;
signals:
void clicked();
private:
bool _mouseOver = false;
bool _mouseDown = false;
QPoint _mousePos;
};
} // namespace widgets
} // namespace chatterino
#endif // NOTEBOOKBUTTON_H
+350
View File
@@ -0,0 +1,350 @@
#include "widgets/notebookpage.h"
#include "colorscheme.h"
#include "widgets/chatwidget.h"
#include "widgets/notebooktab.h"
#include <QDebug>
#include <QHBoxLayout>
#include <QMimeData>
#include <QObject>
#include <QPainter>
#include <QVBoxLayout>
#include <QWidget>
#include <boost/foreach.hpp>
namespace chatterino {
namespace widgets {
bool NotebookPage::isDraggingSplit = false;
ChatWidget *NotebookPage::draggingSplit = NULL;
std::pair<int, int> NotebookPage::dropPosition = std::pair<int, int>(-1, -1);
NotebookPage::NotebookPage(QWidget *parent, NotebookTab *tab)
: QWidget(parent)
, _tab(tab)
, _parentbox(this)
, _chatWidgets()
, _preview(this)
{
tab->page = this;
setHidden(true);
setAcceptDrops(true);
_parentbox.addSpacing(2);
_parentbox.addLayout(&_hbox);
_parentbox.setMargin(0);
_hbox.setSpacing(1);
_hbox.setMargin(0);
}
const std::vector<ChatWidget *> &NotebookPage::getChatWidgets() const
{
return _chatWidgets;
}
NotebookTab *NotebookPage::getTab() const
{
return _tab;
}
void
NotebookPage::addChat(bool openChannelNameDialog)
{
ChatWidget *w = new ChatWidget();
if (openChannelNameDialog) {
w->showChangeChannelPopup();
}
addToLayout(w, std::pair<int, int>(-1, -1));
}
std::pair<int, int> NotebookPage::removeFromLayout(ChatWidget *widget)
{
// remove from chatWidgets vector
for (auto it = _chatWidgets.begin(); it != _chatWidgets.end(); ++it) {
if (*it == widget) {
_chatWidgets.erase(it);
break;
}
}
// 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) {
continue;
}
widget->setParent(NULL);
bool isLastItem = vbox->count() == 0;
if (isLastItem) {
_hbox.removeItem(vbox);
delete vbox;
}
return std::pair<int, int>(i, isLastItem ? -1 : j);
}
}
return std::pair<int, int>(-1, -1);
}
void NotebookPage::addToLayout(ChatWidget *widget,
std::pair<int, int> position = std::pair<int, int>(-1, -1))
{
_chatWidgets.push_back(widget);
// add vbox at the end
if (position.first < 0 || position.first >= _hbox.count()) {
auto vbox = new QVBoxLayout();
vbox->addWidget(widget);
_hbox.addLayout(vbox, 1);
return;
}
// insert vbox
if (position.second == -1) {
auto vbox = new QVBoxLayout();
vbox->addWidget(widget);
_hbox.insertLayout(position.first, vbox, 1);
return;
}
// add to existing vbox
auto vbox = static_cast<QVBoxLayout *>(_hbox.itemAt(position.first));
vbox->insertWidget(std::max(0, std::min(vbox->count(), position.second)), widget);
}
void NotebookPage::enterEvent(QEvent *)
{
if (_hbox.count() == 0) {
setCursor(QCursor(Qt::PointingHandCursor));
} else {
setCursor(QCursor(Qt::ArrowCursor));
}
}
void NotebookPage::leaveEvent(QEvent *)
{
}
void NotebookPage::mouseReleaseEvent(QMouseEvent *event)
{
if (_hbox.count() == 0 && event->button() == Qt::LeftButton) {
// "Add Chat" was clicked
addToLayout(new ChatWidget(), std::pair<int, int>(-1, -1));
setCursor(QCursor(Qt::ArrowCursor));
}
}
void NotebookPage::dragEnterEvent(QDragEnterEvent *event)
{
if (!event->mimeData()->hasFormat("chatterino/split"))
return;
if (isDraggingSplit) {
return;
}
_dropRegions.clear();
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)));
}
for (int i = 0; i < _hbox.count(); ++i) {
auto vbox = static_cast<QVBoxLayout *>(_hbox.itemAt(i));
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)
{
setPreviewRect(event->pos());
}
void NotebookPage::setPreviewRect(QPoint mousePos)
{
for (DropRegion region : _dropRegions) {
if (region.rect.contains(mousePos)) {
_preview.setBounds(region.rect);
if (!_preview.isVisible()) {
_preview.show();
_preview.raise();
}
dropPosition = region.position;
return;
}
}
_preview.hide();
}
void NotebookPage::dragLeaveEvent(QDragLeaveEvent *event)
{
_preview.hide();
}
void NotebookPage::dropEvent(QDropEvent *event)
{
if (isDraggingSplit) {
event->acceptProposedAction();
NotebookPage::draggingSplit->setParent(this);
addToLayout(NotebookPage::draggingSplit, dropPosition);
}
_preview.hide();
}
void NotebookPage::paintEvent(QPaintEvent *)
{
QPainter painter(this);
if (_hbox.count() == 0) {
painter.fillRect(rect(), ColorScheme::getInstance().ChatBackground);
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(0, 0, width(), 2, ColorScheme::getInstance().TabSelectedBackground);
}
}
static std::pair<int, int> getWidgetPositionInLayout(QLayout *layout, const ChatWidget *chatWidget)
{
for (int i = 0; i < layout->count(); ++i) {
printf("xD\n");
}
return std::make_pair(-1, -1);
}
std::pair<int, int> NotebookPage::getChatPosition(const ChatWidget *chatWidget)
{
auto layout = _hbox.layout();
if (layout == nullptr) {
return std::make_pair(-1, -1);
}
return getWidgetPositionInLayout(layout, chatWidget);
}
void NotebookPage::load(const boost::property_tree::ptree &tree)
{
try {
int column = 0;
for (const auto &v : tree.get_child("columns.")) {
int row = 0;
for (const auto &innerV : v.second.get_child("")) {
auto widget = new ChatWidget();
widget->load(innerV.second);
addToLayout(widget, std::pair<int, int>(column, row));
++row;
}
++column;
}
} catch (boost::property_tree::ptree_error &) {
// can't read tabs
}
}
static void saveFromLayout(QLayout *layout, boost::property_tree::ptree &tree)
{
for (int i = 0; i < layout->count(); ++i) {
auto item = layout->itemAt(i);
auto innerLayout = item->layout();
if (innerLayout != nullptr) {
boost::property_tree::ptree innerLayoutTree;
saveFromLayout(innerLayout, innerLayoutTree);
if (innerLayoutTree.size() > 0) {
tree.push_back(std::make_pair("", innerLayoutTree));
}
continue;
}
auto widget = item->widget();
if (widget == nullptr) {
// This layoutitem does not manage a widget for some reason
continue;
}
ChatWidget *chatWidget = qobject_cast<ChatWidget *>(widget);
if (chatWidget != nullptr) {
boost::property_tree::ptree chat = chatWidget->save();
tree.push_back(std::make_pair("", chat));
continue;
}
}
}
boost::property_tree::ptree NotebookPage::save()
{
boost::property_tree::ptree tree;
auto layout = _hbox.layout();
saveFromLayout(layout, tree);
/*
for (const auto &chat : chatWidgets) {
boost::property_tree::ptree child = chat->save();
// Set child position
child.put("position", "5,3");
tree.push_back(std::make_pair("", child));
}
*/
return tree;
}
} // namespace widgets
} // namespace chatterino
+86
View File
@@ -0,0 +1,86 @@
#ifndef NOTEBOOKPAGE_H
#define NOTEBOOKPAGE_H
#include "widgets/chatwidget.h"
#include "widgets/notebookpage.h"
#include "widgets/notebookpagedroppreview.h"
#include "widgets/notebooktab.h"
#include <QDragEnterEvent>
#include <QHBoxLayout>
#include <QRect>
#include <QVBoxLayout>
#include <QVector>
#include <QWidget>
#include <boost/property_tree/ptree.hpp>
#include <boost/signals2.hpp>
namespace chatterino {
namespace widgets {
class NotebookPage : public QWidget
{
Q_OBJECT
public:
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;
NotebookTab *getTab() const;
void addChat(bool openChannelNameDialog = false);
static bool isDraggingSplit;
static ChatWidget *draggingSplit;
static std::pair<int, int> dropPosition;
protected:
void paintEvent(QPaintEvent *) override;
void enterEvent(QEvent *) override;
void leaveEvent(QEvent *) override;
void mouseReleaseEvent(QMouseEvent *event) 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;
DropRegion(QRect rect, std::pair<int, int> position)
{
this->rect = rect;
this->position = position;
}
};
NotebookTab *_tab;
QVBoxLayout _parentbox;
QHBoxLayout _hbox;
std::vector<ChatWidget *> _chatWidgets;
std::vector<DropRegion> _dropRegions;
NotebookPageDropPreview _preview;
void setPreviewRect(QPoint mousePos);
std::pair<int, int> getChatPosition(const ChatWidget *chatWidget);
public:
void load(const boost::property_tree::ptree &tree);
boost::property_tree::ptree save();
};
} // namespace widgets
} // namespace chatterino
#endif // NOTEBOOKPAGE_H
+54
View File
@@ -0,0 +1,54 @@
#include "widgets/notebookpagedroppreview.h"
#include "colorscheme.h"
#include <QDebug>
#include <QPainter>
namespace chatterino {
namespace widgets {
NotebookPageDropPreview::NotebookPageDropPreview(QWidget *parent)
: QWidget(parent)
, positionAnimation(this, "geometry")
, desiredGeometry()
, animate(false)
{
this->positionAnimation.setEasingCurve(QEasingCurve(QEasingCurve::InCubic));
this->setHidden(true);
}
void NotebookPageDropPreview::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.fillRect(8, 8, width() - 17, height() - 17,
ColorScheme::getInstance().DropPreviewBackground);
}
void NotebookPageDropPreview::hideEvent(QHideEvent *)
{
animate = false;
}
void NotebookPageDropPreview::setBounds(const QRect &rect)
{
if (rect == this->desiredGeometry) {
return;
}
if (animate) {
this->positionAnimation.stop();
this->positionAnimation.setDuration(50);
this->positionAnimation.setStartValue(this->geometry());
this->positionAnimation.setEndValue(rect);
this->positionAnimation.start();
} else {
this->setGeometry(rect);
}
this->desiredGeometry = rect;
animate = true;
}
}
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef NOTEBOOKPAGEDROPPREVIEW_H
#define NOTEBOOKPAGEDROPPREVIEW_H
#include <QPropertyAnimation>
#include <QWidget>
namespace chatterino {
namespace widgets {
class NotebookPageDropPreview : public QWidget
{
public:
NotebookPageDropPreview(QWidget *parent);
void setBounds(const QRect &rect);
protected:
void paintEvent(QPaintEvent *);
void hideEvent(QHideEvent *);
QPropertyAnimation positionAnimation;
QRect desiredGeometry;
bool animate;
};
} // namespace widgets
} // namespace chatterino
#endif // NOTEBOOKPAGEDROPPREVIEW_H
+250
View File
@@ -0,0 +1,250 @@
#include "widgets/notebooktab.h"
#include "colorscheme.h"
#include "settingsmanager.h"
#include "widgets/notebook.h"
#include <QPainter>
namespace chatterino {
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)
{
this->calcSize();
this->setAcceptDrops(true);
_posAnimation.setEasingCurve(QEasingCurve(QEasingCurve::InCubic));
this->_hideXConnection = SettingsManager::getInstance().hideTabX.valueChanged.connect(
boost::bind(&NotebookTab::hideTabXChanged, this, _1));
this->setMouseTracking(true);
}
NotebookTab::~NotebookTab()
{
this->_hideXConnection.disconnect();
}
void NotebookTab::calcSize()
{
if (SettingsManager::getInstance().hideTabX.get()) {
resize(fontMetrics().width(_title) + 8, 24);
} else {
resize(fontMetrics().width(_title) + 8 + 24, 24);
}
if (parent() != nullptr) {
((Notebook *)parent())->performLayout(true);
}
}
const QString &NotebookTab::getTitle() const
{
return _title;
}
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;
return;
}
if (_posAnimation.endValue() == pos) {
return;
}
_posAnimation.stop();
_posAnimation.setDuration(75);
_posAnimation.setStartValue(this->pos());
_posAnimation.setEndValue(pos);
_posAnimation.start();
}
void NotebookTab::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QColor fg = QColor(0, 0, 0);
auto &colorScheme = ColorScheme::getInstance();
if (_selected) {
painter.fillRect(rect(), colorScheme.TabSelectedBackground);
fg = colorScheme.TabSelectedText;
} else if (_mouseOver) {
painter.fillRect(rect(), colorScheme.TabHoverBackground);
fg = colorScheme.TabHoverText;
} else if (_highlightStyle == HighlightHighlighted) {
painter.fillRect(rect(), colorScheme.TabHighlightedBackground);
fg = colorScheme.TabHighlightedText;
} else if (_highlightStyle == HighlightNewMessage) {
painter.fillRect(rect(), colorScheme.TabNewMessageBackground);
fg = colorScheme.TabHighlightedText;
} else {
painter.fillRect(rect(), colorScheme.TabBackground);
fg = colorScheme.TabText;
}
painter.setPen(fg);
QRect rect(0, 0, width() - (SettingsManager::getInstance().hideTabX.get() ? 0 : 16), height());
painter.drawText(rect, _title, QTextOption(Qt::AlignCenter));
if (!SettingsManager::getInstance().hideTabX.get() && (_mouseOver || _selected)) {
if (_mouseOverX) {
painter.fillRect(getXRect(), QColor(0, 0, 0, 64));
if (_mouseDownX) {
painter.fillRect(getXRect(), QColor(0, 0, 0, 64));
}
}
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)
{
_mouseDown = true;
_mouseDownX = getXRect().contains(event->pos());
update();
_notebook->select(page);
}
void NotebookTab::mouseReleaseEvent(QMouseEvent *event)
{
_mouseDown = false;
if (!SettingsManager::getInstance().hideTabX.get() && _mouseDownX &&
getXRect().contains(event->pos())) {
_mouseDownX = false;
_notebook->removePage(page);
} else {
update();
}
}
void NotebookTab::enterEvent(QEvent *)
{
_mouseOver = true;
update();
}
void NotebookTab::leaveEvent(QEvent *)
{
_mouseOverX = _mouseOver = false;
update();
}
void NotebookTab::dragEnterEvent(QDragEnterEvent *)
{
_notebook->select(page);
}
void NotebookTab::mouseMoveEvent(QMouseEvent *event)
{
bool overX = getXRect().contains(event->pos());
if (overX != _mouseOverX) {
_mouseOverX = overX && !SettingsManager::getInstance().hideTabX.get();
update();
}
if (_mouseDown && !getDesiredRect().contains(event->pos())) {
QPoint relPoint = mapToParent(event->pos());
int index;
NotebookPage *clickedPage = _notebook->tabAt(relPoint, index);
if (clickedPage != nullptr && clickedPage != page) {
_notebook->rearrangePage(clickedPage, index);
}
}
}
void NotebookTab::load(const boost::property_tree::ptree &tree)
{
// Load tab title
try {
setTitle(QString::fromStdString(tree.get<std::string>("title")));
} catch (boost::property_tree::ptree_error) {
}
}
boost::property_tree::ptree NotebookTab::save()
{
boost::property_tree::ptree tree;
tree.put("title", getTitle().toStdString());
return tree;
}
} // namespace widgets
} // namespace chatterino
+87
View File
@@ -0,0 +1,87 @@
#ifndef NOTEBOOKTAB_H
#define NOTEBOOKTAB_H
#include <QPropertyAnimation>
#include <QWidget>
#include <boost/property_tree/ptree.hpp>
#include <boost/signals2.hpp>
#include <boost/signals2/connection.hpp>
namespace chatterino {
namespace widgets {
class Notebook;
class NotebookPage;
class NotebookTab : public QWidget
{
Q_OBJECT
public:
enum HighlightStyle { HighlightNone, HighlightHighlighted, HighlightNewMessage };
explicit NotebookTab(Notebook *_notebook);
~NotebookTab();
void calcSize();
NotebookPage *page;
const QString &getTitle() const;
void setTitle(const QString &title);
bool getSelected();
void setSelected(bool value);
HighlightStyle getHighlightStyle() const;
void setHighlightStyle(HighlightStyle style);
void moveAnimated(QPoint pos, bool animated = true);
QRect getDesiredRect() const;
void hideTabXChanged(bool);
protected:
void paintEvent(QPaintEvent *) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void enterEvent(QEvent *) override;
void leaveEvent(QEvent *) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
private:
boost::signals2::connection _hideXConnection;
QPropertyAnimation _posAnimation;
bool _posAnimated;
QPoint _posAnimationDesired;
Notebook *_notebook;
QString _title;
bool _selected;
bool _mouseOver;
bool _mouseDown;
bool _mouseOverX;
bool _mouseDownX;
HighlightStyle _highlightStyle;
QRect getXRect()
{
return QRect(this->width() - 20, 4, 16, 16);
}
public:
void load(const boost::property_tree::ptree &tree);
boost::property_tree::ptree save();
};
} // namespace widgets
} // namespace chatterino
#endif // NOTEBOOKTAB_H
+54
View File
@@ -0,0 +1,54 @@
#ifndef RESIZINGTEXTEDIT_H
#define RESIZINGTEXTEDIT_H
#include <QKeyEvent>
#include <QTextEdit>
#include <boost/signals2.hpp>
class ResizingTextEdit : public QTextEdit
{
public:
ResizingTextEdit()
: keyPressed()
{
auto sizePolicy = this->sizePolicy();
sizePolicy.setHeightForWidth(true);
sizePolicy.setVerticalPolicy(QSizePolicy::Preferred);
this->setSizePolicy(sizePolicy);
QObject::connect(this, &QTextEdit::textChanged, this, &QWidget::updateGeometry);
}
QSize sizeHint() const override
{
return QSize(this->width(), this->heightForWidth(this->width()));
}
bool hasHeightForWidth() const override
{
return true;
}
boost::signals2::signal<void(QKeyEvent *)> keyPressed;
protected:
int heightForWidth(int) const override
{
auto margins = this->contentsMargins();
return margins.top() + document()->size().height() + margins.bottom() + 5;
}
void keyPressEvent(QKeyEvent *event) override
{
event->ignore();
keyPressed(event);
if (!event->isAccepted()) {
QTextEdit::keyPressEvent(event);
}
}
};
#endif // RESIZINGTEXTEDIT_H

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