This commit is contained in:
fourtf 2018-01-28 03:39:47 +01:00
commit 431b9a8c1f
76 changed files with 1645 additions and 561 deletions

View file

@ -10,7 +10,7 @@ The code is normally formated using clang format in Qt Creator. [.clang-format](
To setup automatic code formating with QT Creator, see [this guide](https://gist.github.com/pajlada/0296454198eb8f8789fd6fe7ea660c5b). To setup automatic code formating with QT Creator, see [this guide](https://gist.github.com/pajlada/0296454198eb8f8789fd6fe7ea660c5b).
## Building ## Building
Before building run `git submodule update --init --recursive` to get required submodules. In case you are new to using qt creator or c++ be sure to add -j to your make arguments as shown here [image](https://i.fourtf.com/GreenSweetImage.png) so it uses all your cpu cores to build. Before building run `git submodule update --init --recursive` to get required submodules.
### Windows ### Windows
#### Using Qt Creator #### Using Qt Creator
@ -23,6 +23,7 @@ download the [boost library](https://sourceforge.net/projects/boost/files/boost/
#### Using MSYS2 #### Using MSYS2
Building using MSYS2 can be quite easier process. Check out MSYS2 at [msys2.org](http://www.msys2.org/). Building using MSYS2 can be quite easier process. Check out MSYS2 at [msys2.org](http://www.msys2.org/).
Be sure to add "-j <number of cores\*2>" as a make argument so it will use all your cpu cores to build. [example setup](https://i.imgur.com/qlESlS1.png)
1. open appropriate MSYS2 terminal and do `pacman -S mingw-w64-<arch>-boost mingw-w64-<arch>-qt5 mingw-w64-<arch>-rapidjson` where `<arch>` is x86_64 or i686 1. open appropriate MSYS2 terminal and do `pacman -S mingw-w64-<arch>-boost mingw-w64-<arch>-qt5 mingw-w64-<arch>-rapidjson` where `<arch>` is x86_64 or i686
2. go into the project directory 2. go into the project directory
3. create build folder `mkdir build && cd build` 3. create build folder `mkdir build && cd build`

View file

@ -163,7 +163,13 @@ SOURCES += \
src/widgets/basewindow.cpp \ src/widgets/basewindow.cpp \
src/singletons/helper/moderationaction.cpp \ src/singletons/helper/moderationaction.cpp \
src/widgets/streamview.cpp \ src/widgets/streamview.cpp \
src/util/networkrequest.cpp src/util/networkrequest.cpp \
src/widgets/settingspages/ignoreuserspage.cpp \
src/widgets/settingspages/ignoremessagespage.cpp \
src/widgets/settingspages/specialchannelspage.cpp \
src/widgets/settingspages/keyboardsettingspage.cpp \
src/widgets/helper/titlebarbutton.cpp \
src/widgets/helper/label.cpp
HEADERS += \ HEADERS += \
src/precompiled_header.hpp \ src/precompiled_header.hpp \
@ -267,7 +273,14 @@ HEADERS += \
src/util/networkrequest.hpp \ src/util/networkrequest.hpp \
src/util/networkworker.hpp \ src/util/networkworker.hpp \
src/util/networkrequester.hpp \ src/util/networkrequester.hpp \
src/util/flagsenum.h src/util/flagsenum.h \
src/widgets/settingspages/ignoreuserspage.hpp \
src/widgets/settingspages/ignoremessagespage.hpp \
src/widgets/settingspages/specialchannelspage.hpp \
src/widgets/settingspages/keyboardsettings.hpp \
src/widgets/settingspages/keyboardsettingspage.hpp \
src/widgets/helper/titlebarbutton.hpp \
src/widgets/helper/label.hpp
RESOURCES += \ RESOURCES += \
resources/resources.qrc resources/resources.qrc

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View file

@ -64,6 +64,6 @@ private:
// std::shared_ptr<logging::Channel> loggingChannel; // std::shared_ptr<logging::Channel> loggingChannel;
}; };
typedef std::shared_ptr<Channel> SharedChannel; typedef std::shared_ptr<Channel> ChannelPtr;
} // namespace chatterino } // namespace chatterino

View file

@ -89,6 +89,12 @@ bool MessageLayout::layout(int width, float scale, MessageElement::Flags flags)
layoutRequired |= wordMaskChanged; layoutRequired |= wordMaskChanged;
this->currentWordFlags = flags; // singletons::SettingManager::getInstance().getWordTypeMask(); this->currentWordFlags = flags; // singletons::SettingManager::getInstance().getWordTypeMask();
// check if timestamp format changed
bool timestampFormatChanged =
this->timestampFormat != singletons::SettingManager::getInstance().timestampFormat;
layoutRequired |= timestampFormatChanged;
// check if dpi changed // check if dpi changed
bool scaleChanged = this->scale != scale; bool scaleChanged = this->scale != scale;
layoutRequired |= scaleChanged; layoutRequired |= scaleChanged;
@ -98,11 +104,11 @@ bool MessageLayout::layout(int width, float scale, MessageElement::Flags flags)
// update word sizes if needed // update word sizes if needed
if (imagesChanged) { if (imagesChanged) {
// fourtf: update images // this->container.updateImages();
this->addFlags(MessageLayout::RequiresBufferUpdate); this->addFlags(MessageLayout::RequiresBufferUpdate);
} }
if (textChanged) { if (textChanged) {
// fourtf: update text // this->container.updateText();
this->addFlags(MessageLayout::RequiresBufferUpdate); this->addFlags(MessageLayout::RequiresBufferUpdate);
} }
if (widthChanged || wordMaskChanged) { if (widthChanged || wordMaskChanged) {
@ -139,7 +145,8 @@ void MessageLayout::actuallyLayout(int width, MessageElement::Flags flags)
} }
// Painting // Painting
void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection &selection) void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection &selection,
bool isLastReadMessage, bool isWindowFocused)
{ {
QPixmap *pixmap = this->buffer.get(); QPixmap *pixmap = this->buffer.get();
singletons::ThemeManager &themeManager = singletons::ThemeManager::getInstance(); singletons::ThemeManager &themeManager = singletons::ThemeManager::getInstance();
@ -164,7 +171,8 @@ void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection
} }
// draw on buffer // draw on buffer
painter.drawPixmap(0, y, this->container.width, this->container.getHeight(), *pixmap); painter.drawPixmap(0, y, *pixmap);
// painter.drawPixmap(0, y, this->container.width, this->container.getHeight(), *pixmap);
// draw disabled // draw disabled
if (this->message->flags & Message::Disabled) { if (this->message->flags & Message::Disabled) {
@ -174,6 +182,16 @@ void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection
// draw gif emotes // draw gif emotes
this->container.paintAnimatedElements(painter, y); this->container.paintAnimatedElements(painter, y);
// draw last read message line
if (isLastReadMessage) {
QColor color = isWindowFocused ? themeManager.tabs.selected.backgrounds.regular.color()
: themeManager.tabs.selected.backgrounds.unfocused.color();
QBrush brush = QBrush(color, Qt::VerPattern);
painter.fillRect(0, y + this->container.getHeight() - 1, this->container.width, 1, brush);
}
this->bufferValid = true; this->bufferValid = true;
} }

View file

@ -41,7 +41,8 @@ public:
bool layout(int width, float scale, MessageElement::Flags flags); bool layout(int width, float scale, MessageElement::Flags flags);
// Painting // Painting
void paint(QPainter &painter, int y, int messageIndex, Selection &selection); void paint(QPainter &painter, int y, int messageIndex, Selection &selection,
bool isLastReadMessage, bool isWindowFocused);
void invalidateBuffer(); void invalidateBuffer();
void deleteBuffer(); void deleteBuffer();
@ -67,6 +68,7 @@ private:
int currentLayoutWidth = -1; int currentLayoutWidth = -1;
int fontGeneration = -1; int fontGeneration = -1;
int emoteGeneration = -1; int emoteGeneration = -1;
QString timestampFormat;
float scale = -1; float scale = -1;
unsigned int bufferUpdatedCount = 0; unsigned int bufferUpdatedCount = 0;

View file

@ -61,27 +61,39 @@ ImageElement::ImageElement(Image *_image, MessageElement::Flags flags)
void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags) void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
{ {
if (_flags & this->getFlags()) {
QSize size(this->image->getWidth() * this->image->getScale() * container.scale, QSize size(this->image->getWidth() * this->image->getScale() * container.scale,
this->image->getHeight() * this->image->getScale() * container.scale); this->image->getHeight() * this->image->getScale() * container.scale);
container.addElement( container.addElement(
(new ImageLayoutElement(*this, this->image, size))->setLink(this->getLink())); (new ImageLayoutElement(*this, this->image, size))->setLink(this->getLink()));
} }
}
// EMOTE // EMOTE
EmoteElement::EmoteElement(const util::EmoteData &_data, MessageElement::Flags flags) EmoteElement::EmoteElement(const util::EmoteData &_data, MessageElement::Flags flags)
: MessageElement(flags) : MessageElement(flags)
, data(_data) , data(_data)
, textElement(nullptr)
{ {
if (_data.isValid()) { if (_data.isValid()) {
this->setTooltip(data.image1x->getTooltip()); this->setTooltip(data.image1x->getTooltip());
this->textElement = new TextElement(_data.image1x->getName(), MessageElement::Misc);
}
}
EmoteElement::~EmoteElement()
{
if (this->textElement != nullptr) {
delete this->textElement;
} }
} }
void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags) void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
{ {
if (_flags & this->getFlags()) {
if (_flags & MessageElement::EmoteImages) {
if (!this->data.isValid()) { if (!this->data.isValid()) {
qDebug() << "EmoteElement::data is invalid xD";
return; return;
} }
@ -99,7 +111,14 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElem
QSize size((int)(container.scale * _image->getScaledWidth()), QSize size((int)(container.scale * _image->getScaledWidth()),
(int)(container.scale * _image->getScaledHeight())); (int)(container.scale * _image->getScaledHeight()));
container.addElement((new ImageLayoutElement(*this, _image, size))->setLink(this->getLink())); container.addElement(
(new ImageLayoutElement(*this, _image, size))->setLink(this->getLink()));
} else {
if (this->textElement != nullptr) {
this->textElement->addToContainer(container, MessageElement::Misc);
}
}
}
} }
// TEXT // TEXT
@ -117,9 +136,11 @@ TextElement::TextElement(const QString &text, MessageElement::Flags flags,
void TextElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags) void TextElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
{ {
if (_flags & this->getFlags()) {
QFontMetrics &metrics = QFontMetrics &metrics =
singletons::FontManager::getInstance().getFontMetrics(this->style, container.scale); singletons::FontManager::getInstance().getFontMetrics(this->style, container.scale);
singletons::ThemeManager &themeManager = singletons::ThemeManager::ThemeManager::getInstance(); singletons::ThemeManager &themeManager =
singletons::ThemeManager::ThemeManager::getInstance();
for (Word &word : this->words) { for (Word &word : this->words) {
auto getTextLayoutElement = [&](QString text, int width, bool trailingSpace) { auto getTextLayoutElement = [&](QString text, int width, bool trailingSpace) {
@ -133,9 +154,10 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
return e; return e;
}; };
if (word.width == -1) { // fourtf: add again
// if (word.width == -1) {
word.width = metrics.width(word.text); word.width = metrics.width(word.text);
} // }
// see if the text fits in the current line // see if the text fits in the current line
if (container.fitsInLine(word.width)) { if (container.fitsInLine(word.width)) {
@ -188,6 +210,7 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
container.breakLine(); container.breakLine();
} }
} }
}
// TIMESTAMP // TIMESTAMP
TimestampElement::TimestampElement() TimestampElement::TimestampElement()
@ -211,6 +234,7 @@ TimestampElement::~TimestampElement()
void TimestampElement::addToContainer(MessageLayoutContainer &container, void TimestampElement::addToContainer(MessageLayoutContainer &container,
MessageElement::Flags _flags) MessageElement::Flags _flags)
{ {
if (_flags & this->getFlags()) {
if (singletons::SettingManager::getInstance().timestampFormat != this->format) { if (singletons::SettingManager::getInstance().timestampFormat != this->format) {
this->format = singletons::SettingManager::getInstance().timestampFormat.getValue(); this->format = singletons::SettingManager::getInstance().timestampFormat.getValue();
delete this->element; delete this->element;
@ -219,6 +243,7 @@ void TimestampElement::addToContainer(MessageLayoutContainer &container,
this->element->addToContainer(container, _flags); this->element->addToContainer(container, _flags);
} }
}
TextElement *TimestampElement::formatTime(const QTime &time) TextElement *TimestampElement::formatTime(const QTime &time)
{ {
@ -239,8 +264,6 @@ TwitchModerationElement::TwitchModerationElement()
void TwitchModerationElement::addToContainer(MessageLayoutContainer &container, void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
MessageElement::Flags _flags) MessageElement::Flags _flags)
{ {
// qDebug() << _flags;
if (_flags & MessageElement::ModeratorTools) { if (_flags & MessageElement::ModeratorTools) {
QSize size((int)(container.scale * 16), (int)(container.scale * 16)); QSize size((int)(container.scale * 16), (int)(container.scale * 16));

View file

@ -145,20 +145,6 @@ public:
MessageElement::Flags flags) override; MessageElement::Flags flags) override;
}; };
// contains emote data and will pick the emote based on :
// a) are images for the emote type enabled
// b) which size it wants
class EmoteElement : public MessageElement
{
const util::EmoteData data;
public:
EmoteElement(const util::EmoteData &data, MessageElement::Flags flags);
virtual void addToContainer(MessageLayoutContainer &container,
MessageElement::Flags flags) override;
};
// contains a text, it will split it into words // contains a text, it will split it into words
class TextElement : public MessageElement class TextElement : public MessageElement
{ {
@ -180,6 +166,22 @@ public:
MessageElement::Flags flags) override; MessageElement::Flags flags) override;
}; };
// contains emote data and will pick the emote based on :
// a) are images for the emote type enabled
// b) which size it wants
class EmoteElement : public MessageElement
{
const util::EmoteData data;
TextElement *textElement;
public:
EmoteElement(const util::EmoteData &data, MessageElement::Flags flags);
~EmoteElement();
virtual void addToContainer(MessageLayoutContainer &container,
MessageElement::Flags flags) override;
};
// contains a text, formated depending on the preferences // contains a text, formated depending on the preferences
class TimestampElement : public MessageElement class TimestampElement : public MessageElement
{ {
@ -208,12 +210,5 @@ public:
virtual void addToContainer(MessageLayoutContainer &container, virtual void addToContainer(MessageLayoutContainer &container,
MessageElement::Flags flags) override; MessageElement::Flags flags) override;
}; };
// adds bits as text, static image or animated image
// class BitsElement : public MessageElement
//{
// public:
// virtual void addToContainer(LayoutContainer &container) override;
//};
} // namespace messages } // namespace messages
} // namespace chatterino } // namespace chatterino

View file

@ -8,7 +8,6 @@ public:
bool disablePingSoungs = false; bool disablePingSoungs = false;
bool isReceivedWhisper = false; bool isReceivedWhisper = false;
bool isSentWhisper = false; bool isSentWhisper = false;
bool includeChannelName = false;
}; };
} // namespace messages } // namespace messages

View file

@ -19,11 +19,11 @@ ChannelManager::ChannelManager()
{ {
} }
const std::vector<SharedChannel> ChannelManager::getItems() const std::vector<ChannelPtr> ChannelManager::getItems()
{ {
QMutexLocker locker(&this->channelsMutex); QMutexLocker locker(&this->channelsMutex);
std::vector<SharedChannel> items; std::vector<ChannelPtr> items;
for (auto &item : this->twitchChannels.values()) { for (auto &item : this->twitchChannels.values()) {
items.push_back(std::get<0>(item)); items.push_back(std::get<0>(item));
@ -32,7 +32,7 @@ const std::vector<SharedChannel> ChannelManager::getItems()
return items; return items;
} }
SharedChannel ChannelManager::addTwitchChannel(const QString &rawChannelName) ChannelPtr ChannelManager::addTwitchChannel(const QString &rawChannelName)
{ {
QString channelName = rawChannelName.toLower(); QString channelName = rawChannelName.toLower();
@ -63,7 +63,7 @@ SharedChannel ChannelManager::addTwitchChannel(const QString &rawChannelName)
return std::get<0>(it.value()); return std::get<0>(it.value());
} }
SharedChannel ChannelManager::getTwitchChannel(const QString &channel) ChannelPtr ChannelManager::getTwitchChannel(const QString &channel)
{ {
QMutexLocker locker(&this->channelsMutex); QMutexLocker locker(&this->channelsMutex);
@ -128,7 +128,7 @@ const std::string &ChannelManager::getUserID(const std::string &username)
return temporary; return temporary;
} }
void ChannelManager::doOnAll(std::function<void(SharedChannel)> func) void ChannelManager::doOnAll(std::function<void(ChannelPtr)> func)
{ {
for (const auto &channel : this->twitchChannels) { for (const auto &channel : this->twitchChannels) {
func(std::get<0>(channel)); func(std::get<0>(channel));

View file

@ -17,20 +17,20 @@ class ChannelManager
public: public:
static ChannelManager &getInstance(); static ChannelManager &getInstance();
const std::vector<SharedChannel> getItems(); const std::vector<ChannelPtr> getItems();
SharedChannel addTwitchChannel(const QString &channel); ChannelPtr addTwitchChannel(const QString &channel);
SharedChannel getTwitchChannel(const QString &channel); ChannelPtr getTwitchChannel(const QString &channel);
void removeTwitchChannel(const QString &channel); void removeTwitchChannel(const QString &channel);
const std::string &getUserID(const std::string &username); const std::string &getUserID(const std::string &username);
void doOnAll(std::function<void(SharedChannel)> func); void doOnAll(std::function<void(ChannelPtr)> func);
// Special channels // Special channels
const SharedChannel whispersChannel; const ChannelPtr whispersChannel;
const SharedChannel mentionsChannel; const ChannelPtr mentionsChannel;
const SharedChannel emptyChannel; const ChannelPtr emptyChannel;
private: private:
std::map<std::string, std::string> usernameToID; std::map<std::string, std::string> usernameToID;

View file

@ -88,7 +88,7 @@ QStringList CommandManager::getCommands()
return this->commandsStringList; return this->commandsStringList;
} }
QString CommandManager::execCommand(const QString &text, SharedChannel channel, QString CommandManager::execCommand(const QString &text, ChannelPtr channel,
bool dryRun) bool dryRun)
{ {
QStringList words = text.split(' ', QString::SkipEmptyParts); QStringList words = text.split(' ', QString::SkipEmptyParts);

View file

@ -246,16 +246,23 @@ void IrcManager::privateMessageReceived(Communi::IrcPrivateMessage *message)
return; return;
} }
auto xd = message->content(); // auto xd = message->content();
auto xd2 = message->toData(); // auto xd2 = message->toData();
debug::Log("HEHE: {}", xd2.toStdString()); // debug::Log("HEHE: {}", xd2.toStdString());
messages::MessageParseArgs args; messages::MessageParseArgs args;
twitch::TwitchMessageBuilder builder(c.get(), message, args); twitch::TwitchMessageBuilder builder(c.get(), message, args);
c->addMessage(builder.parse()); if (!builder.isIgnored()) {
messages::MessagePtr _message = builder.build();
if (_message->flags & messages::Message::Highlighted) {
singletons::ChannelManager::getInstance().mentionsChannel->addMessage(_message);
}
c->addMessage(_message);
}
} }
void IrcManager::messageReceived(Communi::IrcMessage *message) void IrcManager::messageReceived(Communi::IrcMessage *message)
@ -384,7 +391,7 @@ void IrcManager::onConnected()
MessagePtr connMsg = Message::createSystemMessage("connected to chat"); MessagePtr connMsg = Message::createSystemMessage("connected to chat");
MessagePtr reconnMsg = Message::createSystemMessage("reconnected to chat"); MessagePtr reconnMsg = Message::createSystemMessage("reconnected to chat");
this->channelManager.doOnAll([connMsg, reconnMsg](SharedChannel channel) { this->channelManager.doOnAll([connMsg, reconnMsg](ChannelPtr channel) {
assert(channel); assert(channel);
LimitedQueueSnapshot<MessagePtr> snapshot = channel->getMessageSnapshot(); LimitedQueueSnapshot<MessagePtr> snapshot = channel->getMessageSnapshot();
@ -407,7 +414,7 @@ void IrcManager::onDisconnected()
MessagePtr msg = Message::createSystemMessage("disconnected from chat"); MessagePtr msg = Message::createSystemMessage("disconnected from chat");
msg->flags &= Message::DisconnectedMessage; msg->flags &= Message::DisconnectedMessage;
this->channelManager.doOnAll([msg](SharedChannel channel) { this->channelManager.doOnAll([msg](ChannelPtr channel) {
assert(channel); assert(channel);
channel->addMessage(msg); channel->addMessage(msg);
}); });

View file

@ -345,7 +345,7 @@ void ResourceManager::loadChannelData(const QString &roomID, bool bypassCache)
QString cheermoteURL = "https://api.twitch.tv/kraken/bits/actions?channel_id=" + roomID; QString cheermoteURL = "https://api.twitch.tv/kraken/bits/actions?channel_id=" + roomID;
util::twitch::get2( util::twitch::get2(
cheermoteURL, QThread::currentThread(), [this, roomID](const rapidjson::Document &d) { cheermoteURL, QThread::currentThread(), true, [this, roomID](const rapidjson::Document &d) {
ResourceManager::Channel &ch = this->channels[roomID]; ResourceManager::Channel &ch = this->channels[roomID];
ParseCheermoteSets(ch.jsonCheermoteSets, d); ParseCheermoteSets(ch.jsonCheermoteSets, d);

View file

@ -2,6 +2,7 @@
#include "debug/log.hpp" #include "debug/log.hpp"
#include "singletons/pathmanager.hpp" #include "singletons/pathmanager.hpp"
#include "singletons/resourcemanager.hpp" #include "singletons/resourcemanager.hpp"
#include "singletons/windowmanager.hpp"
using namespace chatterino::messages; using namespace chatterino::messages;
@ -17,6 +18,7 @@ void _registerSetting(std::weak_ptr<pajlada::Settings::ISettingData> setting)
SettingManager::SettingManager() SettingManager::SettingManager()
: snapshot(nullptr) : snapshot(nullptr)
, _ignoredKeywords(new std::vector<QString>)
{ {
this->wordFlagsListener.addSetting(this->showTimestamps); this->wordFlagsListener.addSetting(this->showTimestamps);
this->wordFlagsListener.addSetting(this->showBadges); this->wordFlagsListener.addSetting(this->showBadges);
@ -29,6 +31,10 @@ SettingManager::SettingManager()
}; };
this->moderationActions.connect([this](auto, auto) { this->updateModerationActions(); }); this->moderationActions.connect([this](auto, auto) { this->updateModerationActions(); });
this->ignoredKeywords.connect([this](auto, auto) { this->updateIgnoredKeywords(); });
this->timestampFormat.connect(
[](auto, auto) { singletons::WindowManager::getInstance().layoutVisibleChatWidgets(); });
} }
MessageElement::Flags SettingManager::getWordFlags() MessageElement::Flags SettingManager::getWordFlags()
@ -135,6 +141,11 @@ std::vector<ModerationAction> SettingManager::getModerationActions() const
return this->_moderationActions; return this->_moderationActions;
} }
const std::shared_ptr<std::vector<QString>> SettingManager::getIgnoredKeywords() const
{
return this->_ignoredKeywords;
}
void SettingManager::updateModerationActions() void SettingManager::updateModerationActions()
{ {
auto &resources = singletons::ResourceManager::getInstance(); auto &resources = singletons::ResourceManager::getInstance();
@ -202,5 +213,22 @@ void SettingManager::updateModerationActions()
} }
} }
} }
void SettingManager::updateIgnoredKeywords()
{
static QRegularExpression newLineRegex("(\r\n?|\n)+");
auto items = new std::vector<QString>();
for (const QString &line : this->ignoredKeywords.getValue().split(newLineRegex)) {
QString line2 = line.trimmed();
if (!line2.isEmpty()) {
items->push_back(line2);
}
}
this->_ignoredKeywords = std::shared_ptr<std::vector<QString>>(items);
}
} // namespace singletons } // namespace singletons
} // namespace chatterino } // namespace chatterino

