diff --git a/.gitignore b/.gitignore index 3c6ec6855..3d5ecc2b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +__pycache__/ + # C++ objects and libs *.slo diff --git a/chatterino.pro b/chatterino.pro index e281901d7..131696679 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -240,6 +240,7 @@ SOURCES += \ src/messages/Emote.cpp \ src/messages/ImageSet.cpp \ src/providers/bttv/BttvEmotes.cpp \ + src/providers/LinkResolver.cpp \ src/providers/ffz/FfzEmotes.cpp \ src/autogenerated/ResourcesAutogen.cpp \ src/singletons/Badges.cpp \ @@ -439,6 +440,7 @@ HEADERS += \ src/messages/ImageSet.hpp \ src/common/Outcome.hpp \ src/providers/bttv/BttvEmotes.hpp \ + src/providers/LinkResolver.hpp \ src/providers/ffz/FfzEmotes.hpp \ src/autogenerated/ResourcesAutogen.hpp \ src/singletons/Badges.hpp \ diff --git a/resources/__pycache__/_generate_resources.cpython-36.pyc b/resources/__pycache__/_generate_resources.cpython-36.pyc deleted file mode 100644 index dfaf6af51..000000000 Binary files a/resources/__pycache__/_generate_resources.cpython-36.pyc and /dev/null differ diff --git a/resources/buttons/addSplitDark.png b/resources/buttons/addSplitDark.png new file mode 100644 index 000000000..c6cd6707a Binary files /dev/null and b/resources/buttons/addSplitDark.png differ diff --git a/resources/generate_resources.py b/resources/generate_resources.py index 4b047219c..5239e64e8 100755 --- a/resources/generate_resources.py +++ b/resources/generate_resources.py @@ -5,10 +5,18 @@ from _generate_resources import * ignored_files = ['qt.conf', 'resources.qrc', 'resources_autogenerated.qrc', 'windows.rc', 'generate_resources.py', '_generate_resources.py'] + +# to ignore all files in a/b, add a/b to ignored_directories. +# this will ignore a/b/c/d.txt and a/b/xd.txt ignored_directories = ['__pycache__'] def isNotIgnored(file): - return str(file) not in ignored_files + # check if file exists in an ignored direcory + for ignored_directory in ignored_directories: + if file.parent.as_posix().startswith(ignored_directory): + return False + + return file.as_posix() not in ignored_files all_files = list(filter(isNotIgnored, \ filter(Path.is_file, Path('.').glob('**/*')))) @@ -18,15 +26,15 @@ image_files = list(filter(isNotIgnored, \ with open('./resources_autogenerated.qrc', 'w') as out: out.write(resources_header) for file in all_files: - out.write(f" {str(file)}\n") + out.write(f" {file.as_posix()}\n") out.write(resources_footer) with open('../src/autogenerated/ResourcesAutogen.cpp', 'w') as out: out.write(source_header) for file in sorted(image_files): - var_name = str(file.with_suffix("")).replace("/",".") + var_name = file.with_suffix("").as_posix().replace("/",".") out.write(f' this->{var_name}') - out.write(f' = QPixmap(":/{file}");\n') + out.write(f' = QPixmap(":/{file.as_posix()}");\n') out.write(source_footer) def writeHeader(out, name, element, indent): @@ -46,12 +54,11 @@ with open('../src/autogenerated/ResourcesAutogen.hpp', 'w') as out: elements = {} for file in sorted(image_files): elements_ref = elements - directories = str(file).split('/')[:-1] + directories = file.as_posix().split('/')[:-1] filename = file.stem for directory in directories: if directory not in elements_ref: - if directory not in ignored_directories: - elements_ref[directory] = {} + elements_ref[directory] = {} elements_ref = elements_ref[directory] elements_ref[filename] = filename diff --git a/resources/resources_autogenerated.qrc b/resources/resources_autogenerated.qrc index 6fc9e6f68..d764d54c1 100644 --- a/resources/resources_autogenerated.qrc +++ b/resources/resources_autogenerated.qrc @@ -1,64 +1,66 @@ - pajaDank.png - icon.png - emojidata.txt + chatterino2.icns contributors.txt - error.png emoji.json + emojidata.txt + error.png icon.ico + icon.png + pajaDank.png tlds.txt - chatterino2.icns - qss/settings.qss - __pycache__/_generate_resources.cpython-36.pyc - licenses/fmt_bsd2.txt - licenses/openssl.txt - licenses/pajlada_settings.txt - licenses/qt_lgpl-3.0.txt - licenses/pajlada_signals.txt - licenses/rapidjson.txt - licenses/websocketpp.txt - licenses/boost_boost.txt - licenses/libcommuni_BSD3.txt - settings/aboutlogo.png - settings/behave.svg - settings/accounts.svg - settings/about.svg - settings/notifications.svg - settings/commands.svg - settings/theme.svg - split/up.png - split/left.png - split/move.png - split/right.png - split/down.png - buttons/unban.png - buttons/menuDark.png - buttons/mod.png - buttons/emote.svg - buttons/modModeEnabled2.png + avatars/fourtf.png + avatars/pajlada.png + buttons/addSplitDark.png buttons/ban.png - buttons/unmod.png + buttons/banRed.png + buttons/emote.svg buttons/emoteDark.svg - buttons/updateError.png + buttons/menuDark.png + buttons/menuLight.png + buttons/mod.png buttons/modModeDisabled.png buttons/modModeDisabled2.png buttons/modModeEnabled.png - buttons/menuLight.png - buttons/update.png + buttons/modModeEnabled2.png buttons/timeout.png - buttons/banRed.png + buttons/unban.png + buttons/unmod.png + buttons/update.png + buttons/updateError.png + licenses/boost_boost.txt + licenses/emoji-data-source.txt + licenses/fmt_bsd2.txt + licenses/libcommuni_BSD3.txt + licenses/openssl.txt + licenses/pajlada_settings.txt + licenses/pajlada_signals.txt + licenses/qt_lgpl-3.0.txt + licenses/rapidjson.txt + licenses/websocketpp.txt + qss/settings.qss + settings/about.svg + settings/aboutlogo.png + settings/accounts.svg + settings/behave.svg + settings/commands.svg + settings/emote.svg + settings/notifications.svg + settings/theme.svg sounds/ping2.wav - twitch/prime.png - twitch/verified.png + split/down.png + split/left.png + split/move.png + split/right.png + split/up.png twitch/admin.png + twitch/broadcaster.png + twitch/cheer1.png + twitch/globalmod.png + twitch/moderator.png + twitch/prime.png + twitch/staff.png twitch/subscriber.png twitch/turbo.png - twitch/moderator.png - twitch/globalmod.png - twitch/cheer1.png - twitch/broadcaster.png - twitch/staff.png - avatars/fourtf.png - avatars/pajlada.png + twitch/verified.png \ No newline at end of file diff --git a/src/Application.cpp b/src/Application.cpp index bce4cd504..2f8c02587 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -10,6 +10,7 @@ #include "debug/Log.hpp" #include "messages/MessageBuilder.hpp" #include "providers/bttv/BttvEmotes.hpp" +#include "providers/chatterino/ChatterinoBadges.hpp" #include "providers/ffz/FfzEmotes.hpp" #include "providers/twitch/PubsubClient.hpp" #include "providers/twitch/TwitchServer.hpp" @@ -56,6 +57,7 @@ Application::Application(Settings &_settings, Paths &_paths) , taggedUsers(&this->emplace()) , moderationActions(&this->emplace()) , twitch2(&this->emplace()) + , chatterinoBadges(&this->emplace()) , logging(&this->emplace()) { diff --git a/src/Application.hpp b/src/Application.hpp index b7df63d9d..90e2781af 100644 --- a/src/Application.hpp +++ b/src/Application.hpp @@ -28,7 +28,7 @@ class Settings; class Fonts; class Resources2; class Toasts; - +class ChatterinoBadges; class Application { @@ -65,6 +65,7 @@ public: TaggedUsersController *const taggedUsers{}; ModerationActions *const moderationActions{}; TwitchServer *const twitch2{}; + ChatterinoBadges *const chatterinoBadges{}; /*[[deprecated]]*/ Logging *const logging{}; diff --git a/src/autogenerated/ResourcesAutogen.cpp b/src/autogenerated/ResourcesAutogen.cpp index c5035f2e6..9d67608e8 100644 --- a/src/autogenerated/ResourcesAutogen.cpp +++ b/src/autogenerated/ResourcesAutogen.cpp @@ -6,6 +6,7 @@ Resources2::Resources2() { this->avatars.fourtf = QPixmap(":/avatars/fourtf.png"); this->avatars.pajlada = QPixmap(":/avatars/pajlada.png"); + this->buttons.addSplitDark = QPixmap(":/buttons/addSplitDark.png"); this->buttons.ban = QPixmap(":/buttons/ban.png"); this->buttons.banRed = QPixmap(":/buttons/banRed.png"); this->buttons.menuDark = QPixmap(":/buttons/menuDark.png"); diff --git a/src/autogenerated/ResourcesAutogen.hpp b/src/autogenerated/ResourcesAutogen.hpp index c6adb3576..4a6f9fc4b 100644 --- a/src/autogenerated/ResourcesAutogen.hpp +++ b/src/autogenerated/ResourcesAutogen.hpp @@ -3,8 +3,7 @@ namespace chatterino { -class Resources2 : public Singleton -{ +class Resources2 : public Singleton { public: Resources2(); @@ -13,6 +12,7 @@ public: QPixmap pajlada; } avatars; struct { + QPixmap addSplitDark; QPixmap ban; QPixmap banRed; QPixmap menuDark; diff --git a/src/common/Channel.cpp b/src/common/Channel.cpp index f9ac3ac50..ac1d6ea56 100644 --- a/src/common/Channel.cpp +++ b/src/common/Channel.cpp @@ -143,7 +143,8 @@ void Channel::addOrReplaceTimeout(MessagePtr message) if (s->flags.hasNone({MessageFlag::Timeout, MessageFlag::Untimeout}) && s->loginName == message->timeoutUser) { // FOURTF: disabled for now - // s->flags.EnableFlag(MessageFlag::Disabled); + // PAJLADA: Shitty solution described in Message.hpp + s->flags.set(MessageFlag::Disabled); } } diff --git a/src/common/SignalVectorModel.hpp b/src/common/SignalVectorModel.hpp index 237727d0d..4a56913b4 100644 --- a/src/common/SignalVectorModel.hpp +++ b/src/common/SignalVectorModel.hpp @@ -124,7 +124,7 @@ public: rowItem.items[column]->setData(value, role); if (rowItem.isCustomRow) { - this->customRowSetData(rowItem.items, column, value, role); + this->customRowSetData(rowItem.items, column, value, role, row); } else { int vecRow = this->getVectorIndexFromModelIndex(row); this->vector_->removeItem(vecRow, this); @@ -230,7 +230,8 @@ protected: } virtual void customRowSetData(const std::vector &row, - int column, const QVariant &value, int role) + int column, const QVariant &value, int role, + int rowIndex) { } diff --git a/src/controllers/highlights/HighlightModel.cpp b/src/controllers/highlights/HighlightModel.cpp index 827e23a4a..0ba232b37 100644 --- a/src/controllers/highlights/HighlightModel.cpp +++ b/src/controllers/highlights/HighlightModel.cpp @@ -36,36 +36,67 @@ void HighlightModel::getRowFromItem(const HighlightPhrase &item, void HighlightModel::afterInit() { - std::vector row = this->createRow(); - setBoolItem(row[0], getSettings()->enableHighlightsSelf.getValue(), true, + std::vector usernameRow = this->createRow(); + setBoolItem(usernameRow[0], getSettings()->enableSelfHighlight.getValue(), + true, false); + usernameRow[0]->setData("Your username (automatic)", Qt::DisplayRole); + setBoolItem(usernameRow[1], + getSettings()->enableSelfHighlightTaskbar.getValue(), true, false); - row[0]->setData("Your username (automatic)", Qt::DisplayRole); - setBoolItem(row[1], getSettings()->enableHighlightTaskbar.getValue(), true, + setBoolItem(usernameRow[2], + getSettings()->enableSelfHighlightSound.getValue(), true, false); - setBoolItem(row[2], getSettings()->enableHighlightSound.getValue(), true, + usernameRow[3]->setFlags(0); + this->insertCustomRow(usernameRow, 0); + std::vector whisperRow = this->createRow(); + setBoolItem(whisperRow[0], getSettings()->enableWhisperHighlight.getValue(), + true, false); + whisperRow[0]->setData("Whispers", Qt::DisplayRole); + setBoolItem(whisperRow[1], + getSettings()->enableWhisperHighlightTaskbar.getValue(), true, false); - row[3]->setFlags(0); - this->insertCustomRow(row, 0); + setBoolItem(whisperRow[2], + getSettings()->enableWhisperHighlightSound.getValue(), true, + false); + whisperRow[3]->setFlags(0); + this->insertCustomRow(whisperRow, 1); } void HighlightModel::customRowSetData(const std::vector &row, int column, const QVariant &value, - int role) + int role, int rowIndex) { switch (column) { case 0: { if (role == Qt::CheckStateRole) { - getSettings()->enableHighlightsSelf.setValue(value.toBool()); + if (rowIndex == 0) { + getSettings()->enableSelfHighlight.setValue(value.toBool()); + } else if (rowIndex == 1) { + getSettings()->enableWhisperHighlight.setValue( + value.toBool()); + } } } break; case 1: { if (role == Qt::CheckStateRole) { - getSettings()->enableHighlightTaskbar.setValue(value.toBool()); + if (rowIndex == 0) { + getSettings()->enableSelfHighlightTaskbar.setValue( + value.toBool()); + } else if (rowIndex == 1) { + getSettings()->enableWhisperHighlightTaskbar.setValue( + value.toBool()); + } } } break; case 2: { if (role == Qt::CheckStateRole) { - getSettings()->enableHighlightSound.setValue(value.toBool()); + if (rowIndex == 0) { + getSettings()->enableSelfHighlightSound.setValue( + value.toBool()); + } else if (rowIndex == 1) { + getSettings()->enableWhisperHighlightSound.setValue( + value.toBool()); + } } } break; case 3: { diff --git a/src/controllers/highlights/HighlightModel.hpp b/src/controllers/highlights/HighlightModel.hpp index b42246167..563706246 100644 --- a/src/controllers/highlights/HighlightModel.hpp +++ b/src/controllers/highlights/HighlightModel.hpp @@ -26,8 +26,8 @@ protected: virtual void afterInit() override; virtual void customRowSetData(const std::vector &row, - int column, const QVariant &value, - int role) override; + int column, const QVariant &value, int role, + int rowIndex) override; friend class HighlightController; }; diff --git a/src/messages/Image.cpp b/src/messages/Image.cpp index e6dcfbc8b..01edc9526 100644 --- a/src/messages/Image.cpp +++ b/src/messages/Image.cpp @@ -310,6 +310,14 @@ void Image::load() return Success; }); + req.onError([that = this, weak = weakOf(this)](auto result) -> bool { + auto shared = weak.lock(); + if (!shared) return false; + + shared->empty_ = true; + + return true; + }); req.execute(); } diff --git a/src/messages/Message.hpp b/src/messages/Message.hpp index d6a79fcdd..6b50e8399 100644 --- a/src/messages/Message.hpp +++ b/src/messages/Message.hpp @@ -34,7 +34,13 @@ struct Message : boost::noncopyable { Message(); ~Message(); - MessageFlags flags; + // Making this a mutable means that we can update a messages flags, + // while still keeping Message constant. This means that a message's flag + // can be updated without the renderer being made aware, which might be bad. + // This is a temporary effort until we can figure out what the right + // const-correct way to deal with this is. + // This might bring race conditions with it + mutable MessageFlags flags; QTime parseTime; QString id; QString searchText; diff --git a/src/messages/MessageElement.cpp b/src/messages/MessageElement.cpp index b9271d2ed..49b2cadbc 100644 --- a/src/messages/MessageElement.cpp +++ b/src/messages/MessageElement.cpp @@ -61,6 +61,12 @@ MessageElementFlags MessageElement::getFlags() const return this->flags_; } +MessageElement *MessageElement::updateLink() +{ + this->linkChanged.invoke(); + return this; +} + // IMAGE ImageElement::ImageElement(ImagePtr image, MessageElementFlags flags) : MessageElement(flags) @@ -152,6 +158,15 @@ void TextElement::addToContainer(MessageLayoutContainer &container, color, this->style_, container.getScale())) ->setLink(this->getLink()); e->setTrailingSpace(trailingSpace); + + // If URL link was changed, + // Should update it in MessageLayoutElement too! + if (this->getLink().type == Link::Url) { + this->linkChanged.connect( + [this, e]() { + e->setLink(this->getLink()); + }); + } return e; }; diff --git a/src/messages/MessageElement.hpp b/src/messages/MessageElement.hpp index 8d96d1135..e82bee5e0 100644 --- a/src/messages/MessageElement.hpp +++ b/src/messages/MessageElement.hpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace chatterino { class Channel; @@ -128,6 +129,7 @@ public: const Link &getLink() const; bool hasTrailingSpace() const; MessageElementFlags getFlags() const; + MessageElement *updateLink(); virtual void addToContainer(MessageLayoutContainer &container, MessageElementFlags flags) = 0; @@ -135,6 +137,7 @@ public: protected: MessageElement(MessageElementFlags flags); bool trailingSpace = true; + pajlada::Signals::NoArgSignal linkChanged; private: Link link_; diff --git a/src/providers/LinkResolver.cpp b/src/providers/LinkResolver.cpp new file mode 100644 index 000000000..f48834ef3 --- /dev/null +++ b/src/providers/LinkResolver.cpp @@ -0,0 +1,48 @@ +#include "providers/LinkResolver.hpp" + +#include "common/Common.hpp" +#include "common/NetworkRequest.hpp" +#include "messages/Link.hpp" +#include "singletons/Settings.hpp" + +#include + +namespace chatterino { + +void LinkResolver::getLinkInfo(const QString url, + std::function successCallback) +{ + QString requestUrl("https://braize.pajlada.com/chatterino/link_resolver/" + + QUrl::toPercentEncoding(url, "", "/:")); + + NetworkRequest request(requestUrl); + request.setCaller(QThread::currentThread()); + request.setTimeout(30000); + request.onSuccess([successCallback, url](auto result) mutable -> Outcome { + auto root = result.parseJson(); + auto statusCode = root.value("status").toInt(); + QString response = QString(); + QString linkString = url; + if (statusCode == 200) { + response = root.value("tooltip").toString(); + if (getSettings()->enableUnshortLinks) { + linkString = root.value("link").toString(); + } + } else { + response = root.value("message").toString(); + } + successCallback(QUrl::fromPercentEncoding(response.toUtf8()), Link(Link::Url, linkString)); + + return Success; + }); + + request.onError([successCallback, url](auto result) { + successCallback("No link info found", Link(Link::Url, url)); + + return true; + }); + + request.execute(); +} + +} // namespace chatterino diff --git a/src/providers/LinkResolver.hpp b/src/providers/LinkResolver.hpp new file mode 100644 index 000000000..919f0c3bf --- /dev/null +++ b/src/providers/LinkResolver.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +#include "messages/Link.hpp" + +namespace chatterino { + +class LinkResolver +{ +public: + static void getLinkInfo(const QString url, + std::function callback); + +private: +}; + +} // namespace chatterino diff --git a/src/providers/chatterino/ChatterinoBadges.cpp b/src/providers/chatterino/ChatterinoBadges.cpp index 37dc8f6f0..28bb57f3e 100644 --- a/src/providers/chatterino/ChatterinoBadges.cpp +++ b/src/providers/chatterino/ChatterinoBadges.cpp @@ -5,8 +5,18 @@ #include #include #include "common/NetworkRequest.hpp" +#include "common/Outcome.hpp" +#include "messages/Emote.hpp" + +#include + +#include namespace chatterino { +void ChatterinoBadges::initialize(Settings &settings, Paths &paths) +{ + this->loadChatterinoBadges(); +} ChatterinoBadges::ChatterinoBadges() { @@ -14,41 +24,41 @@ ChatterinoBadges::ChatterinoBadges() boost::optional ChatterinoBadges::getBadge(const UserName &username) { + auto it = badgeMap.find(username.string); + if (it != badgeMap.end()) { + return emotes[it->second]; + } return boost::none; - // return this->badges.access()->get(username); } void ChatterinoBadges::loadChatterinoBadges() { - // static QString url("https://fourtf.com/chatterino/badges.json"); + static QUrl url("https://fourtf.com/chatterino/badges.json"); - // NetworkRequest req(url); - // req.setCaller(QThread::currentThread()); + NetworkRequest req(url); + req.setCaller(QThread::currentThread()); - // req.onSuccess([this](auto result) { - // auto jsonRoot = result.parseJson(); - // auto badges = this->badges.access(); - // auto replacement = badges->makeReplacment(); + req.onSuccess([this](auto result) -> Outcome { + auto jsonRoot = result.parseJson(); + int index = 0; + for (const auto &jsonBadge_ : jsonRoot.value("badges").toArray()) { + auto jsonBadge = jsonBadge_.toObject(); + auto emote = Emote{ + EmoteName{}, ImageSet{Url{jsonBadge.value("image").toString()}}, + Tooltip{jsonBadge.value("tooltip").toString()}, Url{}}; - // for (auto jsonBadge_ : jsonRoot.value("badges").toArray()) { - // auto jsonBadge = jsonBadge_.toObject(); + emotes.push_back(std::make_shared(std::move(emote))); - // auto emote = Emote{ - // EmoteName{}, - // ImageSet{Url{jsonBadge.value("image").toString()}}, - // Tooltip{jsonBadge.value("tooltip").toString()}, Url{}}; + for (const auto &user : jsonBadge.value("users").toArray()) { + badgeMap[user.toString()] = index; + } + ++index; + } - // for (auto jsonUser : jsonBadge.value("users").toArray()) { - // replacement.add(UserName{jsonUser.toString()}, - // std::move(emote)); - // } - // } + return Success; + }); - // replacement.apply(); - // return Success; - //}); - - // req.execute(); + req.execute(); } } // namespace chatterino diff --git a/src/providers/chatterino/ChatterinoBadges.hpp b/src/providers/chatterino/ChatterinoBadges.hpp index 89870fd16..b77e2de4c 100644 --- a/src/providers/chatterino/ChatterinoBadges.hpp +++ b/src/providers/chatterino/ChatterinoBadges.hpp @@ -1,25 +1,30 @@ #pragma once #include +#include #include "common/Aliases.hpp" +#include +#include + namespace chatterino { struct Emote; using EmotePtr = std::shared_ptr; -class ChatterinoBadges +class ChatterinoBadges : public Singleton { public: + virtual void initialize(Settings &settings, Paths &paths) override; ChatterinoBadges(); boost::optional getBadge(const UserName &username); private: void loadChatterinoBadges(); - - // UniqueAccess> badges; + std::map badgeMap; + std::vector emotes; }; } // namespace chatterino diff --git a/src/providers/twitch/IrcMessageHandler.cpp b/src/providers/twitch/IrcMessageHandler.cpp index 9aabbc91d..3ee2a57b6 100644 --- a/src/providers/twitch/IrcMessageHandler.cpp +++ b/src/providers/twitch/IrcMessageHandler.cpp @@ -66,8 +66,8 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message, builder->flags.unset(MessageFlag::Highlighted); } - auto highlighted = builder->flags.has(MessageFlag::Highlighted); auto msg = builder.build(); + auto highlighted = msg->flags.has(MessageFlag::Highlighted); if (!isSub) { if (highlighted) { @@ -221,10 +221,10 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message) false); if (!builder.isIgnored()) { - app->twitch.server->lastUserThatWhisperedMe.set(builder.userName); - MessagePtr _message = builder.build(); + app->twitch.server->lastUserThatWhisperedMe.set(builder.userName); + if (_message->flags.has(MessageFlag::Highlighted)) { app->twitch.server->mentionsChannel->addMessage(_message); } diff --git a/src/providers/twitch/TwitchChannel.cpp b/src/providers/twitch/TwitchChannel.cpp index fecaffe0c..ee57e3e5f 100644 --- a/src/providers/twitch/TwitchChannel.cpp +++ b/src/providers/twitch/TwitchChannel.cpp @@ -633,12 +633,13 @@ void TwitchChannel::refreshBadges() auto jsonVersion = jsonVersion_->toObject(); auto emote = std::make_shared(Emote{ EmoteName{}, - ImageSet{Image::fromUrl( - {jsonVersion["image_url_1x"].toString()}), - Image::fromUrl( - {jsonVersion["image_url_2x"].toString()}), - Image::fromUrl( - {jsonVersion["image_url_4x"].toString()})}, + ImageSet{ + Image::fromUrl({jsonVersion["image_url_1x"].toString()}, + 1), + Image::fromUrl({jsonVersion["image_url_2x"].toString()}, + .5), + Image::fromUrl({jsonVersion["image_url_4x"].toString()}, + .25)}, Tooltip{jsonRoot["description"].toString()}, Url{jsonVersion["clickURL"].toString()}}); diff --git a/src/providers/twitch/TwitchMessageBuilder.cpp b/src/providers/twitch/TwitchMessageBuilder.cpp index a6647d88e..13f02ae5d 100644 --- a/src/providers/twitch/TwitchMessageBuilder.cpp +++ b/src/providers/twitch/TwitchMessageBuilder.cpp @@ -6,6 +6,8 @@ #include "controllers/ignores/IgnoreController.hpp" #include "debug/Log.hpp" #include "messages/Message.hpp" +#include "providers/LinkResolver.hpp" +#include "providers/chatterino/ChatterinoBadges.hpp" #include "providers/twitch/TwitchBadges.hpp" #include "providers/twitch/TwitchChannel.hpp" #include "singletons/Emotes.hpp" @@ -268,12 +270,29 @@ void TwitchMessageBuilder::addTextOrEmoji(const QString &string_) link = Link(Link::Url, linkString); textColor = MessageColor(MessageColor::Link); - this->emplace(lowercaseLinkString, - MessageElementFlag::LowercaseLink, textColor) - ->setLink(link); - this->emplace(string, MessageElementFlag::OriginalLink, - textColor) - ->setLink(link); + auto linkMELowercase = + this->emplace(lowercaseLinkString, + MessageElementFlag::LowercaseLink, + textColor) + ->setLink(link); + auto linkMEOriginal = + this->emplace(string, MessageElementFlag::OriginalLink, + textColor) + ->setLink(link); + + LinkResolver::getLinkInfo( + linkString, [linkMELowercase, linkMEOriginal, linkString] + (QString tooltipText, Link originalLink) { + if (!tooltipText.isEmpty()) { + linkMELowercase->setTooltip(tooltipText); + linkMEOriginal->setTooltip(tooltipText); + } + if (originalLink.value != linkString && + !originalLink.value.isEmpty()) { + linkMELowercase->setLink(originalLink)->updateLink(); + linkMEOriginal->setLink(originalLink)->updateLink(); + } + }); } // if (!linkString.isEmpty()) { @@ -507,10 +526,10 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg) std::vector userHighlights = app->highlights->highlightedUsers.getVector(); - if (getSettings()->enableHighlightsSelf && currentUsername.size() > 0) { + if (getSettings()->enableSelfHighlight && currentUsername.size() > 0) { HighlightPhrase selfHighlight( - currentUsername, getSettings()->enableHighlightTaskbar, - getSettings()->enableHighlightSound, false); + currentUsername, getSettings()->enableSelfHighlightTaskbar, + getSettings()->enableSelfHighlightSound, false); activeHighlights.emplace_back(std::move(selfHighlight)); } @@ -564,6 +583,15 @@ void TwitchMessageBuilder::parseHighlights(bool isPastMsg) } } } + if (this->args.isReceivedWhisper && + getSettings()->enableWhisperHighlight) { + if (getSettings()->enableWhisperHighlightTaskbar) { + doAlert = true; + } + if (getSettings()->enableWhisperHighlightSound) { + playSound = true; + } + } this->message().flags.set(MessageFlag::Highlighted, doHighlight); @@ -769,20 +797,12 @@ void TwitchMessageBuilder::appendTwitchBadges() void TwitchMessageBuilder::appendChatterinoBadges() { - // auto app = getApp(); - - // auto &badges = app->resources->chatterinoBadges; - // auto it = badges.find(this->userName.toStdString()); - - // if (it == badges.end()) { - // return; - // } - - // const auto badge = it->second; - - // this->emplace(badge->image, - // MessageElementFlag::BadgeChatterino) - // ->setTooltip(QString::fromStdString(badge->tooltip)); + auto chatterinoBadgePtr = + getApp()->chatterinoBadges->getBadge({this->userName}); + if (chatterinoBadgePtr) { + this->emplace(*chatterinoBadgePtr, + MessageElementFlag::BadgeChatterino); + } } Outcome TwitchMessageBuilder::tryParseCheermote(const QString &string) diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index 31e9881bc..87b95eda6 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -29,7 +29,6 @@ public: "/appearance/enableAnimationsWhenFocused", false}; QStringSetting timestampFormat = {"/appearance/messages/timestampFormat", "h:mm"}; - BoolSetting showBadges = {"/appearance/messages/showBadges", true}; BoolSetting showLastMessageIndicator = { "/appearance/messages/showLastMessageIndicator", false}; IntSetting lastMessagePattern = {"/appearance/messages/lastMessagePattern", @@ -67,6 +66,16 @@ public: // BoolSetting useCustomWindowFrame = {"/appearance/useCustomWindowFrame", // false}; + // Badges + BoolSetting showBadgesGlobalAuthority = { + "/appearance/badges/GlobalAuthority", true}; + BoolSetting showBadgesChannelAuthority = { + "/appearance/badges/ChannelAuthority", true}; + BoolSetting showBadgesSubscription = {"/appearance/badges/subscription", + true}; + BoolSetting showBadgesVanity = {"/appearance/badges/vanity", true}; + BoolSetting showBadgesChatterino = {"/appearance/badges/chatterino", true}; + /// Behaviour BoolSetting allowDuplicateMessages = {"/behaviour/allowDuplicateMessages", true}; @@ -103,6 +112,8 @@ public: /// Links BoolSetting linksDoubleClickOnly = {"/links/doubleClickToOpen", false}; + BoolSetting enableLinkInfoTooltip = {"/links/linkInfoTooltip", false}; + BoolSetting enableUnshortLinks = {"/links/unshortLinks", false}; BoolSetting enableLowercaseLink = {"/links/linkLowercase", true}; /// Ingored Users @@ -114,12 +125,19 @@ public: /// Highlighting // BoolSetting enableHighlights = {"/highlighting/enabled", true}; - BoolSetting enableHighlightsSelf = {"/highlighting/nameIsHighlightKeyword", - true}; - BoolSetting enableHighlightSound = {"/highlighting/enableSound", true}; - BoolSetting enableHighlightTaskbar = {"/highlighting/enableTaskbarFlashing", - true}; BoolSetting customHighlightSound = {"/highlighting/useCustomSound", false}; + BoolSetting enableSelfHighlight = { + "/highlighting/selfHighlight/nameIsHighlightKeyword", true}; + BoolSetting enableSelfHighlightSound = { + "/highlighting/selfHighlight/enableSound", true}; + BoolSetting enableSelfHighlightTaskbar = { + "/highlighting/selfHighlight/enableTaskbarFlashing", true}; + BoolSetting enableWhisperHighlight = { + "/highlighting/whisperHighlight/whispersHighlighted", true}; + BoolSetting enableWhisperHighlightSound = { + "/highlighting/whisperHighlight/enableSound", false}; + BoolSetting enableWhisperHighlightTaskbar = { + "/highlighting/whisperHighlight/enableTaskbarFlashing", false}; /// Logging BoolSetting enableLogging = {"/logging/enabled", false}; diff --git a/src/singletons/WindowManager.cpp b/src/singletons/WindowManager.cpp index 02b711f7b..dc6f9c63a 100644 --- a/src/singletons/WindowManager.cpp +++ b/src/singletons/WindowManager.cpp @@ -69,7 +69,11 @@ WindowManager::WindowManager() auto settings = getSettings(); this->wordFlagsListener_.addSetting(settings->showTimestamps); - this->wordFlagsListener_.addSetting(settings->showBadges); + this->wordFlagsListener_.addSetting(settings->showBadgesGlobalAuthority); + this->wordFlagsListener_.addSetting(settings->showBadgesChannelAuthority); + this->wordFlagsListener_.addSetting(settings->showBadgesSubscription); + this->wordFlagsListener_.addSetting(settings->showBadgesVanity); + this->wordFlagsListener_.addSetting(settings->showBadgesChatterino); this->wordFlagsListener_.addSetting(settings->enableBttvEmotes); this->wordFlagsListener_.addSetting(settings->enableEmojis); this->wordFlagsListener_.addSetting(settings->enableFfzEmotes); @@ -114,7 +118,15 @@ void WindowManager::updateWordTypeMask() : MEF::BitsStatic); // badges - flags.set(settings->showBadges ? MEF::Badges : MEF::None); + flags.set(settings->showBadgesGlobalAuthority ? MEF::BadgeGlobalAuthority + : MEF::None); + flags.set(settings->showBadgesChannelAuthority ? MEF::BadgeChannelAuthority + : MEF::None); + flags.set(settings->showBadgesSubscription ? MEF::BadgeSubscription + : MEF::None); + flags.set(settings->showBadgesVanity ? MEF::BadgeVanity : MEF::None); + flags.set(settings->showBadgesChatterino ? MEF::BadgeChatterino + : MEF::None); // username flags.set(MEF::Username); diff --git a/src/widgets/Notebook.hpp b/src/widgets/Notebook.hpp index 022c075a2..b0cb22c9d 100644 --- a/src/widgets/Notebook.hpp +++ b/src/widgets/Notebook.hpp @@ -61,9 +61,9 @@ protected: private: struct Item { - NotebookTab *tab; - QWidget *page; - QWidget *selectedWidget = nullptr; + NotebookTab *tab{}; + QWidget *page{}; + QWidget *selectedWidget{}; }; bool containsPage(QWidget *page); diff --git a/src/widgets/Scrollbar.cpp b/src/widgets/Scrollbar.cpp index 879aa9af2..e412d9e25 100644 --- a/src/widgets/Scrollbar.cpp +++ b/src/widgets/Scrollbar.cpp @@ -55,6 +55,11 @@ void Scrollbar::unpauseHighlights() this->highlightsPaused_ = false; } +void Scrollbar::clearHighlights() +{ + this->highlights_.clear(); +} + LimitedQueueSnapshot Scrollbar::getHighlightSnapshot() { if (!this->highlightsPaused_) { diff --git a/src/widgets/Scrollbar.hpp b/src/widgets/Scrollbar.hpp index 114a1dfe6..09cbeb2c5 100644 --- a/src/widgets/Scrollbar.hpp +++ b/src/widgets/Scrollbar.hpp @@ -27,6 +27,7 @@ public: void pauseHighlights(); void unpauseHighlights(); + void clearHighlights(); void scrollToBottom(bool animate = false); bool isAtBottom() const; diff --git a/src/widgets/TooltipWidget.cpp b/src/widgets/TooltipWidget.cpp index 084f729ac..5ee0b1daf 100644 --- a/src/widgets/TooltipWidget.cpp +++ b/src/widgets/TooltipWidget.cpp @@ -87,6 +87,11 @@ void TooltipWidget::setText(QString text) this->displayText_->setText(text); } +void TooltipWidget::setWordWrap(bool wrap) +{ + this->displayText_->setWordWrap(wrap); +} + void TooltipWidget::changeEvent(QEvent *) { // clear parents event diff --git a/src/widgets/TooltipWidget.hpp b/src/widgets/TooltipWidget.hpp index 7d8c95dc2..2228e5662 100644 --- a/src/widgets/TooltipWidget.hpp +++ b/src/widgets/TooltipWidget.hpp @@ -19,6 +19,7 @@ public: virtual ~TooltipWidget() override; void setText(QString text); + void setWordWrap(bool wrap); #ifdef USEWINSDK void raise(); diff --git a/src/widgets/Window.cpp b/src/widgets/Window.cpp index 6df88b402..e993fe671 100644 --- a/src/widgets/Window.cpp +++ b/src/widgets/Window.cpp @@ -41,7 +41,8 @@ Window::Window(WindowType type) this->addShortcuts(); this->addLayout(); - getApp()->accounts->twitch.currentUserChanged.connect( + this->signalHolder_.managedConnect( + getApp()->accounts->twitch.currentUserChanged, [this] { this->onAccountSelected(); }); this->onAccountSelected(); diff --git a/src/widgets/dialogs/EmotePopup.cpp b/src/widgets/dialogs/EmotePopup.cpp index 2fe6bb28a..0f3375121 100644 --- a/src/widgets/dialogs/EmotePopup.cpp +++ b/src/widgets/dialogs/EmotePopup.cpp @@ -29,11 +29,17 @@ namespace { builder->flags.set(MessageFlag::Centered); builder->flags.set(MessageFlag::DisableCompactEmotes); - for (const auto &emote : map) { - builder - .emplace(emote.second, - MessageElementFlag::AlwaysShow) - ->setLink(Link(Link::InsertText, emote.first.string)); + if (!map.empty()) { + for (const auto &emote : map) { + builder + .emplace(emote.second, + MessageElementFlag::AlwaysShow) + ->setLink(Link(Link::InsertText, emote.first.string)); + } + } else { + builder.emplace("no emotes available", + MessageElementFlag::Text, + MessageColor::System); } return builder.release(); @@ -106,7 +112,7 @@ void EmotePopup::loadChannel(ChannelPtr _channel) { BenchmarkGuard guard("loadChannel"); - this->setWindowTitle("Emotes from " + _channel->getName()); + this->setWindowTitle("Emotes in #" + _channel->getName()); auto twitchChannel = dynamic_cast(_channel.get()); if (twitchChannel == nullptr) return; @@ -139,6 +145,16 @@ void EmotePopup::loadChannel(ChannelPtr _channel) this->globalEmotesView_->setChannel(globalChannel); this->subEmotesView_->setChannel(subChannel); this->channelEmotesView_->setChannel(channelChannel); + + if (subChannel->getMessageSnapshot().getLength() == 0) { + MessageBuilder builder; + builder->flags.set(MessageFlag::Centered); + builder->flags.set(MessageFlag::DisableCompactEmotes); + builder.emplace("no subscription emotes available", + MessageElementFlag::Text, + MessageColor::System); + subChannel->addMessage(builder.release()); + } } void EmotePopup::loadEmojis() diff --git a/src/widgets/dialogs/SettingsDialog.cpp b/src/widgets/dialogs/SettingsDialog.cpp index 854514e4d..d21008bd1 100644 --- a/src/widgets/dialogs/SettingsDialog.cpp +++ b/src/widgets/dialogs/SettingsDialog.cpp @@ -35,6 +35,7 @@ SettingsDialog::SettingsDialog() this->scaleChangedEvent(this->getScale()); this->overrideBackgroundColor_ = QColor("#282828"); + this->themeChangedEvent(); } void SettingsDialog::initUi() @@ -187,7 +188,7 @@ void SettingsDialog::themeChangedEvent() BaseWindow::themeChangedEvent(); QPalette palette; - palette.setColor(QPalette::Background, QColor("#444")); + palette.setColor(QPalette::Background, QColor("#222")); this->setPalette(palette); } diff --git a/src/widgets/helper/Button.cpp b/src/widgets/helper/Button.cpp index 2e035c02c..948fbc6d2 100644 --- a/src/widgets/helper/Button.cpp +++ b/src/widgets/helper/Button.cpp @@ -62,6 +62,18 @@ bool Button::getEnable() const return this->enabled_; } +void Button::setEnableMargin(bool value) +{ + this->enableMargin_ = value; + + this->update(); +} + +bool Button::getEnableMargin() const +{ + return this->enableMargin_; +} + qreal Button::getCurrentDimAmount() const { return this->dimPixmap_ && !this->mouseOver_ ? 0.7 : 1; @@ -105,7 +117,7 @@ void Button::paintEvent(QPaintEvent *) } QRect rect = this->rect(); - int s = int(6 * this->getScale()); + int s = this->enableMargin_ ? int(6 * this->getScale()) : 0; rect.moveLeft(s); rect.setRight(rect.right() - s - s); diff --git a/src/widgets/helper/Button.hpp b/src/widgets/helper/Button.hpp index e75385bcd..b7d3bffa2 100644 --- a/src/widgets/helper/Button.hpp +++ b/src/widgets/helper/Button.hpp @@ -41,6 +41,9 @@ public: void setEnable(bool value); bool getEnable() const; + void setEnableMargin(bool value); + bool getEnableMargin() const; + void setBorderColor(const QColor &color); const QColor &getBorderColor() const; @@ -73,6 +76,7 @@ private: QColor borderColor_{}; QPixmap pixmap_{}; bool dimPixmap_{true}; + bool enableMargin_{true}; QPoint mousePos_{}; double hoverMultiplier_{0.0}; QTimer effectTimer_{}; diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 7a6276e17..b97558acb 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -305,6 +305,7 @@ void ChannelView::clearMessages() { // Clear all stored messages in this chat widget this->messages.clear(); + this->scrollBar_->clearHighlights(); // Layout chat widget messages, and force an update regardless if there are // no messages @@ -870,11 +871,15 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) return; } const auto &tooltip = hoverLayoutElement->getCreator().getTooltip(); + bool isLinkValid = hoverLayoutElement->getLink().isValid(); if (tooltip.isEmpty()) { tooltipWidget->hide(); + } else if (isLinkValid && !getSettings()->enableLinkInfoTooltip) { + tooltipWidget->hide(); } else { tooltipWidget->moveTo(this, event->globalPos()); + tooltipWidget->setWordWrap(isLinkValid); tooltipWidget->setText(tooltip); tooltipWidget->adjustSize(); tooltipWidget->show(); @@ -882,7 +887,7 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) } // check if word has a link - if (hoverLayoutElement->getLink().isValid()) { + if (isLinkValid) { this->setCursor(Qt::PointingHandCursor); } else { this->setCursor(Qt::ArrowCursor); diff --git a/src/widgets/helper/NotebookTab.cpp b/src/widgets/helper/NotebookTab.cpp index 3ccff8bb3..4225d904b 100644 --- a/src/widgets/helper/NotebookTab.cpp +++ b/src/widgets/helper/NotebookTab.cpp @@ -313,6 +313,8 @@ void NotebookTab::paintEvent(QPaintEvent *) if (this->shouldDrawXButton()) { QRect xRect = this->getXRect(); if (!xRect.isNull()) { + if (this->selected_) xRect.moveTop(xRect.top() - 1); + painter.setBrush(QColor("#fff")); if (this->mouseOverX_) { @@ -474,7 +476,7 @@ QRect NotebookTab::getXRect() float s = this->getScale(); return QRect(this->width() - static_cast(20 * s), - static_cast(6 * s), static_cast(16 * s), + static_cast(9 * s), static_cast(16 * s), static_cast(16 * s)); } diff --git a/src/widgets/settingspages/FeelPage.cpp b/src/widgets/settingspages/FeelPage.cpp index 78b41c45d..cab08ebc3 100644 --- a/src/widgets/settingspages/FeelPage.cpp +++ b/src/widgets/settingspages/FeelPage.cpp @@ -50,6 +50,12 @@ FeelPage::FeelPage() form->addRow("Links:", this->createCheckBox("Open links only on double click", getSettings()->linksDoubleClickOnly)); + form->addRow("", + this->createCheckBox("Show link info in tooltips", + getSettings()->enableLinkInfoTooltip)); + form->addRow("", + this->createCheckBox("Auto unshort links (requires restart)", + getSettings()->enableUnshortLinks)); } layout->addSpacing(16); diff --git a/src/widgets/settingspages/KeyboardSettingsPage.cpp b/src/widgets/settingspages/KeyboardSettingsPage.cpp index fb28ae877..e66a984bf 100644 --- a/src/widgets/settingspages/KeyboardSettingsPage.cpp +++ b/src/widgets/settingspages/KeyboardSettingsPage.cpp @@ -41,6 +41,8 @@ KeyboardSettingsPage::KeyboardSettingsPage() form->addRow(new QLabel("Ctrl + R"), new QLabel("Change channel")); form->addRow(new QLabel("Ctrl + F"), new QLabel("Search in current channel")); + form->addRow(new QLabel("Ctrl + E"), + new QLabel("Open Emote menu")); } } // namespace chatterino diff --git a/src/widgets/settingspages/LookPage.cpp b/src/widgets/settingspages/LookPage.cpp index eb2a9ff1c..ef89a1840 100644 --- a/src/widgets/settingspages/LookPage.cpp +++ b/src/widgets/settingspages/LookPage.cpp @@ -59,6 +59,7 @@ void LookPage::initializeUi() this->addMessageTab(tabs.appendTab(new QVBoxLayout, "Messages")); this->addEmoteTab(tabs.appendTab(new QVBoxLayout, "Emotes")); this->addSplitHeaderTab(tabs.appendTab(new QVBoxLayout, "Split header")); + this->addBadgesTab(tabs.appendTab(new QVBoxLayout, "Badges")); layout->addStretch(1); @@ -145,10 +146,6 @@ void LookPage::addMessageTab(LayoutCreator layout) box->addStretch(1); } - // badges - layout.append( - this->createCheckBox("Show badges", getSettings()->showBadges)); - // -- layout.emplace(false); @@ -270,16 +267,57 @@ void LookPage::addEmoteTab(LayoutCreator layout) void LookPage::addSplitHeaderTab(LayoutCreator layout) { - layout.append(this->createCheckBox("Show viewer count", - getSettings()->showViewerCount)); - layout.append(this->createCheckBox("Show title", getSettings()->showTitle)); - layout.append(this->createCheckBox("Show game", getSettings()->showGame)); layout.append( this->createCheckBox("Show uptime", getSettings()->showUptime)); + layout.append(this->createCheckBox("Show viewer count", + getSettings()->showViewerCount)); + layout.append(this->createCheckBox("Show game", getSettings()->showGame)); + layout.append(this->createCheckBox("Show title", getSettings()->showTitle)); layout->addStretch(1); } +void LookPage::addBadgesTab(LayoutCreator layout) +{ + // layout.append( + // this->createCheckBox(("Show all badges"), getSettings()->showBadges)); + auto fastSelection = layout.emplace(); + { + auto addAll = fastSelection.emplace("Enable all"); + QObject::connect(addAll.getElement(), &QPushButton::clicked, this, [] { + getSettings()->showBadgesGlobalAuthority = true; + getSettings()->showBadgesChannelAuthority = true; + getSettings()->showBadgesSubscription = true; + getSettings()->showBadgesVanity = true; + getSettings()->showBadgesChatterino = true; + }); + auto removeAll = fastSelection.emplace("Disable all"); + QObject::connect(removeAll.getElement(), &QPushButton::clicked, this, + [] { + getSettings()->showBadgesGlobalAuthority = false; + getSettings()->showBadgesChannelAuthority = false; + getSettings()->showBadgesSubscription = false; + getSettings()->showBadgesVanity = false; + getSettings()->showBadgesChatterino = false; + }); + } + layout.emplace(false); + layout.append(this->createCheckBox( + ("Show authorty badges (staff, admin, turbo, etc)"), + getSettings()->showBadgesGlobalAuthority)); + layout.append( + this->createCheckBox(("Show channel badges (broadcaster, moderator)"), + getSettings()->showBadgesChannelAuthority)); + layout.append(this->createCheckBox(("Show subscriber badges "), + getSettings()->showBadgesSubscription)); + layout.append( + this->createCheckBox(("Show vanity badges (prime, bits, subgifter)"), + getSettings()->showBadgesVanity)); + layout.append(this->createCheckBox(("Show chatterino badges"), + getSettings()->showBadgesChatterino)); + layout->addStretch(1); +} + void LookPage::addLastReadMessageIndicatorPatternSelector( LayoutCreator layout) { diff --git a/src/widgets/settingspages/LookPage.hpp b/src/widgets/settingspages/LookPage.hpp index d7f9102ee..a69c3f2f7 100644 --- a/src/widgets/settingspages/LookPage.hpp +++ b/src/widgets/settingspages/LookPage.hpp @@ -23,6 +23,7 @@ private: void addMessageTab(LayoutCreator layout); void addEmoteTab(LayoutCreator layout); void addSplitHeaderTab(LayoutCreator layout); + void addBadgesTab(LayoutCreator layout); void addLastReadMessageIndicatorPatternSelector( LayoutCreator layout); diff --git a/src/widgets/settingspages/ModerationPage.cpp b/src/widgets/settingspages/ModerationPage.cpp index e7210f5a6..d50e59bc0 100644 --- a/src/widgets/settingspages/ModerationPage.cpp +++ b/src/widgets/settingspages/ModerationPage.cpp @@ -149,7 +149,7 @@ ModerationPage::ModerationPage() auto modMode = tabs.appendTab(new QVBoxLayout, "Moderation buttons"); { // clang-format off - auto label = modMode.emplace("Click the moderation mod button () in a channel that you moderate to enable moderator mode.
"); + auto label = modMode.emplace("Click the moderation mod button () in a channel that you moderate to enable moderator mode.
"); label->setWordWrap(true); label->setStyleSheet("color: #bbb"); // clang-format on diff --git a/src/widgets/settingspages/SettingsPage.cpp b/src/widgets/settingspages/SettingsPage.cpp index e74095986..a70904bc3 100644 --- a/src/widgets/settingspages/SettingsPage.cpp +++ b/src/widgets/settingspages/SettingsPage.cpp @@ -1,6 +1,7 @@ #include "SettingsPage.hpp" #include +#include namespace chatterino { diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index 01a2faaad..856e8ffd7 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -363,6 +363,12 @@ void Split::handleModifiers(Qt::KeyboardModifiers modifiers) } } +void Split::setIsTopRightSplit(bool value) +{ + this->isTopRightSplit_ = value; + this->header_->setAddButtonVisible(value); +} + /// Slots void Split::addSibling() { diff --git a/src/widgets/splits/Split.hpp b/src/widgets/splits/Split.hpp index 8c4ebf84a..6b7e6d057 100644 --- a/src/widgets/splits/Split.hpp +++ b/src/widgets/splits/Split.hpp @@ -65,6 +65,7 @@ public: void layoutMessages(); void updateGifEmotes(); void updateLastReadMessage(); + void setIsTopRightSplit(bool value); void drag(); @@ -106,10 +107,11 @@ private: NullablePtr selectChannelDialog_; - bool moderationMode_ = false; + bool moderationMode_{}; + bool isTopRightSplit_{}; - bool isMouseOver_ = false; - bool isDragging_ = false; + bool isMouseOver_{}; + bool isDragging_{}; pajlada::Signals::Connection channelIDChangedConnection_; pajlada::Signals::Connection usermodeChangedConnection_; diff --git a/src/widgets/splits/SplitContainer.cpp b/src/widgets/splits/SplitContainer.cpp index cac6f2dfb..58e2eb953 100644 --- a/src/widgets/splits/SplitContainer.cpp +++ b/src/widgets/splits/SplitContainer.cpp @@ -310,8 +310,33 @@ void SplitContainer::focusSplitRecursive(Node *node, Direction direction) } } +Split *SplitContainer::getTopRightSplit(Node &node) +{ + switch (node.getType()) { + case Node::_Split: + return node.getSplit(); + case Node::VerticalContainer: + if (!node.getChildren().empty()) + return getTopRightSplit(*node.getChildren().front()); + break; + case Node::HorizontalContainer: + if (!node.getChildren().empty()) + return getTopRightSplit(*node.getChildren().back()); + break; + default:; + } + return nullptr; +} + void SplitContainer::layout() { + // update top right split + auto topRight = this->getTopRightSplit(this->baseNode_); + if (this->topRight_) this->topRight_->setIsTopRightSplit(false); + this->topRight_ = topRight; + if (topRight) this->topRight_->setIsTopRightSplit(true); + + // layout this->baseNode_.geometry_ = this->rect().adjusted(-1, -1, 0, 0); std::vector _dropRects; @@ -431,9 +456,10 @@ void SplitContainer::paintEvent(QPaintEvent *) if (notebook != nullptr) { if (notebook->getPageCount() > 1) { - text += "\n\nTip: After adding a split you can hold to " - "move it or split it " - "further."; + text += + "\n\nTip: After adding a split you can hold to " + "move it or split it " + "further."; } } diff --git a/src/widgets/splits/SplitContainer.hpp b/src/widgets/splits/SplitContainer.hpp index 1f0a2e701..1f90e99aa 100644 --- a/src/widgets/splits/SplitContainer.hpp +++ b/src/widgets/splits/SplitContainer.hpp @@ -219,6 +219,7 @@ private: void addSplit(Split *split); void decodeNodeRecusively(QJsonObject &obj, Node *node); + Split *getTopRightSplit(Node &node); struct DropRegion { QRect rect; @@ -239,6 +240,7 @@ private: Node baseNode_; Split *selected_; + Split *topRight_{}; NotebookTab *tab_; std::vector splits_; diff --git a/src/widgets/splits/SplitHeader.cpp b/src/widgets/splits/SplitHeader.cpp index 9a0d4b71c..61edee4df 100644 --- a/src/widgets/splits/SplitHeader.cpp +++ b/src/widgets/splits/SplitHeader.cpp @@ -93,11 +93,11 @@ namespace { title += " (live)"; // description + if (settings.showUptime) title += " - " + s.uptime; if (settings.showViewerCount) title += " - " + QString::number(s.viewerCount); - if (settings.showTitle) title += " - " + s.title; if (settings.showGame) title += " - " + s.game; - if (settings.showUptime) title += " - " + s.uptime; + if (settings.showTitle) title += " - " + s.title; return title; } @@ -138,33 +138,42 @@ SplitHeader::SplitHeader(Split *_split) void SplitHeader::initializeLayout() { - auto layout = makeLayout( - {// title - this->titleLabel = makeWidget