move around files
This commit is contained in:
Symlink
+1
@@ -0,0 +1 @@
|
||||
../.clang-format
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
@@ -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}});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../.clang-format
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,134 @@
|
||||
#include "messages/word.h"
|
||||
|
||||
namespace chatterino {
|
||||
namespace messages {
|
||||
|
||||
// Image word
|
||||
Word::Word(LazyLoadedImage *image, Type type, const QString ©text, 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 ©text,
|
||||
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
|
||||
@@ -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 ©text,
|
||||
const QString &getTooltip, const Link &getLink = Link());
|
||||
explicit Word(const QString &_text, Type getType, const QColor &getColor,
|
||||
const QString ©text, 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
|
||||
@@ -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 ©Text,
|
||||
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 ©Text, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../.clang-format
|
||||
@@ -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
|
||||
@@ -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;
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
//
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../.clang-format
|
||||
@@ -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
|
||||
@@ -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
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../.clang-format
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user