View file

@ -75,6 +75,10 @@ public:
/// Links /// Links
BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false}; BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false};
/// Ingored Users
BoolSetting enableTwitchIgnoredUsers = {"/ignore/enableTwitchIgnoredUsers", true};
QStringSetting ignoredKeywords = {"/ignore/ignoredKeywords", ""};
/// Moderation /// Moderation
QStringSetting moderationActions = {"/moderation/actions", "/ban {user}\n/timeout {user} 300"}; QStringSetting moderationActions = {"/moderation/actions", "/ban {user}\n/timeout {user} 300"};
@ -107,6 +111,7 @@ public:
void recallSnapshot(); void recallSnapshot();
std::vector<ModerationAction> getModerationActions() const; std::vector<ModerationAction> getModerationActions() const;
const std::shared_ptr<std::vector<QString>> getIgnoredKeywords() const;
signals: signals:
void wordFlagsChanged(); void wordFlagsChanged();
@ -114,10 +119,12 @@ signals:
private: private:
std::vector<ModerationAction> _moderationActions; std::vector<ModerationAction> _moderationActions;
std::unique_ptr<rapidjson::Document> snapshot; std::unique_ptr<rapidjson::Document> snapshot;
std::shared_ptr<std::vector<QString>> _ignoredKeywords;
SettingManager(); SettingManager();
void updateModerationActions(); void updateModerationActions();
void updateIgnoredKeywords();
messages::MessageElement::Flags wordFlags = messages::MessageElement::Default; messages::MessageElement::Flags wordFlags = messages::MessageElement::Default;

View file

@ -59,24 +59,23 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
QColor themeColor = QColor::fromHslF(hue, 0.5, 0.5); QColor themeColor = QColor::fromHslF(hue, 0.5, 0.5);
QColor themeColorNoSat = QColor::fromHslF(hue, 0, 0.5); QColor themeColorNoSat = QColor::fromHslF(hue, 0, 0.5);
//#ifdef USEWINSDK qreal sat = 0.1;
// isLightTabs = isLight; // 0.05;
// QColor tabFg = isLight ? "#000" : "#fff";
// this->windowBg = isLight ? "#fff" : "#444";
//#else
isLightTabs = true;
QColor tabFg = isLightTabs ? "#000" : "#fff";
this->windowBg = "#fff";
//#endif
qreal sat = 0.05;
auto getColor = [multiplier](double h, double s, double l, double a = 1.0) { auto getColor = [multiplier](double h, double s, double l, double a = 1.0) {
return QColor::fromHslF(h, s, ((l - 0.5) * multiplier) + 0.5, a); return QColor::fromHslF(h, s, ((l - 0.5) * multiplier) + 0.5, a);
}; };
//#ifdef USEWINSDK
// isLightTabs = isLight;
// QColor tabFg = isLight ? "#000" : "#fff";
// this->windowBg = isLight ? "#fff" : getColor(0, sat, 0.9);
//#else
isLightTabs = true;
QColor tabFg = isLightTabs ? "#000" : "#fff";
this->windowBg = "#fff";
//#endif
// Ubuntu style // Ubuntu style
// TODO: add setting for this // TODO: add setting for this
// TabText = QColor(210, 210, 210); // TabText = QColor(210, 210, 210);

View file

@ -2,6 +2,8 @@
#include "debug/log.hpp" #include "debug/log.hpp"
#include "singletons/fontmanager.hpp" #include "singletons/fontmanager.hpp"
#include "singletons/thememanager.hpp" #include "singletons/thememanager.hpp"
#include "widgets/accountswitchpopupwidget.hpp"
#include "widgets/settingsdialog.hpp"
#include <QDebug> #include <QDebug>
@ -14,6 +16,35 @@ WindowManager &WindowManager::getInstance()
return instance; return instance;
} }
void WindowManager::showSettingsDialog()
{
QTimer::singleShot(80, [] { widgets::SettingsDialog::showDialog(); });
}
void WindowManager::showAccountSelectPopup(QPoint point)
{
// static QWidget *lastFocusedWidget = nullptr;
static widgets::AccountSwitchPopupWidget *w = new widgets::AccountSwitchPopupWidget();
if (w->hasFocus()) {
w->hide();
// if (lastFocusedWidget) {
// lastFocusedWidget->setFocus();
// }
return;
}
// lastFocusedWidget = this->focusWidget();
w->refresh();
QPoint buttonPos = point;
w->move(buttonPos.x(), buttonPos.y());
w->show();
w->setFocus();
}
WindowManager::WindowManager(ThemeManager &_themeManager) WindowManager::WindowManager(ThemeManager &_themeManager)
: themeManager(_themeManager) : themeManager(_themeManager)
{ {

View file

@ -14,6 +14,9 @@ class WindowManager
public: public:
static WindowManager &getInstance(); static WindowManager &getInstance();
void showSettingsDialog();
void showAccountSelectPopup(QPoint point);
void initMainWindow(); void initMainWindow();
void layoutVisibleChatWidgets(Channel *channel = nullptr); void layoutVisibleChatWidgets(Channel *channel = nullptr);
void repaintVisibleChatWidgets(Channel *channel = nullptr); void repaintVisibleChatWidgets(Channel *channel = nullptr);

View file

@ -1,5 +1,7 @@
#include "twitchchannel.hpp" #include "twitchchannel.hpp"
#include "debug/log.hpp" #include "debug/log.hpp"
#include "messages/message.hpp"
#include "singletons/channelmanager.hpp"
#include "singletons/emotemanager.hpp" #include "singletons/emotemanager.hpp"
#include "singletons/ircmanager.hpp" #include "singletons/ircmanager.hpp"
#include "singletons/settingsmanager.hpp" #include "singletons/settingsmanager.hpp"
@ -156,8 +158,8 @@ void TwitchChannel::refreshLiveStatus()
std::weak_ptr<Channel> weak = this->shared_from_this(); std::weak_ptr<Channel> weak = this->shared_from_this();
util::twitch::get2(url, QThread::currentThread(), [weak](const rapidjson::Document &d) { util::twitch::get2(url, QThread::currentThread(), false, [weak](const rapidjson::Document &d) {
SharedChannel shared = weak.lock(); ChannelPtr shared = weak.lock();
if (!shared) { if (!shared) {
return; return;
@ -218,7 +220,7 @@ void TwitchChannel::fetchRecentMessages()
std::weak_ptr<Channel> weak = this->shared_from_this(); std::weak_ptr<Channel> weak = this->shared_from_this();
util::twitch::get(genericURL.arg(roomID), QThread::currentThread(), [weak](QJsonObject obj) { util::twitch::get(genericURL.arg(roomID), QThread::currentThread(), [weak](QJsonObject obj) {
SharedChannel shared = weak.lock(); ChannelPtr shared = weak.lock();
if (!shared) { if (!shared) {
return; return;
@ -230,7 +232,6 @@ void TwitchChannel::fetchRecentMessages()
auto msgArray = obj.value("messages").toArray(); auto msgArray = obj.value("messages").toArray();
if (msgArray.size() > 0) { if (msgArray.size() > 0) {
std::vector<messages::MessagePtr> messages; std::vector<messages::MessagePtr> messages;
messages.resize(msgArray.size());
for (int i = 0; i < msgArray.size(); i++) { for (int i = 0; i < msgArray.size(); i++) {
QByteArray content = msgArray[i].toString().toUtf8(); QByteArray content = msgArray[i].toString().toUtf8();
@ -239,7 +240,9 @@ void TwitchChannel::fetchRecentMessages()
messages::MessageParseArgs args; messages::MessageParseArgs args;
twitch::TwitchMessageBuilder builder(channel, privMsg, args); twitch::TwitchMessageBuilder builder(channel, privMsg, args);
messages.at(i) = builder.parse(); if (!builder.isIgnored()) {
messages.push_back(builder.build());
}
} }
channel->addMessagesAtStart(messages); channel->addMessagesAtStart(messages);
} }

View file

@ -27,15 +27,28 @@ TwitchMessageBuilder::TwitchMessageBuilder(Channel *_channel,
, tags(this->ircMessage->tags()) , tags(this->ircMessage->tags())
, usernameColor(singletons::ThemeManager::getInstance().messages.textColors.system) , usernameColor(singletons::ThemeManager::getInstance().messages.textColors.system)
{ {
this->originalMessage = this->ircMessage->content();
} }
MessagePtr TwitchMessageBuilder::parse() bool TwitchMessageBuilder::isIgnored() const
{
singletons::SettingManager &settings = singletons::SettingManager::getInstance();
std::shared_ptr<std::vector<QString>> ignoredKeywords = settings.getIgnoredKeywords();
for (const QString &keyword : *ignoredKeywords) {
if (this->originalMessage.contains(keyword, Qt::CaseInsensitive)) {
return true;
}
}
return false;
}
MessagePtr TwitchMessageBuilder::build()
{ {
singletons::SettingManager &settings = singletons::SettingManager::getInstance(); singletons::SettingManager &settings = singletons::SettingManager::getInstance();
singletons::EmoteManager &emoteManager = singletons::EmoteManager::getInstance(); singletons::EmoteManager &emoteManager = singletons::EmoteManager::getInstance();
this->originalMessage = this->ircMessage->content();
// PARSING // PARSING
this->parseUsername(); this->parseUsername();
@ -43,11 +56,14 @@ MessagePtr TwitchMessageBuilder::parse()
// this->appendWord(Word(Resources::getInstance().badgeCollapsed, Word::Collapsed, QString(), // this->appendWord(Word(Resources::getInstance().badgeCollapsed, Word::Collapsed, QString(),
// QString())); // QString()));
// The timestamp is always appended to the builder // PARSING
// Whether or not will be rendered is decided/checked later this->parseMessageID();
// Appends the correct timestamp if the message is a past message this->parseRoomID();
this->appendChannelName();
// timestamp
bool isPastMsg = this->tags.contains("historical"); bool isPastMsg = this->tags.contains("historical");
if (isPastMsg) { if (isPastMsg) {
// This may be architecture dependent(datatype) // This may be architecture dependent(datatype)
@ -58,20 +74,11 @@ MessagePtr TwitchMessageBuilder::parse()
this->emplace<TimestampElement>(); this->emplace<TimestampElement>();
} }
this->parseMessageID();
this->parseRoomID();
// TIMESTAMP
this->emplace<TwitchModerationElement>(); this->emplace<TwitchModerationElement>();
this->parseTwitchBadges(); this->appendTwitchBadges();
this->addChatterinoBadges(); this->appendChatterinoBadges();
if (this->args.includeChannelName) {
this->parseChannelName();
}
this->appendUsername(); this->appendUsername();
@ -219,7 +226,7 @@ void TwitchMessageBuilder::parseRoomID()
} }
} }
void TwitchMessageBuilder::parseChannelName() void TwitchMessageBuilder::appendChannelName()
{ {
QString channelName("#" + this->channel->name); QString channelName("#" + this->channel->name);
Link link(Link::Url, this->channel->name + "\n" + this->messageID); Link link(Link::Url, this->channel->name + "\n" + this->messageID);
@ -450,39 +457,36 @@ bool TwitchMessageBuilder::tryAppendEmote(QString &emoteString)
singletons::EmoteManager &emoteManager = singletons::EmoteManager::getInstance(); singletons::EmoteManager &emoteManager = singletons::EmoteManager::getInstance();
util::EmoteData emoteData; util::EmoteData emoteData;
auto appendEmote = [&](MessageElement::Flags flags) {
this->emplace<EmoteElement>(emoteData, flags);
return true;
};
if (emoteManager.bttvGlobalEmotes.tryGet(emoteString, emoteData)) { if (emoteManager.bttvGlobalEmotes.tryGet(emoteString, emoteData)) {
// BTTV Global Emote // BTTV Global Emote
return this->appendEmote(emoteData); return appendEmote(MessageElement::BttvEmote);
} else if (this->twitchChannel != nullptr && } else if (this->twitchChannel != nullptr &&
this->twitchChannel->bttvChannelEmotes->tryGet(emoteString, emoteData)) { this->twitchChannel->bttvChannelEmotes->tryGet(emoteString, emoteData)) {
// BTTV Channel Emote // BTTV Channel Emote
return this->appendEmote(emoteData); return appendEmote(MessageElement::BttvEmote);
} else if (emoteManager.ffzGlobalEmotes.tryGet(emoteString, emoteData)) { } else if (emoteManager.ffzGlobalEmotes.tryGet(emoteString, emoteData)) {
// FFZ Global Emote // FFZ Global Emote
return this->appendEmote(emoteData); return appendEmote(MessageElement::FfzEmote);
} else if (this->twitchChannel != nullptr && } else if (this->twitchChannel != nullptr &&
this->twitchChannel->ffzChannelEmotes->tryGet(emoteString, emoteData)) { this->twitchChannel->ffzChannelEmotes->tryGet(emoteString, emoteData)) {
// FFZ Channel Emote // FFZ Channel Emote
return this->appendEmote(emoteData); return appendEmote(MessageElement::FfzEmote);
} else if (emoteManager.getChatterinoEmotes().tryGet(emoteString, emoteData)) { } else if (emoteManager.getChatterinoEmotes().tryGet(emoteString, emoteData)) {
// Chatterino Emote // Chatterino Emote
return this->appendEmote(emoteData); return appendEmote(MessageElement::Misc);
} }
return false; return false;
} }
bool TwitchMessageBuilder::appendEmote(const util::EmoteData &emoteData)
{
this->emplace<EmoteElement>(emoteData, MessageElement::BttvEmote);
// Perhaps check for ignored emotes here?
return true;
}
// fourtf: this is ugly // fourtf: this is ugly
// maybe put the individual badges into a map instead of this mess // maybe put the individual badges into a map instead of this mess
void TwitchMessageBuilder::parseTwitchBadges() void TwitchMessageBuilder::appendTwitchBadges()
{ {
singletons::ResourceManager &resourceManager = singletons::ResourceManager::getInstance(); singletons::ResourceManager &resourceManager = singletons::ResourceManager::getInstance();
const auto &channelResources = resourceManager.channels[this->roomID]; const auto &channelResources = resourceManager.channels[this->roomID];
@ -639,7 +643,7 @@ void TwitchMessageBuilder::parseTwitchBadges()
} }
} }
void TwitchMessageBuilder::addChatterinoBadges() void TwitchMessageBuilder::appendChatterinoBadges()
{ {
auto &badges = singletons::ResourceManager::getInstance().chatterinoBadges; auto &badges = singletons::ResourceManager::getInstance().chatterinoBadges;
auto it = badges.find(this->userName.toStdString()); auto it = badges.find(this->userName.toStdString());

View file

@ -38,7 +38,8 @@ public:
QString messageID; QString messageID;
QString userName; QString userName;
messages::MessagePtr parse(); bool isIgnored() const;
messages::MessagePtr build();
private: private:
QString roomID; QString roomID;
@ -47,7 +48,7 @@ private:
void parseMessageID(); void parseMessageID();
void parseRoomID(); void parseRoomID();
void parseChannelName(); void appendChannelName();
void parseUsername(); void parseUsername();
void appendUsername(); void appendUsername();
void parseHighlights(); void parseHighlights();
@ -55,10 +56,9 @@ private:
void appendTwitchEmote(const Communi::IrcPrivateMessage *ircMessage, const QString &emote, void appendTwitchEmote(const Communi::IrcPrivateMessage *ircMessage, const QString &emote,
std::vector<std::pair<long, util::EmoteData>> &vec); std::vector<std::pair<long, util::EmoteData>> &vec);
bool tryAppendEmote(QString &emoteString); bool tryAppendEmote(QString &emoteString);
bool appendEmote(const util::EmoteData &emoteData);
void parseTwitchBadges(); void appendTwitchBadges();
void addChatterinoBadges(); void appendChatterinoBadges();
bool tryParseCheermote(const QString &string); bool tryParseCheermote(const QString &string);
}; };

View file

@ -47,6 +47,18 @@ public:
return LayoutCreator<T2>(t); return LayoutCreator<T2>(t);
} }
template <typename T2, typename Q = T,
typename std::enable_if<std::is_base_of<QWidget, Q>::value, int>::type = 0,
typename std::enable_if<std::is_base_of<QLayout, T2>::value, int>::type = 0>
LayoutCreator<T2> setLayoutType()
{
T2 *layout = new T2;
this->item->setLayout(layout);
return LayoutCreator<T2>(layout);
}
LayoutCreator<T> assign(T **ptr) LayoutCreator<T> assign(T **ptr)
{ {
*ptr = this->item; *ptr = this->item;

View file

@ -37,14 +37,14 @@ static void get(QString url, const QObject *caller,
}); });
} }
static void get2(QString url, const QObject *caller, static void get2(QString url, const QObject *caller, bool useQuickLoadCache,
std::function<void(const rapidjson::Document &)> successCallback) std::function<void(const rapidjson::Document &)> successCallback)
{ {
util::NetworkRequest req(url); util::NetworkRequest req(url);
req.setCaller(caller); req.setCaller(caller);
req.setRawHeader("Client-ID", getDefaultClientID()); req.setRawHeader("Client-ID", getDefaultClientID());
req.setRawHeader("Accept", "application/vnd.twitchtv.v5+json"); req.setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
req.setUseQuickLoadCache(true); req.setUseQuickLoadCache(useQuickLoadCache);
req.getJSON2([=](const rapidjson::Document &document) { req.getJSON2([=](const rapidjson::Document &document) {
successCallback(document); // successCallback(document); //

View file

@ -18,13 +18,15 @@
namespace chatterino { namespace chatterino {
namespace widgets { namespace widgets {
AccountPopupWidget::AccountPopupWidget(SharedChannel _channel) AccountPopupWidget::AccountPopupWidget(ChannelPtr _channel)
: BaseWindow() : BaseWindow()
, ui(new Ui::AccountPopup) , ui(new Ui::AccountPopup)
, channel(_channel) , channel(_channel)
{ {
this->ui->setupUi(this); this->ui->setupUi(this);
this->setStayInScreenRect(true);
this->layout()->setSizeConstraint(QLayout::SetFixedSize); this->layout()->setSizeConstraint(QLayout::SetFixedSize);
this->setWindowFlags(Qt::FramelessWindowHint); this->setWindowFlags(Qt::FramelessWindowHint);
@ -49,7 +51,6 @@ AccountPopupWidget::AccountPopupWidget(SharedChannel _channel)
this->loggedInUser.userID = currentTwitchUser->getUserId(); this->loggedInUser.userID = currentTwitchUser->getUserId();
this->loggedInUser.refreshUserType(this->channel, true); this->loggedInUser.refreshUserType(this->channel, true);
}); });
singletons::SettingManager &settings = singletons::SettingManager::getInstance(); singletons::SettingManager &settings = singletons::SettingManager::getInstance();
@ -154,7 +155,7 @@ AccountPopupWidget::AccountPopupWidget(SharedChannel _channel)
this->hide(); // this->hide(); //
}); });
this->dpiMultiplierChanged(this->getDpiMultiplier(), this->getDpiMultiplier()); this->scaleChangedEvent(this->getScale());
} }
void AccountPopupWidget::setName(const QString &name) void AccountPopupWidget::setName(const QString &name)
@ -171,7 +172,7 @@ void AccountPopupWidget::setName(const QString &name)
this->popupWidgetUser.refreshUserType(this->channel, false); this->popupWidgetUser.refreshUserType(this->channel, false);
} }
void AccountPopupWidget::User::refreshUserType(const SharedChannel &channel, bool loggedInUser) void AccountPopupWidget::User::refreshUserType(const ChannelPtr &channel, bool loggedInUser)
{ {
if (channel->name == this->username) { if (channel->name == this->username) {
this->userType = UserType::Owner; this->userType = UserType::Owner;
@ -182,7 +183,7 @@ void AccountPopupWidget::User::refreshUserType(const SharedChannel &channel, boo
} }
} }
void AccountPopupWidget::setChannel(SharedChannel _channel) void AccountPopupWidget::setChannel(ChannelPtr _channel)
{ {
this->channel = _channel; this->channel = _channel;
} }
@ -246,7 +247,7 @@ void AccountPopupWidget::loadAvatar(const QUrl &avatarUrl)
} }
} }
void AccountPopupWidget::dpiMultiplierChanged(float /*oldDpi*/, float newDpi) void AccountPopupWidget::scaleChangedEvent(float newDpi)
{ {
this->setStyleSheet(QString("* { font-size: <font-size>px; }") this->setStyleSheet(QString("* { font-size: <font-size>px; }")
.replace("<font-size>", QString::number((int)(12 * newDpi)))); .replace("<font-size>", QString::number((int)(12 * newDpi))));

View file

@ -23,10 +23,10 @@ class AccountPopupWidget : public BaseWindow
{ {
Q_OBJECT Q_OBJECT
public: public:
AccountPopupWidget(SharedChannel _channel); AccountPopupWidget(ChannelPtr _channel);
void setName(const QString &name); void setName(const QString &name);
void setChannel(SharedChannel _channel); void setChannel(ChannelPtr _channel);
public slots: public slots:
void actuallyRefreshButtons(); void actuallyRefreshButtons();
@ -35,7 +35,7 @@ signals:
void refreshButtons(); void refreshButtons();
protected: protected:
virtual void dpiMultiplierChanged(float oldDpi, float newDpi) override; virtual void scaleChangedEvent(float newDpi) override;
private: private:
Ui::AccountPopup *ui; Ui::AccountPopup *ui;
@ -52,7 +52,7 @@ private:
enum class UserType { User, Mod, Owner }; enum class UserType { User, Mod, Owner };
SharedChannel channel; ChannelPtr channel;
QPixmap avatar; QPixmap avatar;
@ -63,7 +63,7 @@ private:
QString userID; QString userID;
UserType userType = UserType::User; UserType userType = UserType::User;
void refreshUserType(const SharedChannel &channel, bool loggedInUser); void refreshUserType(const ChannelPtr &channel, bool loggedInUser);
}; };
User loggedInUser; User loggedInUser;

View file

@ -2,6 +2,7 @@
#include "singletons/settingsmanager.hpp" #include "singletons/settingsmanager.hpp"
#include "singletons/thememanager.hpp" #include "singletons/thememanager.hpp"
#include <QChildEvent>
#include <QDebug> #include <QDebug>
#include <QIcon> #include <QIcon>
#include <QLayout> #include <QLayout>
@ -25,13 +26,7 @@ BaseWidget::BaseWidget(BaseWidget *parent, Qt::WindowFlags f)
this->init(); this->init();
} }
BaseWidget::BaseWidget(QWidget *parent, Qt::WindowFlags f) float BaseWidget::getScale() const
: QWidget(parent, f)
, themeManager(singletons::ThemeManager::getInstance())
{
}
float BaseWidget::getDpiMultiplier()
{ {
// return 1.f; // return 1.f;
BaseWidget *baseWidget = dynamic_cast<BaseWidget *>(this->window()); BaseWidget *baseWidget = dynamic_cast<BaseWidget *>(this->window());
@ -39,17 +34,56 @@ float BaseWidget::getDpiMultiplier()
if (baseWidget == nullptr) { if (baseWidget == nullptr) {
return 1.f; return 1.f;
} else { } else {
return baseWidget->dpiMultiplier; return baseWidget->scale;
// int screenNr = QApplication::desktop()->screenNumber(this);
// QScreen *screen = QApplication::screens().at(screenNr);
// return screen->logicalDotsPerInch() / 96.f;
} }
} }
QSize BaseWidget::getScaleIndependantSize() const
{
return this->scaleIndependantSize;
}
int BaseWidget::getScaleIndependantWidth() const
{
return this->scaleIndependantSize.width();
}
int BaseWidget::getScaleIndependantHeight() const
{
return this->scaleIndependantSize.height();
}
void BaseWidget::setScaleIndependantSize(int width, int height)
{
this->setScaleIndependantSize(QSize(width, height));
}
void BaseWidget::setScaleIndependantSize(QSize size)
{
this->scaleIndependantSize = size;
if (size.width() > 0) {
this->setFixedWidth((int)(size.width() * this->getScale()));
}
if (size.height() > 0) {
this->setFixedHeight((int)(size.height() * this->getScale()));
}
}
void BaseWidget::setScaleIndependantWidth(int value)
{
this->setScaleIndependantSize(QSize(value, this->scaleIndependantSize.height()));
}
void BaseWidget::setScaleIndependantHeight(int value)
{
this->setScaleIndependantSize(QSize(this->scaleIndependantSize.height(), value));
}
void BaseWidget::init() void BaseWidget::init()
{ {
auto connection = this->themeManager.updated.connect([this]() { auto connection = this->themeManager.updated.connect([this]() {
this->refreshTheme(); this->themeRefreshEvent();
this->update(); this->update();
}); });
@ -59,7 +93,69 @@ void BaseWidget::init()
}); });
} }
void BaseWidget::refreshTheme() void BaseWidget::childEvent(QChildEvent *event)
{
if (event->added()) {
BaseWidget *widget = dynamic_cast<BaseWidget *>(event->child());
if (widget) {
this->widgets.push_back(widget);
}
} else if (event->removed()) {
for (auto it = this->widgets.begin(); it != this->widgets.end(); it++) {
if (*it == event->child()) {
this->widgets.erase(it);
break;
}
}
}
}
void BaseWidget::setScale(float value)
{
// update scale value
this->scale = value;
this->scaleChangedEvent(value);
this->scaleChanged.invoke(value);
this->setScaleIndependantSize(this->getScaleIndependantSize());
// set scale for all children
BaseWidget::setScaleRecursive(value, this);
}
void BaseWidget::setScaleRecursive(float scale, QObject *object)
{
for (QObject *child : object->children()) {
BaseWidget *widget = dynamic_cast<BaseWidget *>(child);
if (widget != nullptr) {
widget->setScale(scale);
continue;
}
// QLayout *layout = nullptr;
// QWidget *widget = dynamic_cast<QWidget *>(child);
// if (widget != nullptr) {
// layout = widget->layout();
// }
// else {
QLayout *layout = dynamic_cast<QLayout *>(object);
if (layout != nullptr) {
setScaleRecursive(scale, layout);
}
// }
}
}
void BaseWidget::scaleChangedEvent(float newDpi)
{
}
void BaseWidget::themeRefreshEvent()
{ {
// Do any color scheme updates here // Do any color scheme updates here
} }

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <QWidget> #include <QWidget>
#include <pajlada/signals/signal.hpp>
namespace chatterino { namespace chatterino {
namespace singletons { namespace singletons {
@ -8,6 +9,7 @@ class ThemeManager;
} }
namespace widgets { namespace widgets {
class BaseWindow;
class BaseWidget : public QWidget class BaseWidget : public QWidget
{ {
@ -17,23 +19,38 @@ public:
explicit BaseWidget(singletons::ThemeManager &_themeManager, QWidget *parent, explicit BaseWidget(singletons::ThemeManager &_themeManager, QWidget *parent,
Qt::WindowFlags f = Qt::WindowFlags()); Qt::WindowFlags f = Qt::WindowFlags());
explicit BaseWidget(BaseWidget *parent, Qt::WindowFlags f = Qt::WindowFlags()); explicit BaseWidget(BaseWidget *parent, Qt::WindowFlags f = Qt::WindowFlags());
explicit BaseWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
singletons::ThemeManager &themeManager; singletons::ThemeManager &themeManager;
float getDpiMultiplier(); float getScale() const;
pajlada::Signals::Signal<float> scaleChanged;
QSize getScaleIndependantSize() const;
int getScaleIndependantWidth() const;
int getScaleIndependantHeight() const;
void setScaleIndependantSize(int width, int height);
void setScaleIndependantSize(QSize);
void setScaleIndependantWidth(int value);
void setScaleIndependantHeight(int value);
protected: protected:
virtual void dpiMultiplierChanged(float /*oldDpi*/, float /*newDpi*/) virtual void childEvent(QChildEvent *) override;
{
}
float dpiMultiplier = 1.f; virtual void scaleChangedEvent(float newScale);
virtual void themeRefreshEvent();
void setScale(float value);
private: private:
void init(); void init();
float scale = 1.f;
QSize scaleIndependantSize;
virtual void refreshTheme(); std::vector<BaseWidget *> widgets;
static void setScaleRecursive(float scale, QObject *object);
friend class BaseWindow;
}; };
} // namespace widgets } // namespace widgets

View file

@ -6,6 +6,7 @@
#include <QApplication> #include <QApplication>
#include <QDebug> #include <QDebug>
#include <QDesktopWidget>
#include <QIcon> #include <QIcon>
#ifdef USEWINSDK #ifdef USEWINSDK
@ -22,7 +23,7 @@
#define WM_DPICHANGED 0x02E0 #define WM_DPICHANGED 0x02E0
#endif #endif
#include "widgets/helper/rippleeffectlabel.hpp" #include "widgets/helper/titlebarbutton.hpp"
namespace chatterino { namespace chatterino {
namespace widgets { namespace widgets {
@ -43,7 +44,7 @@ BaseWindow::BaseWindow(BaseWidget *parent, bool _enableCustomFrame)
} }
BaseWindow::BaseWindow(QWidget *parent, bool _enableCustomFrame) BaseWindow::BaseWindow(QWidget *parent, bool _enableCustomFrame)
: BaseWidget(parent, Qt::Window) : BaseWidget(singletons::ThemeManager::getInstance(), parent, Qt::Window)
, enableCustomFrame(_enableCustomFrame) , enableCustomFrame(_enableCustomFrame)
{ {
this->init(); this->init();
@ -56,45 +57,57 @@ void BaseWindow::init()
#ifdef USEWINSDK #ifdef USEWINSDK
if (this->hasCustomWindowFrame()) { if (this->hasCustomWindowFrame()) {
// CUSTOM WINDOW FRAME // CUSTOM WINDOW FRAME
QVBoxLayout *layout = new QVBoxLayout; QVBoxLayout *layout = new QVBoxLayout();
layout->setMargin(1); layout->setMargin(1);
layout->setSpacing(0);
this->setLayout(layout); this->setLayout(layout);
{ {
QHBoxLayout *buttons = this->titlebarBox = new QHBoxLayout; QHBoxLayout *buttonLayout = this->titlebarBox = new QHBoxLayout();
buttons->setMargin(0); buttonLayout->setMargin(0);
layout->addLayout(buttons); layout->addLayout(buttonLayout);
// title // title
QLabel *titleLabel = new QLabel("Chatterino"); QLabel *title = new QLabel(" Chatterino");
buttons->addWidget(titleLabel); buttonLayout->addWidget(title);
this->titleLabel = titleLabel; this->titleLabel = title;
// buttons // buttons
RippleEffectLabel *min = new RippleEffectLabel; TitleBarButton *_minButton = new TitleBarButton;
min->getLabel().setText("min"); _minButton->setScaleIndependantSize(46, 30);
min->setFixedSize(46, 30); _minButton->setButtonStyle(TitleBarButton::Minimize);
RippleEffectLabel *max = new RippleEffectLabel; TitleBarButton *_maxButton = new TitleBarButton;
max->setFixedSize(46, 30); _maxButton->setScaleIndependantSize(46, 30);
max->getLabel().setText("max"); _maxButton->setButtonStyle(TitleBarButton::Maximize);
RippleEffectLabel *exit = new RippleEffectLabel; TitleBarButton *_exitButton = new TitleBarButton;
exit->setFixedSize(46, 30); _exitButton->setScaleIndependantSize(46, 30);
exit->getLabel().setText("exit"); _exitButton->setButtonStyle(TitleBarButton::Close);
this->minButton = min; QObject::connect(_minButton, &TitleBarButton::clicked, this, [this] {
this->maxButton = max; this->setWindowState(Qt::WindowMinimized | this->windowState());
this->exitButton = exit; });
QObject::connect(_maxButton, &TitleBarButton::clicked, this, [this] {
this->setWindowState(this->windowState() == Qt::WindowMaximized
? Qt::WindowActive
: Qt::WindowMaximized);
});
QObject::connect(_exitButton, &TitleBarButton::clicked, this,
[this] { this->close(); });
this->widgets.push_back(min); this->minButton = _minButton;
this->widgets.push_back(max); this->maxButton = _maxButton;
this->widgets.push_back(exit); this->exitButton = _exitButton;
buttons->addStretch(1); this->buttons.push_back(_minButton);
buttons->addWidget(min); this->buttons.push_back(_maxButton);
buttons->addWidget(max); this->buttons.push_back(_exitButton);
buttons->addWidget(exit);
buttonLayout->addStretch(1);
buttonLayout->addWidget(_minButton);
buttonLayout->addWidget(_maxButton);
buttonLayout->addWidget(_exitButton);
buttonLayout->setSpacing(0);
} }
this->layoutBase = new QWidget(this); this->layoutBase = new BaseWidget(this);
this->widgets.push_back(this->layoutBase);
layout->addWidget(this->layoutBase); layout->addWidget(this->layoutBase);
} }
@ -102,10 +115,10 @@ void BaseWindow::init()
auto dpi = util::getWindowDpi(this->winId()); auto dpi = util::getWindowDpi(this->winId());
if (dpi) { if (dpi) {
this->dpiMultiplier = dpi.value() / 96.f; this->scale = dpi.value() / 96.f;
} }
this->dpiMultiplierChanged(1, this->dpiMultiplier); this->scaleChangedEvent(this->scale);
#endif #endif
if (singletons::SettingManager::getInstance().windowTopMost.getValue()) { if (singletons::SettingManager::getInstance().windowTopMost.getValue()) {
@ -113,6 +126,16 @@ void BaseWindow::init()
} }
} }
void BaseWindow::setStayInScreenRect(bool value)
{
this->stayInScreenRect = value;
}
bool BaseWindow::getStayInScreenRect() const
{
return this->stayInScreenRect;
}
QWidget *BaseWindow::getLayoutContainer() QWidget *BaseWindow::getLayoutContainer()
{ {
if (this->hasCustomWindowFrame()) { if (this->hasCustomWindowFrame()) {
@ -125,37 +148,96 @@ QWidget *BaseWindow::getLayoutContainer()
bool BaseWindow::hasCustomWindowFrame() bool BaseWindow::hasCustomWindowFrame()
{ {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
// return this->enableCustomFrame; return this->enableCustomFrame;
return false; // return false;
#else #else
return false; return false;
#endif #endif
} }
void BaseWindow::refreshTheme() void BaseWindow::themeRefreshEvent()
{ {
QPalette palette; QPalette palette;
palette.setColor(QPalette::Background, this->themeManager.windowBg); palette.setColor(QPalette::Background, this->themeManager.windowBg);
palette.setColor(QPalette::Foreground, this->themeManager.windowText); palette.setColor(QPalette::Foreground, this->themeManager.windowText);
this->setPalette(palette); this->setPalette(palette);
for (RippleEffectButton *button : this->buttons) {
button->setMouseEffectColor(this->themeManager.windowText);
}
} }
void BaseWindow::addTitleBarButton(const QString &text) void BaseWindow::addTitleBarButton(const TitleBarButton::Style &style,
std::function<void()> onClicked)
{ {
RippleEffectLabel *label = new RippleEffectLabel; TitleBarButton *button = new TitleBarButton;
label->getLabel().setText(text); button->setScaleIndependantSize(30, 30);
this->widgets.push_back(label);
this->titlebarBox->insertWidget(2, label); this->buttons.push_back(button);
this->titlebarBox->insertWidget(2, button);
button->setButtonStyle(style);
QObject::connect(button, &TitleBarButton::clicked, this, [onClicked] { onClicked(); });
} }
void BaseWindow::changeEvent(QEvent *) void BaseWindow::changeEvent(QEvent *)
{ {
// TooltipWidget::getInstance()->hide(); TooltipWidget::getInstance()->hide();
#ifdef USEWINSDK
if (this->hasCustomWindowFrame()) {
this->maxButton->setButtonStyle(this->windowState() & Qt::WindowMaximized
? TitleBarButton::Unmaximize
: TitleBarButton::Maximize);
}
#endif
} }
void BaseWindow::leaveEvent(QEvent *) void BaseWindow::leaveEvent(QEvent *)
{ {
// TooltipWidget::getInstance()->hide(); TooltipWidget::getInstance()->hide();
}
void BaseWindow::moveTo(QWidget *parent, QPoint point)
{
point.rx() += 16;
point.ry() += 16;
this->move(point);
this->moveIntoDesktopRect(parent);
}
void BaseWindow::resizeEvent(QResizeEvent *)
{
this->moveIntoDesktopRect(this);
}
void BaseWindow::moveIntoDesktopRect(QWidget *parent)
{
if (!this->stayInScreenRect)
return;
// move the widget into the screen geometry if it's not already in there
QDesktopWidget *desktop = QApplication::desktop();
QRect s = desktop->screenGeometry(parent);
QPoint p = this->pos();
if (p.x() < s.left()) {
p.setX(s.left());
}
if (p.y() < s.top()) {
p.setY(s.top());
}
if (p.x() + this->width() > s.right()) {
p.setX(s.right() - this->width());
}
if (p.y() + this->height() > s.bottom()) {
p.setY(s.bottom() - this->height());
}
if (p != this->pos())
this->move(p);
} }
#ifdef USEWINSDK #ifdef USEWINSDK
@ -165,17 +247,16 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, long *r
switch (msg->message) { switch (msg->message) {
case WM_DPICHANGED: { case WM_DPICHANGED: {
qDebug() << "dpi changed";
int dpi = HIWORD(msg->wParam); int dpi = HIWORD(msg->wParam);
float oldDpiMultiplier = this->dpiMultiplier; float oldScale = this->scale;
this->dpiMultiplier = dpi / 96.f; float _scale = dpi / 96.f;
float scale = this->dpiMultiplier / oldDpiMultiplier; float resizeScale = _scale / oldScale;
this->dpiMultiplierChanged(oldDpiMultiplier, this->dpiMultiplier); this->resize(static_cast<int>(this->width() * resizeScale),
static_cast<int>(this->height() * resizeScale));
this->resize(static_cast<int>(this->width() * scale), this->setScale(_scale);
static_cast<int>(this->height() * scale));
return true; return true;
} }
@ -250,12 +331,16 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, long *r
bool client = false; bool client = false;
QPoint point(x - winrect.left, y - winrect.top); QPoint point(x - winrect.left, y - winrect.top);
for (QWidget *widget : this->widgets) { for (QWidget *widget : this->buttons) {
if (widget->geometry().contains(point)) { if (widget->geometry().contains(point)) {
client = true; client = true;
} }
} }
if (this->layoutBase->geometry().contains(point)) {
client = true;
}
if (client) { if (client) {
*result = HTCLIENT; *result = HTCLIENT;
} else { } else {
@ -263,8 +348,6 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, long *r
} }
} }
qDebug() << *result;
return true; return true;
} else { } else {
return QWidget::nativeEvent(eventType, message, result); return QWidget::nativeEvent(eventType, message, result);
@ -272,9 +355,10 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, long *r
break; break;
} // end case WM_NCHITTEST } // end case WM_NCHITTEST
case WM_CLOSE: { case WM_CLOSE: {
if (this->enableCustomFrame) { // if (this->enableCustomFrame) {
return close(); // this->close();
} // }
return QWidget::nativeEvent(eventType, message, result);
break; break;
} }
default: default:
@ -282,9 +366,10 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, long *r
} }
} }
void BaseWindow::showEvent(QShowEvent *) void BaseWindow::showEvent(QShowEvent *event)
{ {
if (this->isVisible() && this->hasCustomWindowFrame()) { if (!this->shown && this->isVisible() && this->hasCustomWindowFrame()) {
this->shown = true;
SetWindowLongPtr((HWND)this->winId(), GWL_STYLE, SetWindowLongPtr((HWND)this->winId(), GWL_STYLE,
WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX); WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX);
@ -294,22 +379,29 @@ void BaseWindow::showEvent(QShowEvent *)
SetWindowPos((HWND)this->winId(), 0, 0, 0, 0, 0, SetWindowPos((HWND)this->winId(), 0, 0, 0, 0, 0,
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE); SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
} }
BaseWidget::showEvent(event);
} }
void BaseWindow::paintEvent(QPaintEvent *event) void BaseWindow::paintEvent(QPaintEvent *event)
{ {
if (this->hasCustomWindowFrame()) {
BaseWidget::paintEvent(event); BaseWidget::paintEvent(event);
if (this->hasCustomWindowFrame()) {
QPainter painter(this); QPainter painter(this);
bool windowFocused = this->window() == QApplication::activeWindow(); bool windowFocused = this->window() == QApplication::activeWindow();
QLinearGradient gradient(0, 0, 10, 250);
gradient.setColorAt(1, this->themeManager.tabs.selected.backgrounds.unfocused.color());
if (windowFocused) { if (windowFocused) {
painter.setPen(this->themeManager.tabs.selected.backgrounds.regular.color()); gradient.setColorAt(.4, this->themeManager.tabs.selected.backgrounds.regular.color());
} else { } else {
painter.setPen(this->themeManager.tabs.selected.backgrounds.unfocused.color()); gradient.setColorAt(.4, this->themeManager.tabs.selected.backgrounds.unfocused.color());
} }
painter.setPen(QPen(QBrush(gradient), 1));
painter.drawRect(0, 0, this->width() - 1, this->height() - 1); painter.drawRect(0, 0, this->width() - 1, this->height() - 1);
} }
} }

View file

@ -1,11 +1,16 @@
#pragma once #pragma once
#include "basewidget.hpp" #include "basewidget.hpp"
#include "widgets/helper/titlebarbutton.hpp"
#include <functional>
class QHBoxLayout; class QHBoxLayout;
namespace chatterino { namespace chatterino {
namespace widgets { namespace widgets {
class RippleEffectButton;
class TitleBarButton;
class BaseWindow : public BaseWidget class BaseWindow : public BaseWidget
{ {
@ -17,7 +22,12 @@ public:
QWidget *getLayoutContainer(); QWidget *getLayoutContainer();
bool hasCustomWindowFrame(); bool hasCustomWindowFrame();
void addTitleBarButton(const QString &text); void addTitleBarButton(const TitleBarButton::Style &style, std::function<void()> onClicked);
void setStayInScreenRect(bool value);
bool getStayInScreenRect() const;
void moveTo(QWidget *widget, QPoint point);
protected: protected:
#ifdef USEWINSDK #ifdef USEWINSDK
@ -28,21 +38,25 @@ protected:
virtual void changeEvent(QEvent *) override; virtual void changeEvent(QEvent *) override;
virtual void leaveEvent(QEvent *) override; virtual void leaveEvent(QEvent *) override;
virtual void resizeEvent(QResizeEvent *) override;
virtual void refreshTheme() override; virtual void themeRefreshEvent() override;
private: private:
void init(); void init();
void moveIntoDesktopRect(QWidget *parent);
bool enableCustomFrame; bool enableCustomFrame;
bool stayInScreenRect = false;
bool shown = false;
QHBoxLayout *titlebarBox; QHBoxLayout *titlebarBox;
QWidget *titleLabel; QWidget *titleLabel;
QWidget *minButton; TitleBarButton *minButton;
QWidget *maxButton; TitleBarButton *maxButton;
QWidget *exitButton; TitleBarButton *exitButton;
QWidget *layoutBase; QWidget *layoutBase;
std::vector<QWidget *> widgets; std::vector<RippleEffectButton *> buttons;
}; };
} // namespace widgets } // namespace widgets
} // namespace chatterino } // namespace chatterino

View file

@ -18,6 +18,11 @@ EmotePopup::EmotePopup(singletons::ThemeManager &themeManager)
this->viewEmotes = new ChannelView(); this->viewEmotes = new ChannelView();
this->viewEmojis = new ChannelView(); this->viewEmojis = new ChannelView();
this->viewEmotes->setOverrideFlags((MessageElement::Flags)(
MessageElement::Default | MessageElement::AlwaysShow | MessageElement::EmoteImages));
this->viewEmojis->setOverrideFlags((MessageElement::Flags)(
MessageElement::Default | MessageElement::AlwaysShow | MessageElement::EmoteImages));
this->viewEmotes->setEnableScrollingToBottom(false); this->viewEmotes->setEnableScrollingToBottom(false);
this->viewEmojis->setEnableScrollingToBottom(false); this->viewEmojis->setEnableScrollingToBottom(false);
@ -30,9 +35,14 @@ EmotePopup::EmotePopup(singletons::ThemeManager &themeManager)
tabs->addTab(this->viewEmojis, "Emojis"); tabs->addTab(this->viewEmojis, "Emojis");
this->loadEmojis(); this->loadEmojis();
this->viewEmotes->linkClicked.connect(
[this](const Link &link) { this->linkClicked.invoke(link); });
this->viewEmojis->linkClicked.connect(
[this](const Link &link) { this->linkClicked.invoke(link); });
} }
void EmotePopup::loadChannel(SharedChannel _channel) void EmotePopup::loadChannel(ChannelPtr _channel)
{ {
TwitchChannel *channel = dynamic_cast<TwitchChannel *>(_channel.get()); TwitchChannel *channel = dynamic_cast<TwitchChannel *>(_channel.get());
@ -40,7 +50,7 @@ void EmotePopup::loadChannel(SharedChannel _channel)
return; return;
} }
SharedChannel emoteChannel(new Channel("")); ChannelPtr emoteChannel(new Channel(""));
auto addEmotes = [&](util::EmoteMap &map, const QString &title, const QString &emoteDesc) { auto addEmotes = [&](util::EmoteMap &map, const QString &title, const QString &emoteDesc) {
// TITLE // TITLE
@ -57,7 +67,7 @@ void EmotePopup::loadChannel(SharedChannel _channel)
builder2.getMessage()->flags &= Message::DisableCompactEmotes; builder2.getMessage()->flags &= Message::DisableCompactEmotes;
map.each([&](const QString &key, const util::EmoteData &value) { map.each([&](const QString &key, const util::EmoteData &value) {
builder2.appendElement((new EmoteElement(value, MessageElement::Flags::AlwaysShow)) // builder2.appendElement((new EmoteElement(value, MessageElement::Flags::AlwaysShow))
->setLink(Link(Link::InsertText, key))); ->setLink(Link(Link::InsertText, key)));
}); });
@ -81,7 +91,7 @@ void EmotePopup::loadEmojis()
{ {
util::EmoteMap &emojis = singletons::EmoteManager::getInstance().getEmojis(); util::EmoteMap &emojis = singletons::EmoteManager::getInstance().getEmojis();
SharedChannel emojiChannel(new Channel("")); ChannelPtr emojiChannel(new Channel(""));
// title // title
messages::MessageBuilder builder1; messages::MessageBuilder builder1;
@ -96,7 +106,7 @@ void EmotePopup::loadEmojis()
builder.getMessage()->flags &= Message::DisableCompactEmotes; builder.getMessage()->flags &= Message::DisableCompactEmotes;
emojis.each([this, &builder](const QString &key, const util::EmoteData &value) { emojis.each([this, &builder](const QString &key, const util::EmoteData &value) {
builder.appendElement((new EmoteElement(value, MessageElement::Flags::AlwaysShow)) // builder.appendElement((new EmoteElement(value, MessageElement::Flags::AlwaysShow))
->setLink(Link(Link::Type::InsertText, key))); ->setLink(Link(Link::Type::InsertText, key)));
}); });
emojiChannel->addMessage(builder.getMessage()); emojiChannel->addMessage(builder.getMessage());

View file

@ -4,6 +4,8 @@
#include "widgets/basewindow.hpp" #include "widgets/basewindow.hpp"
#include "widgets/helper/channelview.hpp" #include "widgets/helper/channelview.hpp"
#include <pajlada/signals/signal.hpp>
namespace chatterino { namespace chatterino {
namespace widgets { namespace widgets {
@ -12,9 +14,11 @@ class EmotePopup : public BaseWindow
public: public:
explicit EmotePopup(singletons::ThemeManager &); explicit EmotePopup(singletons::ThemeManager &);
void loadChannel(SharedChannel channel); void loadChannel(ChannelPtr channel);
void loadEmojis(); void loadEmojis();
pajlada::Signals::Signal<messages::Link> linkClicked;
private: private:
ChannelView *viewEmotes; ChannelView *viewEmotes;
ChannelView *viewEmojis; ChannelView *viewEmojis;

View file

@ -13,6 +13,7 @@
#include "widgets/split.hpp" #include "widgets/split.hpp"
#include "widgets/tooltipwidget.hpp" #include "widgets/tooltipwidget.hpp"
#include <QClipboard>
#include <QDebug> #include <QDebug>
#include <QDesktopServices> #include <QDesktopServices>
#include <QGraphicsBlurEffect> #include <QGraphicsBlurEffect>
@ -24,8 +25,7 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#define LAYOUT_WIDTH \ #define LAYOUT_WIDTH (this->width() - (this->scrollBar.isVisible() ? 16 : 4) * this->getScale())
(this->width() - (this->scrollBar.isVisible() ? 16 : 4) * this->getDpiMultiplier())
using namespace chatterino::messages; using namespace chatterino::messages;
@ -96,6 +96,9 @@ ChannelView::ChannelView(BaseWidget *parent)
auto e = new QResizeEvent(this->size(), this->size()); auto e = new QResizeEvent(this->size(), this->size());
this->resizeEvent(e); this->resizeEvent(e);
delete e; delete e;
singletons::SettingManager::getInstance().showLastMessageIndicator.connect(
[this](auto, auto) { this->update(); }, this->managedConnections);
} }
ChannelView::~ChannelView() ChannelView::~ChannelView()
@ -111,6 +114,13 @@ ChannelView::~ChannelView()
this->messageReplacedConnection.disconnect(); this->messageReplacedConnection.disconnect();
} }
void ChannelView::themeRefreshEvent()
{
BaseWidget::themeRefreshEvent();
this->layoutMessages();
}
void ChannelView::queueUpdate() void ChannelView::queueUpdate()
{ {
if (this->updateTimer.isActive()) { if (this->updateTimer.isActive()) {
@ -164,7 +174,7 @@ void ChannelView::actuallyLayoutMessages()
for (size_t i = start; i < messagesSnapshot.getLength(); ++i) { for (size_t i = start; i < messagesSnapshot.getLength(); ++i) {
auto message = messagesSnapshot[i]; auto message = messagesSnapshot[i];
redrawRequired |= message->layout(layoutWidth, this->getDpiMultiplier(), flags); redrawRequired |= message->layout(layoutWidth, this->getScale(), flags);
y += message->getHeight(); y += message->getHeight();
@ -180,7 +190,7 @@ void ChannelView::actuallyLayoutMessages()
for (int i = (int)messagesSnapshot.getLength() - 1; i >= 0; i--) { for (int i = (int)messagesSnapshot.getLength() - 1; i >= 0; i--) {
auto *message = messagesSnapshot[i].get(); auto *message = messagesSnapshot[i].get();
message->layout(layoutWidth, this->getDpiMultiplier(), flags); message->layout(layoutWidth, this->getScale(), flags);
h -= message->getHeight(); h -= message->getHeight();
@ -281,6 +291,16 @@ bool ChannelView::getEnableScrollingToBottom() const
return this->enableScrollingToBottom; return this->enableScrollingToBottom;
} }
void ChannelView::setOverrideFlags(boost::optional<messages::MessageElement::Flags> value)
{
this->overrideFlags = value;
}
const boost::optional<messages::MessageElement::Flags> &ChannelView::getOverrideFlags() const
{
return this->overrideFlags;
}
messages::LimitedQueueSnapshot<MessageLayoutPtr> ChannelView::getMessagesSnapshot() messages::LimitedQueueSnapshot<MessageLayoutPtr> ChannelView::getMessagesSnapshot()
{ {
if (!this->paused) { if (!this->paused) {
@ -290,7 +310,7 @@ messages::LimitedQueueSnapshot<MessageLayoutPtr> ChannelView::getMessagesSnapsho
return this->snapshot; return this->snapshot;
} }
void ChannelView::setChannel(SharedChannel newChannel) void ChannelView::setChannel(ChannelPtr newChannel)
{ {
if (this->channel) { if (this->channel) {
this->detachChannel(); this->detachChannel();
@ -328,7 +348,6 @@ void ChannelView::setChannel(SharedChannel newChannel)
newChannel->messagesAddedAtStart.connect([this](std::vector<MessagePtr> &messages) { newChannel->messagesAddedAtStart.connect([this](std::vector<MessagePtr> &messages) {
std::vector<MessageLayoutPtr> messageRefs; std::vector<MessageLayoutPtr> messageRefs;
messageRefs.resize(messages.size()); messageRefs.resize(messages.size());
qDebug() << messages.size();
for (size_t i = 0; i < messages.size(); i++) { for (size_t i = 0; i < messages.size(); i++) {
messageRefs.at(i) = MessageLayoutPtr(new MessageLayout(messages.at(i))); messageRefs.at(i) = MessageLayoutPtr(new MessageLayout(messages.at(i)));
} }
@ -410,6 +429,17 @@ void ChannelView::pause(int msecTimeout)
this->pauseTimeout.start(msecTimeout); this->pauseTimeout.start(msecTimeout);
} }
void ChannelView::updateLastReadMessage()
{
auto _snapshot = this->getMessagesSnapshot();
if (_snapshot.getLength() > 0) {
this->lastReadMessage = _snapshot[_snapshot.getLength() - 1];
}
this->update();
}
void ChannelView::resizeEvent(QResizeEvent *) void ChannelView::resizeEvent(QResizeEvent *)
{ {
this->scrollBar.resize(this->scrollBar.width(), height()); this->scrollBar.resize(this->scrollBar.width(), height());
@ -434,6 +464,10 @@ void ChannelView::setSelection(const SelectionItem &start, const SelectionItem &
messages::MessageElement::Flags ChannelView::getFlags() const messages::MessageElement::Flags ChannelView::getFlags() const
{ {
if (this->overrideFlags) {
return this->overrideFlags.get();
}
MessageElement::Flags flags = singletons::SettingManager::getInstance().getWordFlags(); MessageElement::Flags flags = singletons::SettingManager::getInstance().getWordFlags();
Split *split = dynamic_cast<Split *>(this->parentWidget()); Split *split = dynamic_cast<Split *>(this->parentWidget());
@ -442,6 +476,9 @@ messages::MessageElement::Flags ChannelView::getFlags() const
if (split->getModerationMode()) { if (split->getModerationMode()) {
flags = (MessageElement::Flags)(flags | MessageElement::ModeratorTools); flags = (MessageElement::Flags)(flags | MessageElement::ModeratorTools);
} }
if (this->channel == singletons::ChannelManager::getInstance().mentionsChannel) {
flags = (MessageElement::Flags)(flags | MessageElement::ChannelName);
}
} }
return flags; return flags;
@ -477,11 +514,17 @@ void ChannelView::drawMessages(QPainter &painter)
(fmod(this->scrollBar.getCurrentValue(), 1))); (fmod(this->scrollBar.getCurrentValue(), 1)));
messages::MessageLayout *end = nullptr; messages::MessageLayout *end = nullptr;
bool windowFocused = this->window() == QApplication::activeWindow();
for (size_t i = start; i < messagesSnapshot.getLength(); ++i) { for (size_t i = start; i < messagesSnapshot.getLength(); ++i) {
messages::MessageLayout *layout = messagesSnapshot[i].get(); messages::MessageLayout *layout = messagesSnapshot[i].get();
layout->paint(painter, y, i, this->selection); bool isLastMessage = false;
if (singletons::SettingManager::getInstance().showLastMessageIndicator) {
isLastMessage = this->lastReadMessage.get() == layout;
}
layout->paint(painter, y, i, this->selection, isLastMessage, windowFocused);
y += layout->getHeight(); y += layout->getHeight();
@ -551,8 +594,7 @@ void ChannelView::wheelEvent(QWheelEvent *event)
if (i == 0) { if (i == 0) {
desired = 0; desired = 0;
} else { } else {
snapshot[i - 1]->layout(LAYOUT_WIDTH, this->getDpiMultiplier(), snapshot[i - 1]->layout(LAYOUT_WIDTH, this->getScale(), this->getFlags());
this->getFlags());
scrollFactor = 1; scrollFactor = 1;
currentScrollLeft = snapshot[i - 1]->getHeight(); currentScrollLeft = snapshot[i - 1]->getHeight();
} }
@ -574,8 +616,7 @@ void ChannelView::wheelEvent(QWheelEvent *event)
if (i == snapshotLength - 1) { if (i == snapshotLength - 1) {
desired = snapshot.getLength(); desired = snapshot.getLength();
} else { } else {
snapshot[i + 1]->layout(LAYOUT_WIDTH, this->getDpiMultiplier(), snapshot[i + 1]->layout(LAYOUT_WIDTH, this->getScale(), this->getFlags());
this->getFlags());
scrollFactor = 1; scrollFactor = 1;
currentScrollLeft = snapshot[i + 1]->getHeight(); currentScrollLeft = snapshot[i + 1]->getHeight();
@ -782,12 +823,50 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event)
} }
auto &link = hoverLayoutElement->getLink(); auto &link = hoverLayoutElement->getLink();
if (event->button() != Qt::LeftButton ||
!singletons::SettingManager::getInstance().linksDoubleClickOnly) {
this->handleLinkClick(event, link, layout.get());
}
this->linkClicked.invoke(link);
}
void ChannelView::mouseDoubleClickEvent(QMouseEvent *event)
{
if (singletons::SettingManager::getInstance().linksDoubleClickOnly) {
std::shared_ptr<messages::MessageLayout> layout;
QPoint relativePos;
int messageIndex;
if (!tryGetMessageAt(event->pos(), layout, relativePos, messageIndex)) {
return;
}
// message under cursor is collapsed
if (layout->getFlags() & MessageLayout::Collapsed) {
return;
}
const messages::MessageLayoutElement *hoverLayoutElement =
layout->getElementAt(relativePos);
if (hoverLayoutElement == nullptr) {
return;
}
auto &link = hoverLayoutElement->getLink();
this->handleLinkClick(event, link, layout.get());
}
}
void ChannelView::handleLinkClick(QMouseEvent *event, const messages::Link &link,
messages::MessageLayout *layout)
{
switch (link.getType()) { switch (link.getType()) {
case messages::Link::UserInfo: { case messages::Link::UserInfo: {
auto user = link.getValue(); auto user = link.getValue();
this->userPopupWidget.setName(user); this->userPopupWidget.setName(user);
this->userPopupWidget.move(event->screenPos().toPoint()); this->userPopupWidget.moveTo(this, event->screenPos().toPoint());
this->userPopupWidget.show(); this->userPopupWidget.show();
this->userPopupWidget.setFocus(); this->userPopupWidget.setFocus();
@ -795,7 +874,24 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event)
break; break;
} }
case messages::Link::Url: { case messages::Link::Url: {
if (event->button() == Qt::RightButton) {
static QMenu *menu = nullptr;
static QString url;
if (menu == nullptr) {
menu = new QMenu;
menu->addAction("Open in browser",
[] { QDesktopServices::openUrl(QUrl(url)); });
menu->addAction("Copy to clipboard",
[] { QApplication::clipboard()->setText(url); });
}
url = link.getValue();
menu->move(QCursor::pos());
menu->show();
} else {
QDesktopServices::openUrl(QUrl(link.getValue())); QDesktopServices::openUrl(QUrl(link.getValue()));
}
break; break;
} }
case messages::Link::UserAction: { case messages::Link::UserAction: {

View file

@ -38,9 +38,12 @@ public:
void clearSelection(); void clearSelection();
void setEnableScrollingToBottom(bool); void setEnableScrollingToBottom(bool);
bool getEnableScrollingToBottom() const; bool getEnableScrollingToBottom() const;
void setOverrideFlags(boost::optional<messages::MessageElement::Flags> value);
const boost::optional<messages::MessageElement::Flags> &getOverrideFlags() const;
void pause(int msecTimeout); void pause(int msecTimeout);
void updateLastReadMessage();
void setChannel(SharedChannel channel); void setChannel(ChannelPtr channel);
messages::LimitedQueueSnapshot<messages::MessageLayoutPtr> getMessagesSnapshot(); messages::LimitedQueueSnapshot<messages::MessageLayoutPtr> getMessagesSnapshot();
void layoutMessages(); void layoutMessages();
@ -49,8 +52,11 @@ public:
boost::signals2::signal<void(QMouseEvent *)> mouseDown; boost::signals2::signal<void(QMouseEvent *)> mouseDown;
boost::signals2::signal<void()> selectionChanged; boost::signals2::signal<void()> selectionChanged;
pajlada::Signals::NoArgSignal highlightedMessageReceived; pajlada::Signals::NoArgSignal highlightedMessageReceived;
pajlada::Signals::Signal<const messages::Link &> linkClicked;
protected: protected:
virtual void themeRefreshEvent() override;
virtual void resizeEvent(QResizeEvent *) override; virtual void resizeEvent(QResizeEvent *) override;
virtual void paintEvent(QPaintEvent *) override; virtual void paintEvent(QPaintEvent *) override;
@ -62,6 +68,10 @@ protected:
virtual void mouseMoveEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override;
virtual void mousePressEvent(QMouseEvent *event) override; virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseReleaseEvent(QMouseEvent *event) override; virtual void mouseReleaseEvent(QMouseEvent *event) override;
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
void handleLinkClick(QMouseEvent *event, const messages::Link &link,
messages::MessageLayout *layout);
bool tryGetMessageAt(QPoint p, std::shared_ptr<messages::MessageLayout> &message, bool tryGetMessageAt(QPoint p, std::shared_ptr<messages::MessageLayout> &message,
QPoint &relativePos, int &index); QPoint &relativePos, int &index);
@ -72,6 +82,8 @@ private:
bool messageWasAdded = false; bool messageWasAdded = false;
bool paused = false; bool paused = false;
QTimer pauseTimeout; QTimer pauseTimeout;
boost::optional<messages::MessageElement::Flags> overrideFlags;
messages::MessageLayoutPtr lastReadMessage;
messages::LimitedQueueSnapshot<messages::MessageLayoutPtr> snapshot; messages::LimitedQueueSnapshot<messages::MessageLayoutPtr> snapshot;
@ -82,7 +94,7 @@ private:
void setSelection(const messages::SelectionItem &start, const messages::SelectionItem &end); void setSelection(const messages::SelectionItem &start, const messages::SelectionItem &end);
messages::MessageElement::Flags getFlags() const; messages::MessageElement::Flags getFlags() const;
SharedChannel channel; ChannelPtr channel;
Scrollbar scrollBar; Scrollbar scrollBar;
RippleEffectLabel *goToBottom; RippleEffectLabel *goToBottom;

View file

@ -0,0 +1,78 @@
#include "label.hpp"
#include "singletons/fontmanager.hpp"
#include <QPainter>
namespace chatterino {
namespace widgets {
Label::Label(BaseWidget *parent)
: BaseWidget(parent)
{
singletons::FontManager::getInstance().fontChanged.connect(
[this]() { this->scaleChangedEvent(this->getScale()); });
}
const QString &Label::getText() const
{
return this->text;
}
void Label::setText(const QString &value)
{
this->text = value;
this->scaleChangedEvent(this->getScale());
}
FontStyle Label::getFontStyle() const
{
return this->fontStyle;
}
void Label::setFontStyle(FontStyle style)
{
this->fontStyle = style;
this->scaleChangedEvent(this->getScale());
}
void Label::scaleChangedEvent(float scale)
{
QFontMetrics metrics =
singletons::FontManager::getInstance().getFontMetrics(this->fontStyle, scale);
this->preferedSize = QSize(metrics.width(this->text), metrics.height());
this->updateGeometry();
}
QSize Label::sizeHint() const
{
return this->preferedSize;
}
QSize Label::minimumSizeHint() const
{
return this->preferedSize;
}
void Label::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setFont(singletons::FontManager::getInstance().getFont(
this->fontStyle, this->getScale() / painter.device()->devicePixelRatioF()));
int width = singletons::FontManager::getInstance()
.getFontMetrics(this->fontStyle, this->getScale())
.width(this->text);
int flags = Qt::TextSingleLine;
if (this->width() < width) {
flags |= Qt::AlignLeft | Qt::AlignVCenter;
} else {
flags |= Qt::AlignCenter;
}
painter.drawText(this->rect(), flags, this->text);
}
} // namespace widgets
} // namespace chatterino

View file

@ -0,0 +1,33 @@
#pragma once
#include "singletons/fontmanager.hpp"
#include "widgets/basewidget.hpp"
namespace chatterino {
namespace widgets {
class Label : public BaseWidget
{
public:
Label(BaseWidget *parent);
const QString &getText() const;
void setText(const QString &text);
FontStyle getFontStyle() const;
void setFontStyle(FontStyle style);
protected:
virtual void scaleChangedEvent(float scale) override;
virtual void paintEvent(QPaintEvent *event) override;
virtual QSize sizeHint() const override;
virtual QSize minimumSizeHint() const override;
private:
QSize preferedSize;
QString text;
FontStyle fontStyle = FontStyle::Medium;
};
} // namespace widgets
} // namespace chatterino

View file

@ -1,12 +1,16 @@
#include "widgets/helper/notebookbutton.hpp" #include "widgets/helper/notebookbutton.hpp"
#include "singletons/thememanager.hpp" #include "singletons/thememanager.hpp"
#include "widgets/helper/rippleeffectbutton.hpp" #include "widgets/helper/rippleeffectbutton.hpp"
#include "widgets/notebook.hpp"
#include "widgets/splitcontainer.hpp"
#include <QMouseEvent> #include <QMouseEvent>
#include <QPainter> #include <QPainter>
#include <QPainterPath> #include <QPainterPath>
#include <QRadialGradient> #include <QRadialGradient>
#define nuuls nullptr
namespace chatterino { namespace chatterino {
namespace widgets { namespace widgets {
@ -14,6 +18,8 @@ NotebookButton::NotebookButton(BaseWidget *parent)
: RippleEffectButton(parent) : RippleEffectButton(parent)
{ {
setMouseEffectColor(QColor(0, 0, 0)); setMouseEffectColor(QColor(0, 0, 0));
this->setAcceptDrops(true);
} }
void NotebookButton::paintEvent(QPaintEvent *) void NotebookButton::paintEvent(QPaintEvent *)
@ -97,5 +103,46 @@ void NotebookButton::mouseReleaseEvent(QMouseEvent *event)
RippleEffectButton::mouseReleaseEvent(event); RippleEffectButton::mouseReleaseEvent(event);
} }
void NotebookButton::dragEnterEvent(QDragEnterEvent *event)
{
if (!event->mimeData()->hasFormat("chatterino/split"))
return;
event->acceptProposedAction();
auto e = new QMouseEvent(QMouseEvent::MouseButtonPress,
QPointF(this->width() / 2, this->height() / 2), Qt::LeftButton,
Qt::LeftButton, 0);
RippleEffectButton::mousePressEvent(e);
delete e;
}
void NotebookButton::dragLeaveEvent(QDragLeaveEvent *)
{
this->mouseDown = true;
this->update();
auto e = new QMouseEvent(QMouseEvent::MouseButtonRelease,
QPointF(this->width() / 2, this->height() / 2), Qt::LeftButton,
Qt::LeftButton, 0);
RippleEffectButton::mouseReleaseEvent(e);
delete e;
}
void NotebookButton::dropEvent(QDropEvent *event)
{
if (SplitContainer::isDraggingSplit) {
event->acceptProposedAction();
Notebook *notebook = dynamic_cast<Notebook *>(this->parentWidget());
if (notebook != nuuls) {
SplitContainer *tab = notebook->addNewPage();
SplitContainer::draggingSplit->setParent(tab);
tab->addToLayout(SplitContainer::draggingSplit);
}
}
}
} // namespace widgets } // namespace widgets
} // namespace chatterino } // namespace chatterino

View file

@ -21,8 +21,11 @@ public:
NotebookButton(BaseWidget *parent); NotebookButton(BaseWidget *parent);
protected: protected:
void paintEvent(QPaintEvent *) override; virtual void paintEvent(QPaintEvent *) override;
void mouseReleaseEvent(QMouseEvent *event) override; virtual void mouseReleaseEvent(QMouseEvent *) override;
virtual void dragEnterEvent(QDragEnterEvent *) override;
virtual void dragLeaveEvent(QDragLeaveEvent *) override;
virtual void dropEvent(QDropEvent *) override;
signals: signals:
void clicked(); void clicked();

View file

@ -25,7 +25,6 @@ NotebookTab::NotebookTab(Notebook *_notebook, const std::string &_uuid)
, useDefaultBehaviour(fS("{}/useDefaultBehaviour", this->settingRoot), true) , useDefaultBehaviour(fS("{}/useDefaultBehaviour", this->settingRoot), true)
, menu(this) , menu(this)
{ {
this->calcSize();
this->setAcceptDrops(true); this->setAcceptDrops(true);
this->positionChangedAnimation.setEasingCurve(QEasingCurve(QEasingCurve::InCubic)); this->positionChangedAnimation.setEasingCurve(QEasingCurve(QEasingCurve::InCubic));
@ -65,22 +64,25 @@ NotebookTab::NotebookTab(Notebook *_notebook, const std::string &_uuid)
this->menu.addAction(enableHighlightsOnNewMessageAction); this->menu.addAction(enableHighlightsOnNewMessageAction);
connect(enableHighlightsOnNewMessageAction, &QAction::toggled, [this](bool newValue) { QObject::connect(enableHighlightsOnNewMessageAction, &QAction::toggled, [this](bool newValue) {
debug::Log("New value is {}", newValue); // debug::Log("New value is {}", newValue); //
}); });
} }
void NotebookTab::calcSize() void NotebookTab::themeRefreshEvent()
{ {
float scale = getDpiMultiplier(); this->update();
}
void NotebookTab::updateSize()
{
float scale = getScale();
QString qTitle(qS(this->title)); QString qTitle(qS(this->title));
if (singletons::SettingManager::getInstance().hideTabX) { if (singletons::SettingManager::getInstance().hideTabX) {
this->resize(static_cast<int>((fontMetrics().width(qTitle) + 16) * scale), this->resize((int)((fontMetrics().width(qTitle) + 16) * scale), (int)(24 * scale));
static_cast<int>(24 * scale));
} else { } else {
this->resize(static_cast<int>((fontMetrics().width(qTitle) + 8 + 24) * scale), this->resize((int)((fontMetrics().width(qTitle) + 8 + 24) * scale), (int)(24 * scale));
static_cast<int>(24 * scale));
} }
if (this->parent() != nullptr) { if (this->parent() != nullptr) {
@ -97,7 +99,7 @@ void NotebookTab::setTitle(const QString &newTitle)
{ {
this->title = newTitle.toStdString(); this->title = newTitle.toStdString();
this->calcSize(); this->updateSize();
} }
bool NotebookTab::isSelected() const bool NotebookTab::isSelected() const
@ -134,7 +136,7 @@ QRect NotebookTab::getDesiredRect() const
void NotebookTab::hideTabXChanged(bool) void NotebookTab::hideTabXChanged(bool)
{ {
this->calcSize(); this->updateSize();
this->update(); this->update();
} }
@ -197,7 +199,7 @@ void NotebookTab::paintEvent(QPaintEvent *)
painter.setPen(colors.text); painter.setPen(colors.text);
// set area for text // set area for text
float scale = this->getDpiMultiplier(); float scale = this->getScale();
int rectW = (settingManager.hideTabX ? 0 : static_cast<int>(16) * scale); int rectW = (settingManager.hideTabX ? 0 : static_cast<int>(16) * scale);
QRect rect(0, 0, this->width() - rectW, this->height()); QRect rect(0, 0, this->width() - rectW, this->height());
@ -291,14 +293,14 @@ void NotebookTab::mouseMoveEvent(QMouseEvent *event)
} }
} }
if (this->mouseDown && !this->getDesiredRect().contains(event->pos())) {
QPoint relPoint = this->mapToParent(event->pos()); QPoint relPoint = this->mapToParent(event->pos());
if (this->mouseDown && !this->getDesiredRect().contains(relPoint)) {
int index; int index;
SplitContainer *clickedPage = notebook->tabAt(relPoint, index); SplitContainer *clickedPage = notebook->tabAt(relPoint, index, this->width());
if (clickedPage != nullptr && clickedPage != this->page) { if (clickedPage != nullptr && clickedPage != this->page) {
this->notebook->rearrangePage(clickedPage, index); this->notebook->rearrangePage(this->page, index);
} }
} }
} }

View file

@ -25,7 +25,7 @@ class NotebookTab : public BaseWidget
public: public:
explicit NotebookTab(Notebook *_notebook, const std::string &_uuid); explicit NotebookTab(Notebook *_notebook, const std::string &_uuid);
void calcSize(); void updateSize();
SplitContainer *page; SplitContainer *page;
@ -42,16 +42,18 @@ public:
void hideTabXChanged(bool); void hideTabXChanged(bool);
protected: protected:
void paintEvent(QPaintEvent *) override; virtual void themeRefreshEvent() override;
void mousePressEvent(QMouseEvent *event) override; virtual void paintEvent(QPaintEvent *) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void enterEvent(QEvent *) override;
void leaveEvent(QEvent *) override;
void dragEnterEvent(QDragEnterEvent *event) override; virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseReleaseEvent(QMouseEvent *event) override;
virtual void enterEvent(QEvent *) override;
virtual void leaveEvent(QEvent *) override;
void mouseMoveEvent(QMouseEvent *event) override; virtual void dragEnterEvent(QDragEnterEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
private: private:
std::vector<pajlada::Signals::ScopedConnection> managedConnections; std::vector<pajlada::Signals::ScopedConnection> managedConnections;
@ -80,7 +82,7 @@ private:
QRect getXRect() QRect getXRect()
{ {
float scale = this->getDpiMultiplier(); float scale = this->getScale();
return QRect(this->width() - static_cast<int>(20 * scale), static_cast<int>(4 * scale), return QRect(this->width() - static_cast<int>(20 * scale), static_cast<int>(4 * scale),
static_cast<int>(16 * scale), static_cast<int>(16 * scale)); static_cast<int>(16 * scale), static_cast<int>(16 * scale));
} }

View file

@ -44,7 +44,7 @@ void RippleEffectButton::paintEvent(QPaintEvent *)
if (this->pixmap != nullptr) { if (this->pixmap != nullptr) {
QRect rect = this->rect(); QRect rect = this->rect();
int xD = 6 * this->getDpiMultiplier(); int xD = 6 * this->getScale();
rect.moveLeft(xD); rect.moveLeft(xD);
rect.setRight(rect.right() - xD - xD); rect.setRight(rect.right() - xD - xD);
@ -57,6 +57,8 @@ void RippleEffectButton::paintEvent(QPaintEvent *)
void RippleEffectButton::fancyPaint(QPainter &painter) void RippleEffectButton::fancyPaint(QPainter &painter)
{ {
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.setRenderHint(QPainter::Antialiasing);
QColor c; QColor c;
if (this->mouseEffectColor) { if (this->mouseEffectColor) {

View file

@ -58,7 +58,7 @@ void SearchPopup::initLayout()
} }
} }
void SearchPopup::setChannel(SharedChannel channel) void SearchPopup::setChannel(ChannelPtr channel)
{ {
this->snapshot = channel->getMessageSnapshot(); this->snapshot = channel->getMessageSnapshot();
this->performSearch(); this->performSearch();
@ -70,7 +70,7 @@ void SearchPopup::performSearch()
{ {
QString text = searchInput->text(); QString text = searchInput->text();
SharedChannel channel(new Channel("search")); ChannelPtr channel(new Channel("search"));
for (size_t i = 0; i < this->snapshot.getLength(); i++) { for (size_t i = 0; i < this->snapshot.getLength(); i++) {
messages::MessagePtr message = this->snapshot[i]; messages::MessagePtr message = this->snapshot[i];

View file

@ -10,7 +10,8 @@ namespace widgets {
SettingsDialogTab::SettingsDialogTab(SettingsDialog *_dialog, settingspages::SettingsPage *_page, SettingsDialogTab::SettingsDialogTab(SettingsDialog *_dialog, settingspages::SettingsPage *_page,
QString imageFileName) QString imageFileName)
: dialog(_dialog) : BaseWidget(_dialog)
, dialog(_dialog)
, page(_page) , page(_page)
{ {
this->ui.labelText = page->getName(); this->ui.labelText = page->getName();
@ -47,8 +48,8 @@ void SettingsDialogTab::paintEvent(QPaintEvent *)
this->style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); this->style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
int a = (this->height() - 20) / 2; int a = (this->height() - (20 * this->getScale())) / 2;
QPixmap pixmap = this->ui.icon.pixmap(QSize(20, 20)); QPixmap pixmap = this->ui.icon.pixmap(QSize(this->height() - a * 2, this->height() - a * 2));
painter.drawPixmap(a, a, pixmap); painter.drawPixmap(a, a, pixmap);

View file

@ -4,6 +4,8 @@
#include <QPaintEvent> #include <QPaintEvent>
#include <QWidget> #include <QWidget>
#include "widgets/basewidget.hpp"
namespace chatterino { namespace chatterino {
namespace widgets { namespace widgets {
namespace settingspages { namespace settingspages {
@ -12,7 +14,7 @@ class SettingsPage;
class SettingsDialog; class SettingsDialog;
class SettingsDialogTab : public QWidget class SettingsDialogTab : public BaseWidget
{ {
Q_OBJECT Q_OBJECT

View file

@ -4,6 +4,7 @@
#include "twitch/twitchchannel.hpp" #include "twitch/twitchchannel.hpp"
#include "util/layoutcreator.hpp" #include "util/layoutcreator.hpp"
#include "util/urlfetch.hpp" #include "util/urlfetch.hpp"
#include "widgets/helper/label.hpp"
#include "widgets/split.hpp" #include "widgets/split.hpp"
#include "widgets/splitcontainer.hpp" #include "widgets/splitcontainer.hpp"
#include "widgets/tooltipwidget.hpp" #include "widgets/tooltipwidget.hpp"
@ -47,7 +48,9 @@ SplitHeader::SplitHeader(Split *_split)
layout->addStretch(1); layout->addStretch(1);
// channel name label // channel name label
// auto title = layout.emplace<Label>(this).assign(&this->titleLabel);
auto title = layout.emplace<SignalLabel>().assign(&this->titleLabel); auto title = layout.emplace<SignalLabel>().assign(&this->titleLabel);
title->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
title->setMouseTracking(true); title->setMouseTracking(true);
QObject::connect(this->titleLabel, &SignalLabel::mouseDoubleClick, this, QObject::connect(this->titleLabel, &SignalLabel::mouseDoubleClick, this,
&SplitHeader::mouseDoubleClickEvent); &SplitHeader::mouseDoubleClickEvent);
@ -66,7 +69,8 @@ SplitHeader::SplitHeader(Split *_split)
// ---- misc // ---- misc
this->layout()->setMargin(0); this->layout()->setMargin(0);
this->refreshTheme(); this->themeRefreshEvent();
this->scaleChangedEvent(this->getScale());
this->updateChannelText(); this->updateChannelText();
@ -93,7 +97,7 @@ void SplitHeader::addDropdownItems(RippleEffectButton *label)
this->dropdownMenu.addSeparator(); this->dropdownMenu.addSeparator();
#ifdef USEWEBENGINE #ifdef USEWEBENGINE
this->dropdownMenu.addAction("Start watching", this, [this]{ this->dropdownMenu.addAction("Start watching", this, [this]{
SharedChannel _channel = this->split->getChannel(); ChannelPtr _channel = this->split->getChannel();
twitch::TwitchChannel *tc = dynamic_cast<twitch::TwitchChannel *>(_channel.get()); twitch::TwitchChannel *tc = dynamic_cast<twitch::TwitchChannel *>(_channel.get());
if (tc != nullptr) { if (tc != nullptr) {
@ -133,13 +137,15 @@ void SplitHeader::initializeChannelSignals()
} }
} }
void SplitHeader::resizeEvent(QResizeEvent *event) void SplitHeader::scaleChangedEvent(float scale)
{ {
int w = 28 * getDpiMultiplier(); int w = 28 * scale;
this->setFixedHeight(w); this->setFixedHeight(w);
this->dropdownButton->setFixedWidth(w); this->dropdownButton->setFixedWidth(w);
this->moderationButton->setFixedWidth(w); this->moderationButton->setFixedWidth(w);
// this->titleLabel->setFont(
// singletons::FontManager::getInstance().getFont(FontStyle::Medium, scale));
} }
void SplitHeader::updateChannelText() void SplitHeader::updateChannelText()
@ -180,7 +186,7 @@ void SplitHeader::updateModerationModeIcon()
: resourceManager.moderationmode_disabled->getPixmap()); : resourceManager.moderationmode_disabled->getPixmap());
bool modButtonVisible = false; bool modButtonVisible = false;
SharedChannel channel = this->split->getChannel(); ChannelPtr channel = this->split->getChannel();
twitch::TwitchChannel *tc = dynamic_cast<twitch::TwitchChannel *>(channel.get()); twitch::TwitchChannel *tc = dynamic_cast<twitch::TwitchChannel *>(channel.get());
@ -242,7 +248,7 @@ void SplitHeader::rightButtonClicked()
{ {
} }
void SplitHeader::refreshTheme() void SplitHeader::themeRefreshEvent()
{ {
QPalette palette; QPalette palette;
palette.setColor(QPalette::Foreground, this->themeManager.splits.header.text); palette.setColor(QPalette::Foreground, this->themeManager.splits.header.text);

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "widgets/basewidget.hpp" #include "widgets/basewidget.hpp"
#include "widgets/helper/label.hpp"
#include "widgets/helper/rippleeffectlabel.hpp" #include "widgets/helper/rippleeffectlabel.hpp"
#include "widgets/helper/signallabel.hpp" #include "widgets/helper/signallabel.hpp"
@ -34,12 +35,14 @@ public:
void updateModerationModeIcon(); void updateModerationModeIcon();
protected: protected:
virtual void scaleChangedEvent(float) override;
virtual void themeRefreshEvent() override;
virtual void paintEvent(QPaintEvent *) override; virtual void paintEvent(QPaintEvent *) override;
virtual void mousePressEvent(QMouseEvent *event) override; virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override;
virtual void leaveEvent(QEvent *event) override; virtual void leaveEvent(QEvent *event) override;
virtual void mouseDoubleClickEvent(QMouseEvent *event) override; virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
virtual void resizeEvent(QResizeEvent *event) override;
private: private:
Split *const split; Split *const split;
@ -50,6 +53,7 @@ private:
boost::signals2::connection onlineStatusChangedConnection; boost::signals2::connection onlineStatusChangedConnection;
RippleEffectButton *dropdownButton; RippleEffectButton *dropdownButton;
// Label *titleLabel;
SignalLabel *titleLabel; SignalLabel *titleLabel;
RippleEffectButton *moderationButton; RippleEffectButton *moderationButton;
@ -57,8 +61,6 @@ private:
void rightButtonClicked(); void rightButtonClicked();
virtual void refreshTheme() override;
void initializeChannelSignals(); void initializeChannelSignals();
QString tooltip; QString tooltip;

View file

@ -4,6 +4,7 @@
#include "singletons/ircmanager.hpp" #include "singletons/ircmanager.hpp"
#include "singletons/settingsmanager.hpp" #include "singletons/settingsmanager.hpp"
#include "singletons/thememanager.hpp" #include "singletons/thememanager.hpp"
#include "util/layoutcreator.hpp"
#include "widgets/notebook.hpp" #include "widgets/notebook.hpp"
#include "widgets/split.hpp" #include "widgets/split.hpp"
#include "widgets/splitcontainer.hpp" #include "widgets/splitcontainer.hpp"
@ -17,68 +18,124 @@ namespace widgets {
SplitInput::SplitInput(Split *_chatWidget) SplitInput::SplitInput(Split *_chatWidget)
: BaseWidget(_chatWidget) : BaseWidget(_chatWidget)
, chatWidget(_chatWidget) , chatWidget(_chatWidget)
, emotesLabel(this)
{ {
this->setLayout(&this->hbox); this->initLayout();
this->hbox.setMargin(4); // auto completion
auto completer = new QCompleter(
singletons::CompletionManager::getInstance().createModel(this->chatWidget->channelName));
this->hbox.addLayout(&this->editContainer); this->ui.textEdit->setCompleter(completer);
this->hbox.addLayout(&this->vbox);
auto &fontManager = singletons::FontManager::getInstance(); // misc
this->installKeyPressedEvent();
this->textInput.setFont( this->themeRefreshEvent();
fontManager.getFont(singletons::FontManager::Type::Medium, this->getDpiMultiplier())); this->scaleChangedEvent(this->getScale());
this->managedConnections.emplace_back(fontManager.fontChanged.connect([this, &fontManager]() {
this->textInput.setFont(
fontManager.getFont(singletons::FontManager::Type::Medium, this->getDpiMultiplier()));
}));
this->editContainer.addWidget(&this->textInput);
this->editContainer.setMargin(2);
this->emotesLabel.setMinimumHeight(24);
this->vbox.addWidget(&this->textLengthLabel);
this->vbox.addStretch(1);
this->vbox.addWidget(&this->emotesLabel);
this->textLengthLabel.setText("");
this->textLengthLabel.setAlignment(Qt::AlignRight);
this->emotesLabel.getLabel().setTextFormat(Qt::RichText);
this->emotesLabel.getLabel().setText("<img src=':/images/emote.svg' width='12' height='12' "
"/>");
connect(&this->emotesLabel, &RippleEffectLabel::clicked, [this] {
if (this->emotePopup == nullptr) {
this->emotePopup = new EmotePopup(this->themeManager);
} }
this->emotePopup->resize((int)(300 * this->emotePopup->getDpiMultiplier()), void SplitInput::initLayout()
(int)(500 * this->emotePopup->getDpiMultiplier())); {
auto &fontManager = singletons::FontManager::getInstance();
util::LayoutCreator<SplitInput> layoutCreator(this);
auto layout = layoutCreator.setLayoutType<QHBoxLayout>().withoutMargin().assign(&this->ui.hbox);
// input
auto textEdit = layout.emplace<ResizingTextEdit>().assign(&this->ui.textEdit);
connect(textEdit.getElement(), &ResizingTextEdit::textChanged, this,
&SplitInput::editTextChanged);
// right box
auto box = layout.emplace<QVBoxLayout>().withoutMargin();
box->setSpacing(0);
{
auto textEditLength = box.emplace<QLabel>().assign(&this->ui.textEditLength);
textEditLength->setAlignment(Qt::AlignRight);
box->addStretch(1);
box.emplace<RippleEffectLabel>().assign(&this->ui.emoteButton);
}
this->ui.emoteButton->getLabel().setTextFormat(Qt::RichText);
// ---- misc
// set edit font
this->ui.textEdit->setFont(
fontManager.getFont(singletons::FontManager::Type::Medium, this->getScale()));
this->managedConnections.emplace_back(fontManager.fontChanged.connect([this, &fontManager]() {
this->ui.textEdit->setFont(
fontManager.getFont(singletons::FontManager::Type::Medium, this->getScale()));
}));
// open emote popup
QObject::connect(this->ui.emoteButton, &RippleEffectLabel::clicked, [this] {
if (!this->emotePopup) {
this->emotePopup = std::make_unique<EmotePopup>(this->themeManager);
this->emotePopup->linkClicked.connect([this](const messages::Link &link) {
if (link.getType() == messages::Link::InsertText) {
this->insertText(link.getValue() + " ");
}
});
}
this->emotePopup->resize((int)(300 * this->emotePopup->getScale()),
(int)(500 * this->emotePopup->getScale()));
this->emotePopup->loadChannel(this->chatWidget->getChannel()); this->emotePopup->loadChannel(this->chatWidget->getChannel());
this->emotePopup->show(); this->emotePopup->show();
}); });
connect(&textInput, &ResizingTextEdit::textChanged, this, &SplitInput::editTextChanged); // clear channelview selection when selecting in the input
QObject::connect(this->ui.textEdit, &QTextEdit::copyAvailable, [this](bool available) {
if (available) {
this->chatWidget->view.clearSelection();
}
});
this->refreshTheme(); // textEditLength visibility
textLengthLabel.setHidden(!singletons::SettingManager::getInstance().showMessageLength); singletons::SettingManager::getInstance().showMessageLength.connect(
[this](const bool &value, auto) { this->ui.textEditLength->setHidden(!value); },
this->managedConnections);
}
auto completer = new QCompleter( void SplitInput::scaleChangedEvent(float scale)
singletons::CompletionManager::getInstance().createModel(this->chatWidget->channelName)); {
// update the icon size of the emote button
QString text = "<img src=':/images/emote.svg' width='xD' height='xD' />";
text.replace("xD", QString::number((int)12 * scale));
this->textInput.setCompleter(completer); this->ui.emoteButton->getLabel().setText(text);
this->ui.emoteButton->setFixedHeight((int)18 * scale);
this->textInput.keyPressed.connect([this](QKeyEvent *event) { // set maximum height
this->setMaximumHeight((int)(150 * this->getScale()));
this->themeRefreshEvent();
}
void SplitInput::themeRefreshEvent()
{
QPalette palette;
palette.setColor(QPalette::Foreground, this->themeManager.splits.input.text);
this->ui.textEditLength->setPalette(palette);
this->ui.textEdit->setStyleSheet(this->themeManager.splits.input.styleSheet);
this->ui.hbox->setMargin((this->themeManager.isLightTheme() ? 4 : 2) * this->getScale());
}
void SplitInput::installKeyPressedEvent()
{
this->ui.textEdit->keyPressed.connect([this](QKeyEvent *event) {
if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) { if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
auto c = this->chatWidget->getChannel(); auto c = this->chatWidget->getChannel();
if (c == nullptr) { if (c == nullptr) {
return; return;
} }
QString message = textInput.toPlainText(); QString message = ui.textEdit->toPlainText();
QString sendMessage = QString sendMessage =
singletons::CommandManager::getInstance().execCommand(message, c, false); singletons::CommandManager::getInstance().execCommand(message, c, false);
@ -89,9 +146,9 @@ SplitInput::SplitInput(Split *_chatWidget)
event->accept(); event->accept();
if (!(event->modifiers() == Qt::ControlModifier)) { if (!(event->modifiers() == Qt::ControlModifier)) {
this->textInput.setText(QString()); this->ui.textEdit->setText(QString());
this->prevIndex = 0; this->prevIndex = 0;
} else if (this->textInput.toPlainText() == } else if (this->ui.textEdit->toPlainText() ==
this->prevMsg.at(this->prevMsg.size() - 1)) { this->prevMsg.at(this->prevMsg.size() - 1)) {
this->prevMsg.removeLast(); this->prevMsg.removeLast();
} }
@ -110,7 +167,7 @@ SplitInput::SplitInput(Split *_chatWidget)
} else { } else {
if (this->prevMsg.size() && this->prevIndex) { if (this->prevMsg.size() && this->prevIndex) {
this->prevIndex--; this->prevIndex--;
this->textInput.setText(this->prevMsg.at(this->prevIndex)); this->ui.textEdit->setText(this->prevMsg.at(this->prevIndex));
} }
} }
} else if (event->key() == Qt::Key_Down) { } else if (event->key() == Qt::Key_Down) {
@ -128,10 +185,10 @@ SplitInput::SplitInput(Split *_chatWidget)
if (this->prevIndex != (this->prevMsg.size() - 1) && if (this->prevIndex != (this->prevMsg.size() - 1) &&
this->prevIndex != this->prevMsg.size()) { this->prevIndex != this->prevMsg.size()) {
this->prevIndex++; this->prevIndex++;
this->textInput.setText(this->prevMsg.at(this->prevIndex)); this->ui.textEdit->setText(this->prevMsg.at(this->prevIndex));
} else { } else {
this->prevIndex = this->prevMsg.size(); this->prevIndex = this->prevMsg.size();
this->textInput.setText(QString()); this->ui.textEdit->setText(QString());
} }
} }
} else if (event->key() == Qt::Key_Left) { } else if (event->key() == Qt::Key_Left) {
@ -183,49 +240,32 @@ SplitInput::SplitInput(Split *_chatWidget)
} }
} }
}); });
singletons::SettingManager::getInstance().showMessageLength.connect(
[this](const bool &value, auto) { this->textLengthLabel.setHidden(!value); },
this->managedConnections);
QObject::connect(&this->textInput, &QTextEdit::copyAvailable, [this](bool available) {
if (available) {
this->chatWidget->view.clearSelection();
}
});
} }
void SplitInput::clearSelection() void SplitInput::clearSelection()
{ {
QTextCursor c = this->textInput.textCursor(); QTextCursor c = this->ui.textEdit->textCursor();
c.setPosition(c.position()); c.setPosition(c.position());
c.setPosition(c.position(), QTextCursor::KeepAnchor); c.setPosition(c.position(), QTextCursor::KeepAnchor);
this->textInput.setTextCursor(c); this->ui.textEdit->setTextCursor(c);
} }
QString SplitInput::getInputText() const QString SplitInput::getInputText() const
{ {
return this->textInput.toPlainText(); return this->ui.textEdit->toPlainText();
} }
void SplitInput::refreshTheme() void SplitInput::insertText(const QString &text)
{ {
QPalette palette; this->ui.textEdit->insertPlainText(text);
palette.setColor(QPalette::Foreground, this->themeManager.splits.input.text);
this->textLengthLabel.setPalette(palette);
this->textInput.setStyleSheet(this->themeManager.splits.input.styleSheet);
this->hbox.setMargin((this->themeManager.isLightTheme() ? 4 : 2) * this->getDpiMultiplier());
} }
void SplitInput::editTextChanged() void SplitInput::editTextChanged()
{ {
QString text = this->textInput.toPlainText(); // set textLengthLabel value
QString text = this->ui.textEdit->toPlainText();
this->textChanged.invoke(text); this->textChanged.invoke(text);
@ -244,7 +284,7 @@ void SplitInput::editTextChanged()
labelText = QString::number(text.length()); labelText = QString::number(text.length());
} }
this->textLengthLabel.setText(labelText); this->ui.textEditLength->setText(labelText);
} }
void SplitInput::paintEvent(QPaintEvent *) void SplitInput::paintEvent(QPaintEvent *)
@ -255,7 +295,7 @@ void SplitInput::paintEvent(QPaintEvent *)
QPen pen(this->themeManager.splits.input.border); QPen pen(this->themeManager.splits.input.border);
if (this->themeManager.isLightTheme()) { if (this->themeManager.isLightTheme()) {
pen.setWidth((int)(6 * this->getDpiMultiplier())); pen.setWidth((int)(6 * this->getScale()));
} }
painter.setPen(pen); painter.setPen(pen);
painter.drawRect(0, 0, this->width() - 1, this->height() - 1); painter.drawRect(0, 0, this->width() - 1, this->height() - 1);
@ -264,14 +304,10 @@ void SplitInput::paintEvent(QPaintEvent *)
void SplitInput::resizeEvent(QResizeEvent *) void SplitInput::resizeEvent(QResizeEvent *)
{ {
if (this->height() == this->maximumHeight()) { if (this->height() == this->maximumHeight()) {
this->textInput.setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); this->ui.textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
} else { } else {
this->textInput.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); this->ui.textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
} }
this->setMaximumHeight((int)(150 * this->getDpiMultiplier()));
this->refreshTheme();
} }
void SplitInput::mousePressEvent(QMouseEvent *) void SplitInput::mousePressEvent(QMouseEvent *)

View file

@ -27,10 +27,13 @@ public:
void clearSelection(); void clearSelection();
QString getInputText() const; QString getInputText() const;
void insertText(const QString &text);
pajlada::Signals::Signal<const QString &> textChanged; pajlada::Signals::Signal<const QString &> textChanged;
protected: protected:
virtual void scaleChangedEvent(float scale) override;
virtual void paintEvent(QPaintEvent *) override; virtual void paintEvent(QPaintEvent *) override;
virtual void resizeEvent(QResizeEvent *) override; virtual void resizeEvent(QResizeEvent *) override;
@ -38,18 +41,29 @@ protected:
private: private:
Split *const chatWidget; Split *const chatWidget;
EmotePopup *emotePopup = nullptr; std::unique_ptr<EmotePopup> emotePopup;
struct {
ResizingTextEdit *textEdit;
QLabel *textEditLength;
RippleEffectLabel *emoteButton;
QHBoxLayout *hbox;
} ui;
std::vector<pajlada::Signals::ScopedConnection> managedConnections; std::vector<pajlada::Signals::ScopedConnection> managedConnections;
QHBoxLayout hbox; // QHBoxLayout hbox;
QVBoxLayout vbox; // QVBoxLayout vbox;
QHBoxLayout editContainer; // QHBoxLayout editContainer;
ResizingTextEdit textInput; // ResizingTextEdit textInput;
QLabel textLengthLabel; // QLabel textLengthLabel;
RippleEffectLabel emotesLabel; // RippleEffectLabel emotesLabel;
QStringList prevMsg; QStringList prevMsg;
int prevIndex = 0; int prevIndex = 0;
virtual void refreshTheme() override;
void initLayout();
void installKeyPressedEvent();
virtual void themeRefreshEvent() override;
private slots: private slots:
void editTextChanged(); void editTextChanged();

View file

@ -0,0 +1,118 @@
#include "titlebarbutton.hpp"
namespace chatterino {
namespace widgets {
TitleBarButton::TitleBarButton()
: RippleEffectButton(nullptr)
{
}
TitleBarButton::Style TitleBarButton::getButtonStyle() const
{
return this->style;
}
void TitleBarButton::setButtonStyle(Style _style)
{
this->style = _style;
this->update();
}
void TitleBarButton::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QColor color = "#000";
QColor background = "#fff";
int xD = this->height() / 3;
int centerX = this->width() / 2;
painter.setRenderHint(QPainter::Antialiasing, false);
switch (this->style) {
case Minimize: {
painter.fillRect(centerX - xD / 2, xD * 3 / 2, xD, 1, color);
break;
}
case Maximize: {
painter.setPen(color);
painter.drawRect(centerX - xD / 2, xD, xD - 1, xD - 1);
break;
}
case Unmaximize: {
int xD2 = xD * 1 / 5;
int xD3 = xD * 4 / 5;
painter.drawRect(centerX - xD / 2 + xD2, xD, xD3, xD3);
painter.fillRect(centerX - xD / 2, xD + xD2, xD3, xD3, QColor("#fff"));
painter.drawRect(centerX - xD / 2, xD + xD2, xD3, xD3);
break;
}
case Close: {
QRect rect(centerX - xD / 2, xD, xD - 1, xD - 1);
painter.setPen(QPen(color, 1));
painter.drawLine(rect.topLeft(), rect.bottomRight());
painter.drawLine(rect.topRight(), rect.bottomLeft());
break;
}
case User: {
color = QColor("#333");
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
auto a = xD / 3;
QPainterPath path;
painter.save();
painter.translate(3, 3);
path.arcMoveTo(a, 4 * a, 6 * a, 6 * a, 0);
path.arcTo(a, 4 * a, 6 * a, 6 * a, 0, 180);
painter.fillPath(path, color);
painter.setBrush(background);
painter.drawEllipse(2 * a, 1 * a, 4 * a, 4 * a);
painter.setBrush(color);
painter.drawEllipse(2.5 * a, 1.5 * a, 3 * a + 1, 3 * a);
painter.restore();
break;
}
case Settings: {
color = QColor("#333");
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.save();
painter.translate(3, 3);
auto a = xD / 3;
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.strokePath(path, color);
painter.fillPath(path, color);
painter.setBrush(background);
painter.drawEllipse(3 * a, 3 * a, 2 * a, 2 * a);
painter.restore();
break;
}
}
this->fancyPaint(painter);
}
} // namespace widgets
} // namespace chatterino

View file

@ -0,0 +1,24 @@
#pragma once
#include "widgets/helper/rippleeffectbutton.hpp"
namespace chatterino {
namespace widgets {
class TitleBarButton : public RippleEffectButton
{
public:
enum Style { Minimize = 1, Maximize = 2, Unmaximize = 4, Close = 8, User = 16, Settings = 32 };
TitleBarButton();
Style getButtonStyle() const;
void setButtonStyle(Style style);
protected:
virtual void paintEvent(QPaintEvent *) override;
private:
Style style;
};
} // namespace widgets
} // namespace chatterino

View file

@ -1,7 +1,7 @@
#include "widgets/notebook.hpp" #include "widgets/notebook.hpp"
#include "debug/log.hpp" #include "debug/log.hpp"
#include "singletons/thememanager.hpp" #include "singletons/thememanager.hpp"
#include "widgets/accountswitchpopupwidget.hpp" #include "singletons/windowmanager.hpp"
#include "widgets/helper/notebookbutton.hpp" #include "widgets/helper/notebookbutton.hpp"
#include "widgets/helper/notebooktab.hpp" #include "widgets/helper/notebooktab.hpp"
#include "widgets/settingsdialog.hpp" #include "widgets/settingsdialog.hpp"
@ -53,6 +53,8 @@ Notebook::Notebook(Window *parent, bool _showButtons, const std::string &setting
closeConfirmDialog.setIcon(QMessageBox::Icon::Question); closeConfirmDialog.setIcon(QMessageBox::Icon::Question);
closeConfirmDialog.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); closeConfirmDialog.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
closeConfirmDialog.setDefaultButton(QMessageBox::Yes); closeConfirmDialog.setDefaultButton(QMessageBox::Yes);
this->scaleChangedEvent(this->getScale());
} }
SplitContainer *Notebook::addNewPage() SplitContainer *Notebook::addNewPage()
@ -130,6 +132,9 @@ void Notebook::select(SplitContainer *page)
if (this->selectedPage != nullptr) { if (this->selectedPage != nullptr) {
this->selectedPage->setHidden(true); this->selectedPage->setHidden(true);
this->selectedPage->getTab()->setSelected(false); this->selectedPage->getTab()->setSelected(false);
for (auto split : this->selectedPage->getSplits()) {
split->updateLastReadMessage();
}
} }
this->selectedPage = page; this->selectedPage = page;
@ -151,12 +156,15 @@ int Notebook::tabCount()
return this->pages.size(); return this->pages.size();
} }
SplitContainer *Notebook::tabAt(QPoint point, int &index) SplitContainer *Notebook::tabAt(QPoint point, int &index, int maxWidth)
{ {
int i = 0; int i = 0;
for (auto *page : this->pages) { for (auto *page : this->pages) {
if (page->getTab()->getDesiredRect().contains(point)) { QRect rect = page->getTab()->getDesiredRect();
rect.setWidth(std::min(maxWidth, rect.width()));
if (rect.contains(point)) {
index = i; index = i;
return page; return page;
} }
@ -206,7 +214,7 @@ void Notebook::performLayout(bool animated)
singletons::SettingManager &settings = singletons::SettingManager::getInstance(); singletons::SettingManager &settings = singletons::SettingManager::getInstance();
int x = 0, y = 0; int x = 0, y = 0;
float scale = this->getDpiMultiplier(); float scale = this->getScale();
bool customFrame = this->parentWindow->hasCustomWindowFrame(); bool customFrame = this->parentWindow->hasCustomWindowFrame();
if (!this->showButtons || settings.hidePreferencesButton || customFrame) { if (!this->showButtons || settings.hidePreferencesButton || customFrame) {
@ -250,51 +258,37 @@ void Notebook::performLayout(bool animated)
if (this->selectedPage != nullptr) { if (this->selectedPage != nullptr) {
this->selectedPage->move(0, y + tabHeight); this->selectedPage->move(0, y + tabHeight);
this->selectedPage->resize(width(), height() - y - tabHeight); this->selectedPage->resize(width(), height() - y - tabHeight);
this->selectedPage->raise();
} }
} }
void Notebook::resizeEvent(QResizeEvent *) void Notebook::resizeEvent(QResizeEvent *)
{ {
float scale = this->getDpiMultiplier(); this->performLayout(false);
this->settingsButton.resize(static_cast<int>(24 * scale), static_cast<int>(24 * scale));
this->userButton.resize(static_cast<int>(24 * scale), static_cast<int>(24 * scale));
this->addButton.resize(static_cast<int>(24 * scale), static_cast<int>(24 * scale));
for (auto &i : this->pages) {
i->getTab()->calcSize();
} }
this->performLayout(false); void Notebook::scaleChangedEvent(float)
{
float h = 24 * this->getScale();
this->settingsButton.setFixedSize(h, h);
this->userButton.setFixedSize(h, h);
this->addButton.setFixedSize(h, h);
for (auto &i : this->pages) {
i->getTab()->updateSize();
}
} }
void Notebook::settingsButtonClicked() void Notebook::settingsButtonClicked()
{ {
QTimer::singleShot(80, [this] { SettingsDialog::showDialog(); }); singletons::WindowManager::getInstance().showSettingsDialog();
} }
void Notebook::usersButtonClicked() void Notebook::usersButtonClicked()
{ {
static QWidget *lastFocusedWidget = nullptr; singletons::WindowManager::getInstance().showAccountSelectPopup(
static AccountSwitchPopupWidget *w = new AccountSwitchPopupWidget(this); this->mapToGlobal(this->userButton.rect().bottomRight()));
if (w->hasFocus()) {
w->hide();
if (lastFocusedWidget) {
lastFocusedWidget->setFocus();
}
return;
}
lastFocusedWidget = this->focusWidget();
w->refresh();
QPoint buttonPos = this->userButton.rect().bottomRight();
w->move(buttonPos.x(), buttonPos.y());
w->show();
w->setFocus();
} }
void Notebook::addPageButtonClicked() void Notebook::addPageButtonClicked()

View file

@ -41,13 +41,14 @@ public:
void performLayout(bool animate = true); void performLayout(bool animate = true);
int tabCount(); int tabCount();
SplitContainer *tabAt(QPoint point, int &index); SplitContainer *tabAt(QPoint point, int &index, int maxWidth = 2000000000);
void rearrangePage(SplitContainer *page, int index); void rearrangePage(SplitContainer *page, int index);
void nextTab(); void nextTab();
void previousTab(); void previousTab();
protected: protected:
void scaleChangedEvent(float scale);
void resizeEvent(QResizeEvent *); void resizeEvent(QResizeEvent *);
void settingsButtonMouseReleased(QMouseEvent *event); void settingsButtonMouseReleased(QMouseEvent *event);

View file

@ -18,7 +18,7 @@ Scrollbar::Scrollbar(ChannelView *parent)
, currentValueAnimation(this, "currentValue") , currentValueAnimation(this, "currentValue")
, smoothScrollingSetting(singletons::SettingManager::getInstance().enableSmoothScrolling) , smoothScrollingSetting(singletons::SettingManager::getInstance().enableSmoothScrolling)
{ {
resize((int)(16 * this->getDpiMultiplier()), 100); resize((int)(16 * this->getScale()), 100);
this->currentValueAnimation.setDuration(150); this->currentValueAnimation.setDuration(150);
this->currentValueAnimation.setEasingCurve(QEasingCurve(QEasingCurve::OutCubic)); this->currentValueAnimation.setEasingCurve(QEasingCurve(QEasingCurve::OutCubic));
@ -29,7 +29,7 @@ Scrollbar::Scrollbar(ChannelView *parent)
timer->setSingleShot(true); timer->setSingleShot(true);
connect(timer, &QTimer::timeout, [=]() { connect(timer, &QTimer::timeout, [=]() {
resize((int)(16 * this->getDpiMultiplier()), 100); resize((int)(16 * this->getScale()), 100);
timer->deleteLater(); timer->deleteLater();
}); });
@ -194,7 +194,7 @@ void Scrollbar::printCurrentState(const QString &prefix) const
void Scrollbar::paintEvent(QPaintEvent *) void Scrollbar::paintEvent(QPaintEvent *)
{ {
bool mouseOver = this->mouseOverIndex != -1; bool mouseOver = this->mouseOverIndex != -1;
int xOffset = mouseOver ? 0 : width() - (int)(4 * this->getDpiMultiplier()); int xOffset = mouseOver ? 0 : width() - (int)(4 * this->getScale());
QPainter painter(this); QPainter painter(this);
// painter.fillRect(rect(), this->themeManager.ScrollbarBG); // painter.fillRect(rect(), this->themeManager.ScrollbarBG);
@ -248,7 +248,7 @@ void Scrollbar::paintEvent(QPaintEvent *)
void Scrollbar::resizeEvent(QResizeEvent *) void Scrollbar::resizeEvent(QResizeEvent *)
{ {
this->resize((int)(16 * this->getDpiMultiplier()), this->height()); this->resize((int)(16 * this->getScale()), this->height());
} }
void Scrollbar::mouseMoveEvent(QMouseEvent *event) void Scrollbar::mouseMoveEvent(QMouseEvent *event)

View file

@ -8,8 +8,12 @@
#include "widgets/settingspages/commandpage.hpp" #include "widgets/settingspages/commandpage.hpp"
#include "widgets/settingspages/emotespage.hpp" #include "widgets/settingspages/emotespage.hpp"
#include "widgets/settingspages/highlightingpage.hpp" #include "widgets/settingspages/highlightingpage.hpp"
#include "widgets/settingspages/ignoremessagespage.hpp"
#include "widgets/settingspages/ignoreuserspage.hpp"
#include "widgets/settingspages/keyboardsettingspage.hpp"
#include "widgets/settingspages/logspage.hpp" #include "widgets/settingspages/logspage.hpp"
#include "widgets/settingspages/moderationpage.hpp" #include "widgets/settingspages/moderationpage.hpp"
#include "widgets/settingspages/specialchannelspage.hpp"
#include <QDialogButtonBox> #include <QDialogButtonBox>
@ -25,7 +29,7 @@ SettingsDialog::SettingsDialog()
this->addTabs(); this->addTabs();
this->dpiMultiplierChanged(this->getDpiMultiplier(), this->getDpiMultiplier()); this->scaleChangedEvent(this->getScale());
} }
void SettingsDialog::initUi() void SettingsDialog::initUi()
@ -72,14 +76,24 @@ SettingsDialog *SettingsDialog::getHandle()
void SettingsDialog::addTabs() void SettingsDialog::addTabs()
{ {
this->ui.tabContainer->setSpacing(0);
this->addTab(new settingspages::AccountsPage); this->addTab(new settingspages::AccountsPage);
this->addTab(new settingspages::AppearancePage); this->addTab(new settingspages::AppearancePage);
this->addTab(new settingspages::BehaviourPage); this->addTab(new settingspages::BehaviourPage);
this->addTab(new settingspages::CommandPage); this->addTab(new settingspages::CommandPage);
this->addTab(new settingspages::EmotesPage); this->addTab(new settingspages::EmotesPage);
this->addTab(new settingspages::HighlightingPage); this->addTab(new settingspages::HighlightingPage);
// this->addTab(new settingspages::LogsPage);
this->ui.tabContainer->addStretch(1);
this->addTab(new settingspages::IgnoreMessagesPage);
this->addTab(new settingspages::IgnoreUsersPage);
this->addTab(new settingspages::KeyboardSettingsPage);
this->addTab(new settingspages::LogsPage);
this->addTab(new settingspages::ModerationPage); this->addTab(new settingspages::ModerationPage);
this->addTab(new settingspages::SpecialChannelsPage);
this->ui.tabContainer->addStretch(1); this->ui.tabContainer->addStretch(1);
this->addTab(new settingspages::AboutPage, Qt::AlignBottom); this->addTab(new settingspages::AboutPage, Qt::AlignBottom);
} }
@ -135,7 +149,7 @@ void SettingsDialog::refresh()
singletons::SettingManager::getInstance().saveSnapshot(); singletons::SettingManager::getInstance().saveSnapshot();
} }
void SettingsDialog::dpiMultiplierChanged(float oldDpi, float newDpi) void SettingsDialog::scaleChangedEvent(float newDpi)
{ {
QFile file(":/qss/settings.qss"); QFile file(":/qss/settings.qss");
file.open(QFile::ReadOnly); file.open(QFile::ReadOnly);

View file

@ -32,7 +32,7 @@ public:
static void showDialog(PreferredTab preferredTab = PreferredTab::NoPreference); static void showDialog(PreferredTab preferredTab = PreferredTab::NoPreference);
protected: protected:
virtual void dpiMultiplierChanged(float oldDpi, float newDpi) override; virtual void scaleChangedEvent(float newDpi) override;
private: private:
void refresh(); void refresh();

View file

@ -44,8 +44,10 @@ AppearancePage::AppearancePage()
form->addRow("Font:", this->createFontChanger()); form->addRow("Font:", this->createFontChanger());
form->addRow("Tab bar:", this->createCheckBox(TAB_X, settings.hideTabX)); form->addRow("Tab bar:", this->createCheckBox(TAB_X, settings.hideTabX));
#ifndef USEWINSDK
form->addRow("", this->createCheckBox(TAB_PREF, settings.hidePreferencesButton)); form->addRow("", this->createCheckBox(TAB_PREF, settings.hidePreferencesButton));
form->addRow("", this->createCheckBox(TAB_USER, settings.hideUserButton)); form->addRow("", this->createCheckBox(TAB_USER, settings.hideUserButton));
#endif
form->addRow("Scrolling:", this->createCheckBox(SCROLL_SMOOTH, settings.enableSmoothScrolling)); form->addRow("Scrolling:", this->createCheckBox(SCROLL_SMOOTH, settings.enableSmoothScrolling));
form->addRow("", this->createCheckBox(SCROLL_NEWMSG, settings.enableSmoothScrollingNewMessages)); form->addRow("", this->createCheckBox(SCROLL_NEWMSG, settings.enableSmoothScrollingNewMessages));
@ -59,6 +61,7 @@ AppearancePage::AppearancePage()
{ {
tbox.emplace<QLabel>("timestamp format (a = am/pm):"); tbox.emplace<QLabel>("timestamp format (a = am/pm):");
tbox.append(this->createComboBox({TIMESTAMP_FORMATS}, settings.timestampFormat)); tbox.append(this->createComboBox({TIMESTAMP_FORMATS}, settings.timestampFormat));
tbox->addStretch(1);
} }
messages.append(this->createCheckBox("Show badges", settings.showBadges)); messages.append(this->createCheckBox("Show badges", settings.showBadges));
messages.append(this->createCheckBox("Seperate messages", settings.seperateMessages)); messages.append(this->createCheckBox("Seperate messages", settings.seperateMessages));
@ -77,7 +80,7 @@ QLayout *AppearancePage::createThemeColorChanger()
// SLIDER // SLIDER
QSlider *slider = new QSlider(Qt::Horizontal); QSlider *slider = new QSlider(Qt::Horizontal);
layout->addWidget(slider); layout->addWidget(slider);
slider->setValue(std::min(std::max(themeHue.getValue(), 0.0), 1.0) * 1000); slider->setValue(std::min(std::max(themeHue.getValue(), 0.0), 1.0) * 100);
// BUTTON // BUTTON
QPushButton *button = new QPushButton; QPushButton *button = new QPushButton;
@ -87,7 +90,7 @@ QLayout *AppearancePage::createThemeColorChanger()
// SIGNALS // SIGNALS
QObject::connect(slider, &QSlider::valueChanged, this, [button, &themeHue](int value) mutable { QObject::connect(slider, &QSlider::valueChanged, this, [button, &themeHue](int value) mutable {
double newValue = value / 1000.0; double newValue = value / 100.0;
themeHue.setValue(newValue); themeHue.setValue(newValue);

View file

@ -1,6 +1,7 @@
#include "behaviourpage.hpp" #include "behaviourpage.hpp"
#include <QFormLayout> #include <QFormLayout>
#include <QGroupBox>
#include <QLabel> #include <QLabel>
#include <QVBoxLayout> #include <QVBoxLayout>
@ -8,7 +9,7 @@
#define WINDOW_TOPMOST "Window always on top (requires restart)" #define WINDOW_TOPMOST "Window always on top (requires restart)"
#define INPUT_EMPTY "Hide input box when empty" #define INPUT_EMPTY "Hide input box when empty"
#define LAST_MSG "Show last read message indicator" #define LAST_MSG "Show last read message indicator (marks the spot where you left the window)"
#define PAUSE_HOVERING "When hovering" #define PAUSE_HOVERING "When hovering"
#define STREAMLINK_QUALITY "Choose", "Source", "High", "Medium", "Low", "Audio only" #define STREAMLINK_QUALITY "Choose", "Source", "High", "Medium", "Low", "Audio only"
@ -22,7 +23,9 @@ BehaviourPage::BehaviourPage()
singletons::SettingManager &settings = singletons::SettingManager::getInstance(); singletons::SettingManager &settings = singletons::SettingManager::getInstance();
util::LayoutCreator<BehaviourPage> layoutCreator(this); util::LayoutCreator<BehaviourPage> layoutCreator(this);
auto form = layoutCreator.emplace<QFormLayout>().withoutMargin(); auto layout = layoutCreator.setLayoutType<QVBoxLayout>();
auto form = layout.emplace<QFormLayout>().withoutMargin();
{ {
form->addRow("Window:", this->createCheckBox(WINDOW_TOPMOST, settings.windowTopMost)); form->addRow("Window:", this->createCheckBox(WINDOW_TOPMOST, settings.windowTopMost));
form->addRow("Messages:", this->createCheckBox(INPUT_EMPTY, settings.hideEmptyInput)); form->addRow("Messages:", this->createCheckBox(INPUT_EMPTY, settings.hideEmptyInput));
@ -30,10 +33,21 @@ BehaviourPage::BehaviourPage()
form->addRow("Pause chat:", this->createCheckBox(PAUSE_HOVERING, settings.pauseChatHover)); form->addRow("Pause chat:", this->createCheckBox(PAUSE_HOVERING, settings.pauseChatHover));
form->addRow("Mouse scroll speed:", this->createMouseScrollSlider()); form->addRow("Mouse scroll speed:", this->createMouseScrollSlider());
form->addRow("Streamlink path:", this->createLineEdit(settings.streamlinkPath)); form->addRow("Links:", this->createCheckBox("Open links only on double click",
form->addRow("Prefered quality:", settings.linksDoubleClickOnly));
}
layout->addSpacing(16);
auto group = layout.emplace<QGroupBox>("Streamlink");
{
auto groupLayout = group.setLayoutType<QFormLayout>();
groupLayout->addRow("Streamlink path:", this->createLineEdit(settings.streamlinkPath));
groupLayout->addRow("Prefered quality:",
this->createComboBox({STREAMLINK_QUALITY}, settings.preferredQuality)); this->createComboBox({STREAMLINK_QUALITY}, settings.preferredQuality));
} }
layout->addStretch(1);
} }
QSlider *BehaviourPage::createMouseScrollSlider() QSlider *BehaviourPage::createMouseScrollSlider()

View file

@ -0,0 +1,37 @@
#include "ignoremessagespage.hpp"
#include "util/layoutcreator.hpp"
#include <QLabel>
#include <QTextEdit>
namespace chatterino {
namespace widgets {
namespace settingspages {
IgnoreMessagesPage::IgnoreMessagesPage()
: SettingsPage("Ignore Messages", "")
{
singletons::SettingManager &settings = singletons::SettingManager::getInstance();
util::LayoutCreator<IgnoreMessagesPage> layoutCreator(this);
auto layout = layoutCreator.setLayoutType<QVBoxLayout>();
layout.emplace<QLabel>("Ignored keywords:");
QTextEdit *textEdit = layout.emplace<QTextEdit>().getElement();
textEdit->setPlainText(settings.ignoredKeywords);
QObject::connect(textEdit, &QTextEdit::textChanged,
[this] { this->keywordsUpdated.start(200); });
QObject::connect(&this->keywordsUpdated, &QTimer::timeout, [textEdit, &settings] {
QString text = textEdit->toPlainText();
settings.ignoredKeywords = text;
});
// ---- misc
this->keywordsUpdated.setSingleShot(true);
}
} // namespace settingspages
} // namespace widgets
} // namespace chatterino

View file

@ -0,0 +1,19 @@
#pragma once
#include <QTimer>
#include "widgets/settingspages/settingspage.hpp"
namespace chatterino {
namespace widgets {
namespace settingspages {
class IgnoreMessagesPage : public SettingsPage
{
public:
IgnoreMessagesPage();
QTimer keywordsUpdated;
};
} // namespace settingspages
} // namespace widgets
} // namespace chatterino

View file

@ -0,0 +1,55 @@
#include "ignoreuserspage.hpp"
#include "singletons/settingsmanager.hpp"
#include "util/layoutcreator.hpp"
#include <QCheckBox>
#include <QGroupBox>
#include <QLabel>
#include <QListView>
#include <QPushButton>
#include <QVBoxLayout>
// clang-format off
#define INFO "/ignore <user> in chat ignores a user\n/unignore <user> in chat unignores a user\n\nChatterino uses the twitch api for ignored users so they are shared with the webchat.\nIf you use your own oauth key make sure that it has the correct permissions.\n"
// clang-format on
namespace chatterino {
namespace widgets {
namespace settingspages {
IgnoreUsersPage::IgnoreUsersPage()
: SettingsPage("Ignore Users", "")
{
singletons::SettingManager &settings = singletons::SettingManager::getInstance();
util::LayoutCreator<IgnoreUsersPage> layoutCreator(this);
auto layout = layoutCreator.setLayoutType<QVBoxLayout>();
auto label = layout.emplace<QLabel>(INFO);
label->setWordWrap(true);
label->setStyleSheet("color: #BBB");
auto group = layout.emplace<QGroupBox>("Ignored users").setLayoutType<QVBoxLayout>();
{
group.append(
this->createCheckBox("Enable twitch ignored users", settings.enableTwitchIgnoredUsers));
auto anyways = group.emplace<QHBoxLayout>().withoutMargin();
{
anyways.emplace<QLabel>("Show anyways if:");
anyways.emplace<QComboBox>();
anyways->addStretch(1);
}
auto addremove = group.emplace<QHBoxLayout>().withoutMargin();
{
auto add = addremove.emplace<QPushButton>("Ignore user");
auto remove = addremove.emplace<QPushButton>("Unignore User");
addremove->addStretch(1);
}
auto userList = group.emplace<QListView>();
}
}
} // namespace settingspages
} // namespace widgets
} // namespace chatterino

View file

@ -0,0 +1,15 @@
#pragma once
#include "widgets/settingspages/settingspage.hpp"
namespace chatterino {
namespace widgets {
namespace settingspages {
class IgnoreUsersPage : public SettingsPage
{
public:
IgnoreUsersPage();
};
} // namespace settingspages
} // namespace widgets
} // namespace chatterino

View file

@ -0,0 +1,12 @@
#include "keyboardsettingspage.hpp"
namespace chatterino {
namespace widgets {
namespace settingspages {
KeyboardSettingsPage::KeyboardSettingsPage()
: SettingsPage("Keybindings", "")
{
}
} // namespace settingspages
} // namespace widgets
} // namespace chatterino

View file

@ -0,0 +1,15 @@
#pragma once
#include "widgets/settingspages/settingspage.hpp"
namespace chatterino {
namespace widgets {
namespace settingspages {
class KeyboardSettingsPage : public SettingsPage
{
public:
KeyboardSettingsPage();
};
} // namespace settingspages
} // namespace widgets
} // namespace chatterino

View file

@ -4,7 +4,7 @@ namespace chatterino {
namespace widgets { namespace widgets {
namespace settingspages { namespace settingspages {
LogsPage::LogsPage() LogsPage::LogsPage()
: SettingsPage("Logs", ":/images/VSO_Link_blue_16x.png") : SettingsPage("Logs", "")
{ {
} }
} // namespace settingspages } // namespace settingspages

View file

@ -1,5 +1,6 @@
#include "moderationpage.hpp" #include "moderationpage.hpp"
#include <QGroupBox>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QListView> #include <QListView>
@ -18,22 +19,33 @@ ModerationPage::ModerationPage()
singletons::SettingManager &settings = singletons::SettingManager::getInstance(); singletons::SettingManager &settings = singletons::SettingManager::getInstance();
util::LayoutCreator<ModerationPage> layoutCreator(this); util::LayoutCreator<ModerationPage> layoutCreator(this);
auto layout = layoutCreator.emplace<QVBoxLayout>().withoutMargin(); auto layout = layoutCreator.setLayoutType<QVBoxLayout>();
{ {
// clang-format off // clang-format off
auto label = layout.emplace<QLabel>("In channels that you moderate there is a button <insert image of button here> to enable moderation mode.\n\nOne action per line. {user} will be replaced with the username.\nExample `/timeout {user} 120`"); auto label = layout.emplace<QLabel>("Click the moderation mod button (<img width='18' height='18' src=':/images/moderatormode_disabled.png'>) in a channel that you moderate to enable moderator mode.<br>");
label->setWordWrap(true); label->setWordWrap(true);
label->setStyleSheet("color: #bbb");
// clang-format on // clang-format on
auto text = layout.emplace<QTextEdit>().getElement(); auto modButtons =
layout.emplace<QGroupBox>("Custom moderator buttons").setLayoutType<QVBoxLayout>();
{
auto label2 =
modButtons.emplace<QLabel>("One action per line. {user} will be replaced with the "
"username.<br>Example `/timeout {user} 120`<br>");
label2->setWordWrap(true);
auto text = modButtons.emplace<QTextEdit>().getElement();
text->setPlainText(settings.moderationActions); text->setPlainText(settings.moderationActions);
QObject::connect(text, &QTextEdit::textChanged, this, QObject::connect(text, &QTextEdit::textChanged, this,
[this] { this->itemsChangedTimer.start(200); }); [this] { this->itemsChangedTimer.start(200); });
QObject::connect(&this->itemsChangedTimer, &QTimer::timeout, this, QObject::connect(&this->itemsChangedTimer, &QTimer::timeout, this, [text, &settings]() {
[text, &settings]() { settings.moderationActions = text->toPlainText(); }); settings.moderationActions = text->toPlainText();
});
}
} }
// ---- misc // ---- misc

View file

@ -0,0 +1,28 @@
#include "specialchannelspage.hpp"
#include "singletons/settingsmanager.hpp"
#include "util/layoutcreator.hpp"
#include <QGroupBox>
#include <QLabel>
#include <QVBoxLayout>
namespace chatterino {
namespace widgets {
namespace settingspages {
SpecialChannelsPage::SpecialChannelsPage()
: SettingsPage("Special channels", "")
{
util::LayoutCreator<SpecialChannelsPage> layoutCreator(this);
auto layout = layoutCreator.setLayoutType<QVBoxLayout>();
auto mentions = layout.emplace<QGroupBox>("Mentions channel").setLayoutType<QVBoxLayout>();
{
mentions.emplace<QLabel>("Join /mentions to view your mentions.");
}
layout->addStretch(1);
}
} // namespace settingspages
} // namespace widgets
} // namespace chatterino

View file

@ -0,0 +1,15 @@
#pragma once
#include "widgets/settingspages/settingspage.hpp"
namespace chatterino {
namespace widgets {
namespace settingspages {
class SpecialChannelsPage : public SettingsPage
{
public:
SpecialChannelsPage();
};
} // namespace settingspages
} // namespace widgets
} // namespace chatterino

View file

@ -87,7 +87,7 @@ Split::Split(SplitContainer *parent, const std::string &_uuid)
this->channelNameUpdated(this->channelName.getValue()); this->channelNameUpdated(this->channelName.getValue());
this->input.textInput.installEventFilter(parent); this->input.ui.textEdit->installEventFilter(parent);
this->view.mouseDown.connect([this](QMouseEvent *) { this->giveFocus(Qt::MouseFocusReason); }); this->view.mouseDown.connect([this](QMouseEvent *) { this->giveFocus(Qt::MouseFocusReason); });
this->view.selectionChanged.connect([this]() { this->view.selectionChanged.connect([this]() {
@ -132,17 +132,17 @@ const std::string &Split::getUUID() const
return this->uuid; return this->uuid;
} }
SharedChannel Split::getChannel() const ChannelPtr Split::getChannel() const
{ {
return this->channel; return this->channel;
} }
SharedChannel &Split::getChannelRef() ChannelPtr &Split::getChannelRef()
{ {
return this->channel; return this->channel;
} }
void Split::setChannel(SharedChannel _newChannel) void Split::setChannel(ChannelPtr _newChannel)
{ {
this->view.setChannel(_newChannel); this->view.setChannel(_newChannel);
@ -248,18 +248,22 @@ void Split::layoutMessages()
void Split::updateGifEmotes() void Split::updateGifEmotes()
{ {
qDebug() << "this shouldn't even exist";
this->view.queueUpdate(); this->view.queueUpdate();
} }
void Split::updateLastReadMessage()
{
this->view.updateLastReadMessage();
}
void Split::giveFocus(Qt::FocusReason reason) void Split::giveFocus(Qt::FocusReason reason)
{ {
this->input.textInput.setFocus(reason); this->input.ui.textEdit->setFocus(reason);
} }
bool Split::hasFocus() const bool Split::hasFocus() const
{ {
return this->input.textInput.hasFocus(); return this->input.ui.textEdit->hasFocus();
} }
void Split::paintEvent(QPaintEvent *) void Split::paintEvent(QPaintEvent *)
@ -350,7 +354,7 @@ void Split::doClearChat()
void Split::doOpenChannel() void Split::doOpenChannel()
{ {
SharedChannel _channel = this->channel; ChannelPtr _channel = this->channel;
twitch::TwitchChannel *tc = dynamic_cast<twitch::TwitchChannel *>(_channel.get()); twitch::TwitchChannel *tc = dynamic_cast<twitch::TwitchChannel *>(_channel.get());
if (tc != nullptr) { if (tc != nullptr) {
@ -360,7 +364,7 @@ void Split::doOpenChannel()
void Split::doOpenPopupPlayer() void Split::doOpenPopupPlayer()
{ {
SharedChannel _channel = this->channel; ChannelPtr _channel = this->channel;
twitch::TwitchChannel *tc = dynamic_cast<twitch::TwitchChannel *>(_channel.get()); twitch::TwitchChannel *tc = dynamic_cast<twitch::TwitchChannel *>(_channel.get());
if (tc != nullptr) { if (tc != nullptr) {
@ -460,6 +464,7 @@ void Split::doOpenViewerList()
viewerDock->move(0, this->header.height()); viewerDock->move(0, this->header.height());
auto accountPopup = new AccountPopupWidget(this->channel); auto accountPopup = new AccountPopupWidget(this->channel);
accountPopup->setAttribute(Qt::WA_DeleteOnClose);
auto multiWidget = new QWidget(viewerDock); auto multiWidget = new QWidget(viewerDock);
auto dockVbox = new QVBoxLayout(viewerDock); auto dockVbox = new QVBoxLayout(viewerDock);
auto searchBar = new QLineEdit(viewerDock); auto searchBar = new QLineEdit(viewerDock);
@ -538,9 +543,9 @@ void Split::doOpenViewerList()
void Split::doOpenAccountPopupWidget(AccountPopupWidget *widget, QString user) void Split::doOpenAccountPopupWidget(AccountPopupWidget *widget, QString user)
{ {
widget->setName(user); widget->setName(user);
widget->move(QCursor::pos());
widget->show(); widget->show();
widget->setFocus(); widget->setFocus();
widget->moveTo(this, QCursor::pos());
} }
void Split::doCopy() void Split::doCopy()

View file

@ -55,8 +55,8 @@ public:
} }
const std::string &getUUID() const; const std::string &getUUID() const;
SharedChannel getChannel() const; ChannelPtr getChannel() const;
SharedChannel &getChannelRef(); ChannelPtr &getChannelRef();
void setFlexSizeX(double x); void setFlexSizeX(double x);
double getFlexSizeX(); double getFlexSizeX();
void setFlexSizeY(double y); void setFlexSizeY(double y);
@ -70,6 +70,7 @@ public:
bool hasFocus() const; bool hasFocus() const;
void layoutMessages(); void layoutMessages();
void updateGifEmotes(); void updateGifEmotes();
void updateLastReadMessage();
void drag(); void drag();
@ -82,7 +83,7 @@ protected:
private: private:
SplitContainer &parentPage; SplitContainer &parentPage;
SharedChannel channel; ChannelPtr channel;
QVBoxLayout vbox; QVBoxLayout vbox;
SplitHeader header; SplitHeader header;
@ -96,7 +97,7 @@ private:
boost::signals2::connection channelIDChangedConnection; boost::signals2::connection channelIDChangedConnection;
boost::signals2::connection usermodeChangedConnection; boost::signals2::connection usermodeChangedConnection;
void setChannel(SharedChannel newChannel); void setChannel(ChannelPtr newChannel);
void doOpenAccountPopupWidget(AccountPopupWidget *widget, QString user); void doOpenAccountPopupWidget(AccountPopupWidget *widget, QString user);
void channelNameUpdated(const std::string &newChannelName); void channelNameUpdated(const std::string &newChannelName);
void handleModifiers(QEvent *event, Qt::KeyboardModifiers modifiers); void handleModifiers(QEvent *event, Qt::KeyboardModifiers modifiers);

View file

@ -143,7 +143,7 @@ void SplitContainer::addToLayout(Split *widget, std::pair<int, int> position)
this->refreshCurrentFocusCoordinates(); this->refreshCurrentFocusCoordinates();
} }
const std::vector<Split *> &SplitContainer::getChatWidgets() const const std::vector<Split *> &SplitContainer::getSplits() const
{ {
return this->splits; return this->splits;
} }

View file

@ -33,7 +33,7 @@ public:
std::pair<int, int> removeFromLayout(Split *widget); std::pair<int, int> removeFromLayout(Split *widget);
void addToLayout(Split *widget, std::pair<int, int> position = std::pair<int, int>(-1, -1)); void addToLayout(Split *widget, std::pair<int, int> position = std::pair<int, int>(-1, -1));
const std::vector<Split *> &getChatWidgets() const; const std::vector<Split *> &getSplits() const;
NotebookTab *getTab() const; NotebookTab *getTab() const;
void addChat(bool openChannelNameDialog = false, std::string chatUUID = std::string()); void addChat(bool openChannelNameDialog = false, std::string chatUUID = std::string());

View file

@ -11,7 +11,7 @@
namespace chatterino { namespace chatterino {
namespace widgets { namespace widgets {
StreamView::StreamView(SharedChannel channel, QUrl url) StreamView::StreamView(ChannelPtr channel, QUrl url)
{ {
util::LayoutCreator<StreamView> layoutCreator(this); util::LayoutCreator<StreamView> layoutCreator(this);

View file

@ -17,6 +17,7 @@ TooltipWidget::TooltipWidget(BaseWidget *parent)
this->setStyleSheet("color: #fff; background: #000"); this->setStyleSheet("color: #fff; background: #000");
this->setWindowOpacity(0.8); this->setWindowOpacity(0.8);
this->updateFont(); this->updateFont();
this->setStayInScreenRect(true);
this->setAttribute(Qt::WA_ShowWithoutActivating); this->setAttribute(Qt::WA_ShowWithoutActivating);
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint |
@ -39,7 +40,7 @@ TooltipWidget::~TooltipWidget()
this->fontChangedConnection.disconnect(); this->fontChangedConnection.disconnect();
} }
void TooltipWidget::dpiMultiplierChanged(float, float) void TooltipWidget::scaleChangedEvent(float)
{ {
this->updateFont(); this->updateFont();
} }
@ -47,7 +48,7 @@ void TooltipWidget::dpiMultiplierChanged(float, float)
void TooltipWidget::updateFont() void TooltipWidget::updateFont()
{ {
this->setFont(singletons::FontManager::getInstance().getFont( this->setFont(singletons::FontManager::getInstance().getFont(
singletons::FontManager::Type::MediumSmall, this->getDpiMultiplier())); singletons::FontManager::Type::MediumSmall, this->getScale()));
} }
void TooltipWidget::setText(QString text) void TooltipWidget::setText(QString text)
@ -55,45 +56,6 @@ void TooltipWidget::setText(QString text)
this->displayText->setText(text); this->displayText->setText(text);
} }
void TooltipWidget::moveTo(QWidget *parent, QPoint point)
{
point.rx() += 16;
point.ry() += 16;
this->move(point);
this->moveIntoDesktopRect(parent);
}
void TooltipWidget::resizeEvent(QResizeEvent *)
{
this->moveIntoDesktopRect(this);
}
void TooltipWidget::moveIntoDesktopRect(QWidget *parent)
{
QDesktopWidget *desktop = QApplication::desktop();
QRect s = desktop->screenGeometry(parent);
QPoint p = this->pos();
if (p.x() < s.left()) {
p.setX(s.left());
}
if (p.y() < s.top()) {
p.setY(s.top());
}
if (p.x() + this->width() > s.right()) {
p.setX(s.right() - this->width());
}
if (p.y() + this->height() > s.bottom()) {
p.setY(s.bottom() - this->height());
}
if (p != this->pos()) {
this->move(p);
}
}
void TooltipWidget::changeEvent(QEvent *) void TooltipWidget::changeEvent(QEvent *)
{ {
// clear parents event // clear parents event

View file

@ -16,7 +16,6 @@ public:
~TooltipWidget(); ~TooltipWidget();
void setText(QString text); void setText(QString text);
void moveTo(QWidget *widget, QPoint point);
static TooltipWidget *getInstance() static TooltipWidget *getInstance()
{ {
@ -28,16 +27,14 @@ public:
} }
protected: protected:
virtual void resizeEvent(QResizeEvent *) override;
virtual void changeEvent(QEvent *) override; virtual void changeEvent(QEvent *) override;
virtual void leaveEvent(QEvent *) override; virtual void leaveEvent(QEvent *) override;
virtual void dpiMultiplierChanged(float, float) override; virtual void scaleChangedEvent(float) override;
private: private:
QLabel *displayText; QLabel *displayText;
pajlada::Signals::Connection fontChangedConnection; pajlada::Signals::Connection fontChangedConnection;
void moveIntoDesktopRect(QWidget *parent);
void updateFont(); void updateFont();
}; };

View file

@ -4,6 +4,8 @@
#include "singletons/ircmanager.hpp" #include "singletons/ircmanager.hpp"
#include "singletons/settingsmanager.hpp" #include "singletons/settingsmanager.hpp"
#include "singletons/thememanager.hpp" #include "singletons/thememanager.hpp"
#include "singletons/windowmanager.hpp"
#include "widgets/accountswitchpopupwidget.hpp"
#include "widgets/helper/shortcut.hpp" #include "widgets/helper/shortcut.hpp"
#include "widgets/notebook.hpp" #include "widgets/notebook.hpp"
#include "widgets/settingsdialog.hpp" #include "widgets/settingsdialog.hpp"
@ -21,7 +23,7 @@ Window::Window(const QString &windowName, singletons::ThemeManager &_themeManage
: BaseWindow(_themeManager, nullptr, true) : BaseWindow(_themeManager, nullptr, true)
, settingRoot(fS("/windows/{}", windowName)) , settingRoot(fS("/windows/{}", windowName))
, windowGeometry(this->settingRoot) , windowGeometry(this->settingRoot)
, dpi(this->getDpiMultiplier()) , dpi(this->getScale())
, themeManager(_themeManager) , themeManager(_themeManager)
, notebook(this, _isMainWindow, this->settingRoot) , notebook(this, _isMainWindow, this->settingRoot)
{ {
@ -35,8 +37,12 @@ Window::Window(const QString &windowName, singletons::ThemeManager &_themeManage
}); });
if (this->hasCustomWindowFrame()) { if (this->hasCustomWindowFrame()) {
this->addTitleBarButton("Preferences"); this->addTitleBarButton(TitleBarButton::Settings, [] {
this->addTitleBarButton("User"); singletons::WindowManager::getInstance().showSettingsDialog();
});
this->addTitleBarButton(TitleBarButton::User, [this] {
singletons::WindowManager::getInstance().showAccountSelectPopup(QCursor::pos());
});
} }
QVBoxLayout *layout = new QVBoxLayout(this); QVBoxLayout *layout = new QVBoxLayout(this);
@ -52,7 +58,7 @@ Window::Window(const QString &windowName, singletons::ThemeManager &_themeManage
// set margin // set margin
layout->setMargin(0); layout->setMargin(0);
this->refreshTheme(); this->themeRefreshEvent();
this->loadGeometry(); this->loadGeometry();
@ -107,7 +113,7 @@ void Window::repaintVisibleChatWidgets(Channel *channel)
return; return;
} }
const std::vector<Split *> &widgets = page->getChatWidgets(); const std::vector<Split *> &widgets = page->getSplits();
for (auto it = widgets.begin(); it != widgets.end(); ++it) { for (auto it = widgets.begin(); it != widgets.end(); ++it) {
Split *widget = *it; Split *widget = *it;
@ -140,6 +146,27 @@ void Window::closeEvent(QCloseEvent *)
this->closed(); this->closed();
} }
bool Window::event(QEvent *e)
{
switch (e->type()) {
case QEvent::WindowActivate:
break;
case QEvent::WindowDeactivate: {
auto page = this->notebook.getSelectedPage();
if (page != nullptr) {
std::vector<Split *> splits = page->getSplits();
for (Split *split : splits) {
split->updateLastReadMessage();
}
}
} break;
};
return BaseWindow::event(e);
}
void Window::loadGeometry() void Window::loadGeometry()
{ {
bool doSetGeometry = false; bool doSetGeometry = false;

View file

@ -57,6 +57,7 @@ public:
protected: protected:
virtual void closeEvent(QCloseEvent *event) override; virtual void closeEvent(QCloseEvent *event) override;
virtual bool event(QEvent *event) override;
private: private:
singletons::ThemeManager &themeManager; singletons::ThemeManager &themeManager;