diff --git a/chatterino.pro b/chatterino.pro
index 4a1d00adc..61f797d0b 100644
--- a/chatterino.pro
+++ b/chatterino.pro
@@ -17,6 +17,10 @@ DEFINES += QT_DEPRECATED_WARNINGS
PRECOMPILED_HEADER = src/PrecompiledHeader.hpp
CONFIG += precompile_header
+debug {
+ DEFINES += QT_DEBUG
+}
+
useBreakpad {
LIBS += -L$$PWD/lib/qBreakpad/handler/build
include(lib/qBreakpad/qBreakpad.pri)
@@ -132,9 +136,7 @@ SOURCES += \
src/messages/MessageBuilder.cpp \
src/messages/MessageColor.cpp \
src/messages/MessageElement.cpp \
- src/providers/bttv/BttvEmotes.cpp \
src/providers/emoji/Emojis.cpp \
- src/providers/ffz/FfzEmotes.cpp \
src/providers/irc/AbstractIrcServer.cpp \
src/providers/irc/IrcAccount.cpp \
src/providers/irc/IrcChannel2.cpp \
@@ -232,7 +234,21 @@ SOURCES += \
src/widgets/dialogs/UpdateDialog.cpp \
src/widgets/settingspages/IgnoresPage.cpp \
src/providers/twitch/PubsubClient.cpp \
- src/providers/twitch/TwitchApi.cpp
+ src/providers/twitch/TwitchApi.cpp \
+ src/messages/Emote.cpp \
+ src/messages/EmoteMap.cpp \
+ src/messages/ImageSet.cpp \
+ src/providers/bttv/BttvEmotes.cpp \
+ src/providers/ffz/FfzEmotes.cpp \
+ src/autogenerated/ResourcesAutogen.cpp \
+ src/singletons/Badges.cpp \
+ src/providers/twitch/TwitchBadges.cpp \
+ src/providers/chatterino/ChatterinoBadges.cpp \
+ src/providers/twitch/TwitchParseCheerEmotes.cpp \
+ src/providers/bttv/LoadBttvChannelEmote.cpp \
+ src/util/JsonQuery.cpp \
+ src/RunGui.cpp \
+ src/BrowserExtension.cpp
HEADERS += \
src/Application.hpp \
@@ -292,9 +308,7 @@ HEADERS += \
src/messages/MessageParseArgs.hpp \
src/messages/Selection.hpp \
src/PrecompiledHeader.hpp \
- src/providers/bttv/BttvEmotes.hpp \
src/providers/emoji/Emojis.hpp \
- src/providers/ffz/FfzEmotes.hpp \
src/providers/irc/AbstractIrcServer.hpp \
src/providers/irc/IrcAccount.hpp \
src/providers/irc/IrcChannel2.hpp \
@@ -413,10 +427,28 @@ HEADERS += \
src/widgets/dialogs/UpdateDialog.hpp \
src/widgets/settingspages/IgnoresPage.hpp \
src/providers/twitch/PubsubClient.hpp \
- src/providers/twitch/TwitchApi.hpp
+ src/providers/twitch/TwitchApi.hpp \
+ src/messages/Emote.hpp \
+ src/messages/EmoteMap.hpp \
+ src/messages/EmoteCache.hpp \
+ src/messages/ImageSet.hpp \
+ src/common/Outcome.hpp \
+ src/providers/bttv/BttvEmotes.hpp \
+ src/providers/ffz/FfzEmotes.hpp \
+ src/autogenerated/ResourcesAutogen.hpp \
+ src/singletons/Badges.hpp \
+ src/providers/twitch/TwitchBadges.hpp \
+ src/providers/chatterino/ChatterinoBadges.hpp \
+ src/common/Aliases.hpp \
+ src/providers/twitch/TwitchParseCheerEmotes.hpp \
+ src/providers/bttv/LoadBttvChannelEmote.hpp \
+ src/util/JsonQuery.hpp \
+ src/RunGui.hpp \
+ src/BrowserExtension.hpp
RESOURCES += \
resources/resources.qrc \
+ resources/resources_autogenerated.qrc
DISTFILES +=
diff --git a/resources/__pycache__/_generate_resources.cpython-36.pyc b/resources/__pycache__/_generate_resources.cpython-36.pyc
new file mode 100644
index 000000000..dfaf6af51
Binary files /dev/null and b/resources/__pycache__/_generate_resources.cpython-36.pyc differ
diff --git a/resources/_generate_resources.py b/resources/_generate_resources.py
new file mode 100644
index 000000000..2ce917fbd
--- /dev/null
+++ b/resources/_generate_resources.py
@@ -0,0 +1,38 @@
+resources_header = \
+'''
+ '''
+
+resources_footer = \
+'''
+'''
+
+header_header = \
+'''#include
+#include "common/Singleton.hpp"
+
+namespace chatterino {
+
+class Resources2 : public Singleton {
+public:
+ Resources2();
+
+'''
+
+header_footer = \
+'''};
+
+} // namespace chatterino'''
+
+source_header = \
+'''#include "ResourcesAutogen.hpp"
+
+namespace chatterino {
+
+Resources2::Resources2()
+{
+'''
+
+source_footer = \
+'''}
+
+} // namespace chatterino'''
diff --git a/resources/images/button_ban.png b/resources/buttons/ban.png
similarity index 100%
rename from resources/images/button_ban.png
rename to resources/buttons/ban.png
diff --git a/resources/images/buttons/ban.png b/resources/buttons/banRed.png
similarity index 100%
rename from resources/images/buttons/ban.png
rename to resources/buttons/banRed.png
diff --git a/resources/images/emote.svg b/resources/buttons/emote.svg
similarity index 100%
rename from resources/images/emote.svg
rename to resources/buttons/emote.svg
diff --git a/resources/images/emote_dark.svg b/resources/buttons/emoteDark.svg
similarity index 100%
rename from resources/images/emote_dark.svg
rename to resources/buttons/emoteDark.svg
diff --git a/resources/images/menu_black.png b/resources/buttons/menuDark.png
similarity index 100%
rename from resources/images/menu_black.png
rename to resources/buttons/menuDark.png
diff --git a/resources/images/menu_white.png b/resources/buttons/menuLight.png
similarity index 100%
rename from resources/images/menu_white.png
rename to resources/buttons/menuLight.png
diff --git a/resources/images/buttons/mod.png b/resources/buttons/mod.png
similarity index 100%
rename from resources/images/buttons/mod.png
rename to resources/buttons/mod.png
diff --git a/resources/images/moderatormode_disabled.png b/resources/buttons/modModeDisabled.png
similarity index 100%
rename from resources/images/moderatormode_disabled.png
rename to resources/buttons/modModeDisabled.png
diff --git a/resources/images/moderatormode_disabled2.png b/resources/buttons/modModeDisabled2.png
similarity index 100%
rename from resources/images/moderatormode_disabled2.png
rename to resources/buttons/modModeDisabled2.png
diff --git a/resources/images/moderatormode_enabled.png b/resources/buttons/modModeEnabled.png
similarity index 100%
rename from resources/images/moderatormode_enabled.png
rename to resources/buttons/modModeEnabled.png
diff --git a/resources/images/moderatormode_enabled2.png b/resources/buttons/modModeEnabled2.png
similarity index 100%
rename from resources/images/moderatormode_enabled2.png
rename to resources/buttons/modModeEnabled2.png
diff --git a/resources/images/button_timeout.png b/resources/buttons/timeout.png
similarity index 100%
rename from resources/images/button_timeout.png
rename to resources/buttons/timeout.png
diff --git a/resources/images/buttons/unban.png b/resources/buttons/unban.png
similarity index 100%
rename from resources/images/buttons/unban.png
rename to resources/buttons/unban.png
diff --git a/resources/images/buttons/unmod.png b/resources/buttons/unmod.png
similarity index 100%
rename from resources/images/buttons/unmod.png
rename to resources/buttons/unmod.png
diff --git a/resources/images/download_update.png b/resources/buttons/update.png
similarity index 100%
rename from resources/images/download_update.png
rename to resources/buttons/update.png
diff --git a/resources/images/download_update_error.png b/resources/buttons/updateError.png
similarity index 100%
rename from resources/images/download_update_error.png
rename to resources/buttons/updateError.png
diff --git a/resources/images/chatterino2.icns b/resources/chatterino2.icns
similarity index 100%
rename from resources/images/chatterino2.icns
rename to resources/chatterino2.icns
diff --git a/resources/error.png b/resources/error.png
new file mode 100644
index 000000000..07fba9f7c
Binary files /dev/null and b/resources/error.png differ
diff --git a/resources/generate_resources.py b/resources/generate_resources.py
new file mode 100755
index 000000000..4b047219c
--- /dev/null
+++ b/resources/generate_resources.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+from pathlib import Path
+
+from _generate_resources import *
+
+ignored_files = ['qt.conf', 'resources.qrc', 'resources_autogenerated.qrc', 'windows.rc',
+ 'generate_resources.py', '_generate_resources.py']
+ignored_directories = ['__pycache__']
+
+def isNotIgnored(file):
+ return str(file) not in ignored_files
+
+all_files = list(filter(isNotIgnored, \
+ filter(Path.is_file, Path('.').glob('**/*'))))
+image_files = list(filter(isNotIgnored, \
+ filter(Path.is_file, Path('.').glob('**/*.png'))))
+
+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(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("/",".")
+ out.write(f' this->{var_name}')
+ out.write(f' = QPixmap(":/{file}");\n')
+ out.write(source_footer)
+
+def writeHeader(out, name, element, indent):
+ if isinstance(element, dict):
+ if name != "":
+ out.write(f"{indent}struct {{\n")
+ for (key, value) in element.items():
+ writeHeader(out, key, value, indent + ' ')
+ if name != "":
+ out.write(f"{indent}}} {name};\n");
+ else:
+ out.write(f"{indent}QPixmap {element};\n")
+
+with open('../src/autogenerated/ResourcesAutogen.hpp', 'w') as out:
+ out.write(header_header)
+
+ elements = {}
+ for file in sorted(image_files):
+ elements_ref = elements
+ directories = str(file).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 = elements_ref[directory]
+ elements_ref[filename] = filename
+
+ writeHeader(out, "", elements, '')
+
+ out.write(header_footer)
+
diff --git a/resources/images/icon.png b/resources/icon.png
similarity index 100%
rename from resources/images/icon.png
rename to resources/icon.png
diff --git a/resources/images/AppearanceEditorPart_16x.png b/resources/images/AppearanceEditorPart_16x.png
deleted file mode 100644
index 86c59f7ff..000000000
Binary files a/resources/images/AppearanceEditorPart_16x.png and /dev/null differ
diff --git a/resources/images/BrowserLink_16x.png b/resources/images/BrowserLink_16x.png
deleted file mode 100644
index bafb9d4e7..000000000
Binary files a/resources/images/BrowserLink_16x.png and /dev/null differ
diff --git a/resources/images/CopyLongTextToClipboard_16x.png b/resources/images/CopyLongTextToClipboard_16x.png
deleted file mode 100644
index 774ee97fa..000000000
Binary files a/resources/images/CopyLongTextToClipboard_16x.png and /dev/null differ
diff --git a/resources/images/CustomActionEditor_16x.png b/resources/images/CustomActionEditor_16x.png
deleted file mode 100644
index b7b68a21e..000000000
Binary files a/resources/images/CustomActionEditor_16x.png and /dev/null differ
diff --git a/resources/images/Emoji_Color_1F60A_19 old.png b/resources/images/Emoji_Color_1F60A_19 old.png
deleted file mode 100644
index 81d296bf8..000000000
Binary files a/resources/images/Emoji_Color_1F60A_19 old.png and /dev/null differ
diff --git a/resources/images/Emoji_Color_1F60A_19.png b/resources/images/Emoji_Color_1F60A_19.png
deleted file mode 100644
index 15e15bd4a..000000000
Binary files a/resources/images/Emoji_Color_1F60A_19.png and /dev/null differ
diff --git a/resources/images/Filter_16x.png b/resources/images/Filter_16x.png
deleted file mode 100644
index f946e63c1..000000000
Binary files a/resources/images/Filter_16x.png and /dev/null differ
diff --git a/resources/images/Message_16xLG.png b/resources/images/Message_16xLG.png
deleted file mode 100644
index 7d06b1995..000000000
Binary files a/resources/images/Message_16xLG.png and /dev/null differ
diff --git a/resources/images/StatusAnnotations_Blocked_16xLG_color.png b/resources/images/StatusAnnotations_Blocked_16xLG_color.png
deleted file mode 100644
index b58166e30..000000000
Binary files a/resources/images/StatusAnnotations_Blocked_16xLG_color.png and /dev/null differ
diff --git a/resources/images/UserProfile_22x.png b/resources/images/UserProfile_22x.png
deleted file mode 100644
index c47f61243..000000000
Binary files a/resources/images/UserProfile_22x.png and /dev/null differ
diff --git a/resources/images/VSO_Link_blue_16x.png b/resources/images/VSO_Link_blue_16x.png
deleted file mode 100644
index eb3882929..000000000
Binary files a/resources/images/VSO_Link_blue_16x.png and /dev/null differ
diff --git a/resources/images/cheer100.png b/resources/images/cheer100.png
deleted file mode 100644
index 301998040..000000000
Binary files a/resources/images/cheer100.png and /dev/null differ
diff --git a/resources/images/cheer1000.png b/resources/images/cheer1000.png
deleted file mode 100644
index d6c0ed04a..000000000
Binary files a/resources/images/cheer1000.png and /dev/null differ
diff --git a/resources/images/cheer10000.png b/resources/images/cheer10000.png
deleted file mode 100644
index 3e3eb4284..000000000
Binary files a/resources/images/cheer10000.png and /dev/null differ
diff --git a/resources/images/cheer100000.png b/resources/images/cheer100000.png
deleted file mode 100644
index c20e956b1..000000000
Binary files a/resources/images/cheer100000.png and /dev/null differ
diff --git a/resources/images/cheer5000.png b/resources/images/cheer5000.png
deleted file mode 100644
index 89a7a4015..000000000
Binary files a/resources/images/cheer5000.png and /dev/null differ
diff --git a/resources/images/collapse.png b/resources/images/collapse.png
deleted file mode 100644
index b181ccbf9..000000000
Binary files a/resources/images/collapse.png and /dev/null differ
diff --git a/resources/images/format_Bold_16xLG.png b/resources/images/format_Bold_16xLG.png
deleted file mode 100644
index e636bb0d5..000000000
Binary files a/resources/images/format_Bold_16xLG.png and /dev/null differ
diff --git a/resources/images/settings.png b/resources/images/settings.png
deleted file mode 100644
index f6542bd48..000000000
Binary files a/resources/images/settings.png and /dev/null differ
diff --git a/resources/images/tool_moreCollapser_off16.png b/resources/images/tool_moreCollapser_off16.png
deleted file mode 100644
index 717fc75e2..000000000
Binary files a/resources/images/tool_moreCollapser_off16.png and /dev/null differ
diff --git a/resources/images/pajaDank.png b/resources/pajaDank.png
similarity index 100%
rename from resources/images/pajaDank.png
rename to resources/pajaDank.png
diff --git a/resources/resources.qrc b/resources/resources.qrc
index 129713804..8568b5032 100644
--- a/resources/resources.qrc
+++ b/resources/resources.qrc
@@ -1,82 +1,5 @@
-
- images/AppearanceEditorPart_16x.png
- images/BrowserLink_16x.png
- images/cheer1.png
- images/cheer100.png
- images/cheer1000.png
- images/cheer10000.png
- images/cheer100000.png
- images/cheer5000.png
- images/verified.png
- images/CopyLongTextToClipboard_16x.png
- images/CustomActionEditor_16x.png
- images/Emoji_Color_1F60A_19.png
- images/Filter_16x.png
- images/format_Bold_16xLG.png
- images/Message_16xLG.png
- images/settings.png
- images/tool_moreCollapser_off16.png
- images/twitchprime_bg.png
- qss/settings.qss
- images/admin_bg.png
- images/broadcaster_bg.png
- images/globalmod_bg.png
- images/moderator_bg.png
- images/staff_bg.png
- images/turbo_bg.png
- emojidata.txt
- images/button_ban.png
- images/button_timeout.png
- images/StatusAnnotations_Blocked_16xLG_color.png
- images/UserProfile_22x.png
- images/VSO_Link_blue_16x.png
- sounds/ping2.wav
- images/subscriber.png
- images/collapse.png
- images/emote.svg
- images/notifications.svg
- images/behave.svg
- images/theme.svg
- images/accounts.svg
- images/chatterino2.icns
- images/icon.png
- images/commands.svg
- images/aboutlogo.png
- images/about.svg
- images/moderatormode_disabled.png
- images/moderatormode_enabled.png
- images/split/splitdown.png
- images/split/splitleft.png
- images/split/splitright.png
- images/split/splitup.png
- images/split/splitmove.png
- licenses/boost_boost.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
- emoji.json
- images/buttons/ban.png
- images/buttons/mod.png
- images/buttons/unban.png
- images/buttons/unmod.png
- images/emote_dark.svg
- tlds.txt
- images/menu_black.png
- images/menu_white.png
- contributors.txt
- avatars/fourtf.png
- avatars/pajlada.png
- images/download_update.png
- images/download_update_error.png
- images/pajaDank.png
-
-
- qt.conf
-
+
+ qt.conf
+
diff --git a/resources/resources_autogenerated.qrc b/resources/resources_autogenerated.qrc
new file mode 100644
index 000000000..6fc9e6f68
--- /dev/null
+++ b/resources/resources_autogenerated.qrc
@@ -0,0 +1,64 @@
+
+ pajaDank.png
+ icon.png
+ emojidata.txt
+ contributors.txt
+ error.png
+ emoji.json
+ icon.ico
+ 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
+ buttons/ban.png
+ buttons/unmod.png
+ buttons/emoteDark.svg
+ buttons/updateError.png
+ buttons/modModeDisabled.png
+ buttons/modModeDisabled2.png
+ buttons/modModeEnabled.png
+ buttons/menuLight.png
+ buttons/update.png
+ buttons/timeout.png
+ buttons/banRed.png
+ sounds/ping2.wav
+ twitch/prime.png
+ twitch/verified.png
+ twitch/admin.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
+
+
\ No newline at end of file
diff --git a/resources/images/about.svg b/resources/settings/about.svg
similarity index 100%
rename from resources/images/about.svg
rename to resources/settings/about.svg
diff --git a/resources/images/aboutlogo.png b/resources/settings/aboutlogo.png
similarity index 100%
rename from resources/images/aboutlogo.png
rename to resources/settings/aboutlogo.png
diff --git a/resources/images/accounts.svg b/resources/settings/accounts.svg
similarity index 100%
rename from resources/images/accounts.svg
rename to resources/settings/accounts.svg
diff --git a/resources/images/behave.svg b/resources/settings/behave.svg
similarity index 100%
rename from resources/images/behave.svg
rename to resources/settings/behave.svg
diff --git a/resources/images/commands.svg b/resources/settings/commands.svg
similarity index 100%
rename from resources/images/commands.svg
rename to resources/settings/commands.svg
diff --git a/resources/images/notifications.svg b/resources/settings/notifications.svg
similarity index 100%
rename from resources/images/notifications.svg
rename to resources/settings/notifications.svg
diff --git a/resources/images/theme.svg b/resources/settings/theme.svg
similarity index 100%
rename from resources/images/theme.svg
rename to resources/settings/theme.svg
diff --git a/resources/images/split/splitdown.png b/resources/split/down.png
similarity index 100%
rename from resources/images/split/splitdown.png
rename to resources/split/down.png
diff --git a/resources/images/split/splitleft.png b/resources/split/left.png
similarity index 100%
rename from resources/images/split/splitleft.png
rename to resources/split/left.png
diff --git a/resources/images/split/splitmove.png b/resources/split/move.png
similarity index 100%
rename from resources/images/split/splitmove.png
rename to resources/split/move.png
diff --git a/resources/images/split/splitright.png b/resources/split/right.png
similarity index 100%
rename from resources/images/split/splitright.png
rename to resources/split/right.png
diff --git a/resources/images/split/splitup.png b/resources/split/up.png
similarity index 100%
rename from resources/images/split/splitup.png
rename to resources/split/up.png
diff --git a/resources/images/admin_bg.png b/resources/twitch/admin.png
similarity index 100%
rename from resources/images/admin_bg.png
rename to resources/twitch/admin.png
diff --git a/resources/images/broadcaster_bg.png b/resources/twitch/broadcaster.png
similarity index 100%
rename from resources/images/broadcaster_bg.png
rename to resources/twitch/broadcaster.png
diff --git a/resources/images/cheer1.png b/resources/twitch/cheer1.png
similarity index 100%
rename from resources/images/cheer1.png
rename to resources/twitch/cheer1.png
diff --git a/resources/images/globalmod_bg.png b/resources/twitch/globalmod.png
similarity index 100%
rename from resources/images/globalmod_bg.png
rename to resources/twitch/globalmod.png
diff --git a/resources/images/moderator_bg.png b/resources/twitch/moderator.png
similarity index 100%
rename from resources/images/moderator_bg.png
rename to resources/twitch/moderator.png
diff --git a/resources/images/twitchprime_bg.png b/resources/twitch/prime.png
similarity index 100%
rename from resources/images/twitchprime_bg.png
rename to resources/twitch/prime.png
diff --git a/resources/images/staff_bg.png b/resources/twitch/staff.png
similarity index 100%
rename from resources/images/staff_bg.png
rename to resources/twitch/staff.png
diff --git a/resources/images/subscriber.png b/resources/twitch/subscriber.png
similarity index 100%
rename from resources/images/subscriber.png
rename to resources/twitch/subscriber.png
diff --git a/resources/images/turbo_bg.png b/resources/twitch/turbo.png
similarity index 100%
rename from resources/images/turbo_bg.png
rename to resources/twitch/turbo.png
diff --git a/resources/images/verified.png b/resources/twitch/verified.png
similarity index 100%
rename from resources/images/verified.png
rename to resources/twitch/verified.png
diff --git a/src/Application.cpp b/src/Application.cpp
index 9068bf9a8..b7d651aa4 100644
--- a/src/Application.cpp
+++ b/src/Application.cpp
@@ -6,9 +6,10 @@
#include "controllers/ignores/IgnoreController.hpp"
#include "controllers/moderationactions/ModerationActions.hpp"
#include "controllers/taggedusers/TaggedUsersController.hpp"
+#include "providers/bttv/BttvEmotes.hpp"
+#include "providers/ffz/FfzEmotes.hpp"
#include "providers/twitch/PubsubClient.hpp"
#include "providers/twitch/TwitchServer.hpp"
-#include "singletons/Emotes.hpp"
#include "singletons/Fonts.hpp"
#include "singletons/Logging.hpp"
#include "singletons/NativeMessaging.hpp"
@@ -24,69 +25,75 @@
namespace chatterino {
-static std::atomic isAppConstructed{false};
static std::atomic isAppInitialized{false};
-static Application *staticApp = nullptr;
+Application *Application::instance = nullptr;
// this class is responsible for handling the workflow of Chatterino
// It will create the instances of the major classes, and connect their signals to each other
-Application::Application(int _argc, char **_argv)
- : argc_(_argc)
- , argv_(_argv)
+Application::Application(Settings &_settings, Paths &_paths)
+ : settings(&_settings)
+ , paths(&_paths)
+ , resources(&this->emplace())
+
+ , themes(&this->emplace())
+ , fonts(&this->emplace())
+ , emotes(&this->emplace())
+ , windows(&this->emplace())
+
+ , accounts(&this->emplace())
+ , commands(&this->emplace())
+ , highlights(&this->emplace())
+ , ignores(&this->emplace())
+ , taggedUsers(&this->emplace())
+ , moderationActions(&this->emplace())
+ , twitch2(&this->emplace())
+ , logging(&this->emplace())
{
- getSettings()->initialize();
- getSettings()->load();
-}
+ this->instance = this;
-void Application::construct()
-{
- assert(isAppConstructed == false);
- isAppConstructed = true;
+ this->fonts->fontChanged.connect([this]() { this->windows->layoutChannelViews(); });
- // 1. Instantiate all classes
- this->settings = getSettings();
- this->paths = getPaths();
-
- this->addSingleton(this->themes = new Theme);
- this->addSingleton(this->windows = new WindowManager);
- this->addSingleton(this->logging = new Logging);
- this->addSingleton(this->commands = new CommandController);
- this->addSingleton(this->highlights = new HighlightController);
- this->addSingleton(this->ignores = new IgnoreController);
- this->addSingleton(this->taggedUsers = new TaggedUsersController);
- this->addSingleton(this->accounts = new AccountController);
- this->addSingleton(this->emotes = new Emotes);
- this->addSingleton(this->fonts = new Fonts);
- this->addSingleton(this->resources = new Resources);
- this->addSingleton(this->moderationActions = new ModerationActions);
-
- this->addSingleton(this->twitch2 = new TwitchServer);
this->twitch.server = this->twitch2;
this->twitch.pubsub = this->twitch2->pubsub;
}
-void Application::instantiate(int argc, char **argv)
-{
- assert(staticApp == nullptr);
-
- staticApp = new Application(argc, argv);
-}
-
-void Application::initialize()
+void Application::initialize(Settings &settings, Paths &paths)
{
assert(isAppInitialized == false);
isAppInitialized = true;
- // 2. Initialize/load classes
- for (Singleton *singleton : this->singletons_) {
- singleton->initialize(*this);
+ for (auto &singleton : this->singletons_) {
+ singleton->initialize(settings, paths);
}
- // XXX
this->windows->updateWordTypeMask();
+ this->initNm();
+ this->initPubsub();
+}
+
+int Application::run(QApplication &qtApp)
+{
+ assert(isAppInitialized);
+
+ this->twitch.server->connect();
+
+ this->windows->getMainWindow().show();
+
+ return qtApp.exec();
+}
+
+void Application::save()
+{
+ for (auto &singleton : this->singletons_) {
+ singleton->save();
+ }
+}
+
+void Application::initNm()
+{
#ifdef Q_OS_WIN
#ifdef QT_DEBUG
#ifdef C_DEBUG_NM
@@ -98,7 +105,10 @@ void Application::initialize()
this->nativeMessaging->openGuiMessageQueue();
#endif
#endif
+}
+void Application::initPubsub()
+{
this->twitch.pubsub->signals_.whisper.sent.connect([](const auto &msg) {
Log("WHISPER SENT LOL"); //
});
@@ -197,39 +207,11 @@ void Application::initialize()
RequestModerationActions();
}
-int Application::run(QApplication &qtApp)
-{
- // Start connecting to the IRC Servers (Twitch only for now)
- this->twitch.server->connect();
-
- // Show main window
- this->windows->getMainWindow().show();
-
- return qtApp.exec();
-}
-
-void Application::save()
-{
- for (Singleton *singleton : this->singletons_) {
- singleton->save();
- }
-}
-
-void Application::addSingleton(Singleton *singleton)
-{
- this->singletons_.push_back(singleton);
-}
-
Application *getApp()
{
- assert(staticApp != nullptr);
+ assert(Application::instance != nullptr);
- return staticApp;
-}
-
-bool appInitialized()
-{
- return isAppInitialized;
+ return Application::instance;
}
} // namespace chatterino
diff --git a/src/Application.hpp b/src/Application.hpp
index 8fda2ebba..c378715f5 100644
--- a/src/Application.hpp
+++ b/src/Application.hpp
@@ -1,13 +1,13 @@
#pragma once
+#include "common/Singleton.hpp"
#include "singletons/Resources.hpp"
#include
+#include
namespace chatterino {
-class Singleton;
-
class TwitchServer;
class PubSub;
@@ -24,45 +24,47 @@ class Logging;
class Paths;
class AccountManager;
class Emotes;
-class NativeMessaging;
class Settings;
class Fonts;
class Resources;
class Application
{
- Application(int _argc, char **_argv);
+ std::vector> singletons_;
+ int argc_;
+ char **argv_;
public:
- static void instantiate(int argc_, char **argv_);
+ static Application *instance;
- ~Application() = delete;
+ Application(Settings &settings, Paths &paths);
- void construct();
- void initialize();
+ void initialize(Settings &settings, Paths &paths);
void load();
+ void save();
int run(QApplication &qtApp);
friend void test();
- [[deprecated("use getSettings() instead")]] Settings *settings = nullptr;
- [[deprecated("use getPaths() instead")]] Paths *paths = nullptr;
+ Settings *const settings = nullptr;
+ Paths *const paths = nullptr;
+ Resources2 *const resources;
- Theme *themes = nullptr;
- WindowManager *windows = nullptr;
- Logging *logging = nullptr;
- CommandController *commands = nullptr;
- HighlightController *highlights = nullptr;
- IgnoreController *ignores = nullptr;
- TaggedUsersController *taggedUsers = nullptr;
- AccountController *accounts = nullptr;
- Emotes *emotes = nullptr;
- NativeMessaging *nativeMessaging = nullptr;
- Fonts *fonts = nullptr;
- Resources *resources = nullptr;
- ModerationActions *moderationActions = nullptr;
- TwitchServer *twitch2 = nullptr;
+ Theme *const themes = nullptr;
+ Fonts *const fonts = nullptr;
+ Emotes *const emotes = nullptr;
+ WindowManager *const windows = nullptr;
+
+ AccountController *const accounts = nullptr;
+ CommandController *const commands = nullptr;
+ HighlightController *const highlights = nullptr;
+ IgnoreController *const ignores = nullptr;
+ TaggedUsersController *const taggedUsers = nullptr;
+ ModerationActions *const moderationActions = nullptr;
+ TwitchServer *const twitch2 = nullptr;
+
+ [[deprecated]] Logging *const logging = nullptr;
/// Provider-specific
struct {
@@ -70,22 +72,20 @@ public:
[[deprecated("use twitch2->pubsub instead")]] PubSub *pubsub = nullptr;
} twitch;
- void save();
-
- // Special application mode that only initializes the native messaging host
- static void runNativeMessagingHost();
-
private:
void addSingleton(Singleton *singleton);
+ void initPubsub();
+ void initNm();
- int argc_;
- char **argv_;
-
- std::vector singletons_;
+ template ::value>>
+ T &emplace()
+ {
+ auto t = new T;
+ this->singletons_.push_back(std::unique_ptr(t));
+ return *t;
+ }
};
Application *getApp();
-bool appInitialized();
-
} // namespace chatterino
diff --git a/src/BrowserExtension.cpp b/src/BrowserExtension.cpp
new file mode 100644
index 000000000..3440170ab
--- /dev/null
+++ b/src/BrowserExtension.cpp
@@ -0,0 +1,88 @@
+#include "BrowserExtension.hpp"
+
+#include "singletons/NativeMessaging.hpp"
+
+#include
+#include
+#include
+#include
+#include
+
+#ifdef Q_OS_WIN
+#include
+#include
+#include
+#endif
+
+namespace chatterino {
+
+namespace {
+void initFileMode()
+{
+#ifdef Q_OS_WIN
+ _setmode(_fileno(stdin), _O_BINARY);
+ _setmode(_fileno(stdout), _O_BINARY);
+#endif
+}
+
+void runLoop(NativeMessagingClient &client)
+{
+ while (true) {
+ char size_c[4];
+ std::cin.read(size_c, 4);
+
+ if (std::cin.eof()) {
+ break;
+ }
+
+ uint32_t size = *reinterpret_cast(size_c);
+
+#if 0
+ bool bigEndian = isBigEndian();
+ // To avoid breaking strict-aliasing rules and potentially inducing undefined behaviour, the following code can be run instead
+ uint32_t size = 0;
+ if (bigEndian) {
+ size = size_c[3] | static_cast(size_c[2]) << 8 |
+ static_cast(size_c[1]) << 16 | static_cast(size_c[0]) << 24;
+ } else {
+ size = size_c[0] | static_cast(size_c[1]) << 8 |
+ static_cast(size_c[2]) << 16 | static_cast(size_c[3]) << 24;
+ }
+#endif
+
+ std::unique_ptr b(new char[size + 1]);
+ std::cin.read(b.get(), size);
+ *(b.get() + size) = '\0';
+
+ client.sendMessage(QByteArray::fromRawData(b.get(), static_cast(size)));
+ }
+}
+} // namespace
+
+bool shouldRunBrowserExtensionHost(const QStringList &args)
+{
+ return args.size() > 0 &&
+ (args[0].startsWith("chrome-extension://") || args[0].endsWith(".json"));
+}
+
+void runBrowserExtensionHost()
+{
+ initFileMode();
+
+ std::atomic ping(false);
+
+ QTimer timer;
+ QObject::connect(&timer, &QTimer::timeout, [&ping] {
+ if (!ping.exchange(false)) {
+ _Exit(0);
+ }
+ });
+ timer.setInterval(11000);
+ timer.start();
+
+ NativeMessagingClient client;
+
+ runLoop(client);
+}
+
+} // namespace chatterino
diff --git a/src/BrowserExtension.hpp b/src/BrowserExtension.hpp
new file mode 100644
index 000000000..0232ac2b8
--- /dev/null
+++ b/src/BrowserExtension.hpp
@@ -0,0 +1,10 @@
+#pragma once
+
+class QStringList;
+
+namespace chatterino {
+
+bool shouldRunBrowserExtensionHost(const QStringList &args);
+void runBrowserExtensionHost();
+
+} // namespace chatterino
diff --git a/src/RunGui.cpp b/src/RunGui.cpp
new file mode 100644
index 000000000..a15bce6bc
--- /dev/null
+++ b/src/RunGui.cpp
@@ -0,0 +1,130 @@
+#include "RunGui.hpp"
+
+#include
+#include
+#include
+#include
+
+#include "Application.hpp"
+#include "common/NetworkManager.hpp"
+#include "singletons/Paths.hpp"
+#include "singletons/Updates.hpp"
+#include "widgets/dialogs/LastRunCrashDialog.hpp"
+
+#ifdef C_USE_BREAKPAD
+#include
+#endif
+
+// void initQt();
+// void installCustomPalette();
+// void showLastCrashDialog();
+// void createRunningFile(const QString &path);
+// void removeRunningFile(const QString &path);
+
+namespace chatterino {
+namespace {
+void installCustomPalette()
+{
+ // borrowed from
+ // https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
+ QPalette darkPalette = qApp->palette();
+
+ darkPalette.setColor(QPalette::Window, QColor(22, 22, 22));
+ darkPalette.setColor(QPalette::WindowText, Qt::white);
+ darkPalette.setColor(QPalette::Text, Qt::white);
+ darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127));
+ darkPalette.setColor(QPalette::Base, QColor("#333"));
+ darkPalette.setColor(QPalette::AlternateBase, QColor("#444"));
+ darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
+ darkPalette.setColor(QPalette::ToolTipText, Qt::white);
+ darkPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127));
+ darkPalette.setColor(QPalette::Dark, QColor(35, 35, 35));
+ darkPalette.setColor(QPalette::Shadow, QColor(20, 20, 20));
+ darkPalette.setColor(QPalette::Button, QColor(70, 70, 70));
+ darkPalette.setColor(QPalette::ButtonText, Qt::white);
+ darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127));
+ darkPalette.setColor(QPalette::BrightText, Qt::red);
+ darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
+ darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
+ darkPalette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80));
+ darkPalette.setColor(QPalette::HighlightedText, Qt::white);
+ darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127));
+
+ qApp->setPalette(darkPalette);
+}
+
+void initQt()
+{
+ // set up the QApplication flags
+ QApplication::setAttribute(Qt::AA_Use96Dpi, true);
+#ifdef Q_OS_WIN32
+ QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
+#endif
+
+ QApplication::setStyle(QStyleFactory::create("Fusion"));
+
+ installCustomPalette();
+}
+
+void showLastCrashDialog()
+{
+#ifndef C_DISABLE_CRASH_DIALOG
+ LastRunCrashDialog dialog;
+
+ switch (dialog.exec()) {
+ case QDialog::Accepted: {
+ }; break;
+ default: {
+ _exit(0);
+ }
+ }
+#endif
+}
+
+void createRunningFile(const QString &path)
+{
+ QFile runningFile(path);
+
+ runningFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
+ runningFile.flush();
+ runningFile.close();
+}
+
+void removeRunningFile(const QString &path)
+{
+ QFile::remove(path);
+}
+} // namespace
+
+void runGui(QApplication &a, Paths &paths, Settings &settings)
+{
+ chatterino::NetworkManager::init();
+ chatterino::Updates::getInstance().checkForUpdates();
+
+#ifdef C_USE_BREAKPAD
+ QBreakpadInstance.setDumpPath(app->paths->settingsFolderPath + "/Crashes");
+#endif
+
+ // Running file
+ auto runningPath = paths.miscDirectory + "/running_" + paths.applicationFilePathHash;
+
+ if (QFile::exists(runningPath)) {
+ showLastCrashDialog();
+ } else {
+ createRunningFile(runningPath);
+ }
+
+ Application app(settings, paths);
+ app.initialize(settings, paths);
+ app.run(a);
+ app.save();
+
+ removeRunningFile(runningPath);
+
+ pajlada::Settings::SettingManager::gSave();
+
+ chatterino::NetworkManager::deinit();
+
+ _exit(0);
+}
+} // namespace chatterino
diff --git a/src/RunGui.hpp b/src/RunGui.hpp
new file mode 100644
index 000000000..338164404
--- /dev/null
+++ b/src/RunGui.hpp
@@ -0,0 +1,10 @@
+#pragma once
+
+class QApplication;
+
+namespace chatterino {
+class Paths;
+class Settings;
+
+void runGui(QApplication &a, Paths &paths, Settings &settings);
+} // namespace chatterino
diff --git a/src/autogenerated/ResourcesAutogen.cpp b/src/autogenerated/ResourcesAutogen.cpp
new file mode 100644
index 000000000..c5035f2e6
--- /dev/null
+++ b/src/autogenerated/ResourcesAutogen.cpp
@@ -0,0 +1,44 @@
+#include "ResourcesAutogen.hpp"
+
+namespace chatterino {
+
+Resources2::Resources2()
+{
+ this->avatars.fourtf = QPixmap(":/avatars/fourtf.png");
+ this->avatars.pajlada = QPixmap(":/avatars/pajlada.png");
+ this->buttons.ban = QPixmap(":/buttons/ban.png");
+ this->buttons.banRed = QPixmap(":/buttons/banRed.png");
+ this->buttons.menuDark = QPixmap(":/buttons/menuDark.png");
+ this->buttons.menuLight = QPixmap(":/buttons/menuLight.png");
+ this->buttons.mod = QPixmap(":/buttons/mod.png");
+ this->buttons.modModeDisabled = QPixmap(":/buttons/modModeDisabled.png");
+ this->buttons.modModeDisabled2 = QPixmap(":/buttons/modModeDisabled2.png");
+ this->buttons.modModeEnabled = QPixmap(":/buttons/modModeEnabled.png");
+ this->buttons.modModeEnabled2 = QPixmap(":/buttons/modModeEnabled2.png");
+ this->buttons.timeout = QPixmap(":/buttons/timeout.png");
+ this->buttons.unban = QPixmap(":/buttons/unban.png");
+ this->buttons.unmod = QPixmap(":/buttons/unmod.png");
+ this->buttons.update = QPixmap(":/buttons/update.png");
+ this->buttons.updateError = QPixmap(":/buttons/updateError.png");
+ this->error = QPixmap(":/error.png");
+ this->icon = QPixmap(":/icon.png");
+ this->pajaDank = QPixmap(":/pajaDank.png");
+ this->settings.aboutlogo = QPixmap(":/settings/aboutlogo.png");
+ this->split.down = QPixmap(":/split/down.png");
+ this->split.left = QPixmap(":/split/left.png");
+ this->split.move = QPixmap(":/split/move.png");
+ this->split.right = QPixmap(":/split/right.png");
+ this->split.up = QPixmap(":/split/up.png");
+ this->twitch.admin = QPixmap(":/twitch/admin.png");
+ this->twitch.broadcaster = QPixmap(":/twitch/broadcaster.png");
+ this->twitch.cheer1 = QPixmap(":/twitch/cheer1.png");
+ this->twitch.globalmod = QPixmap(":/twitch/globalmod.png");
+ this->twitch.moderator = QPixmap(":/twitch/moderator.png");
+ this->twitch.prime = QPixmap(":/twitch/prime.png");
+ this->twitch.staff = QPixmap(":/twitch/staff.png");
+ this->twitch.subscriber = QPixmap(":/twitch/subscriber.png");
+ this->twitch.turbo = QPixmap(":/twitch/turbo.png");
+ this->twitch.verified = QPixmap(":/twitch/verified.png");
+}
+
+} // namespace chatterino
\ No newline at end of file
diff --git a/src/autogenerated/ResourcesAutogen.hpp b/src/autogenerated/ResourcesAutogen.hpp
new file mode 100644
index 000000000..048a1a94b
--- /dev/null
+++ b/src/autogenerated/ResourcesAutogen.hpp
@@ -0,0 +1,57 @@
+#include
+#include "common/Singleton.hpp"
+
+namespace chatterino {
+
+class Resources2 : public Singleton {
+public:
+ Resources2();
+
+ struct {
+ QPixmap fourtf;
+ QPixmap pajlada;
+ } avatars;
+ struct {
+ QPixmap ban;
+ QPixmap banRed;
+ QPixmap menuDark;
+ QPixmap menuLight;
+ QPixmap mod;
+ QPixmap modModeDisabled;
+ QPixmap modModeDisabled2;
+ QPixmap modModeEnabled;
+ QPixmap modModeEnabled2;
+ QPixmap timeout;
+ QPixmap unban;
+ QPixmap unmod;
+ QPixmap update;
+ QPixmap updateError;
+ } buttons;
+ QPixmap error;
+ QPixmap icon;
+ QPixmap pajaDank;
+ struct {
+ QPixmap aboutlogo;
+ } settings;
+ struct {
+ QPixmap down;
+ QPixmap left;
+ QPixmap move;
+ QPixmap right;
+ QPixmap up;
+ } split;
+ struct {
+ QPixmap admin;
+ QPixmap broadcaster;
+ QPixmap cheer1;
+ QPixmap globalmod;
+ QPixmap moderator;
+ QPixmap prime;
+ QPixmap staff;
+ QPixmap subscriber;
+ QPixmap turbo;
+ QPixmap verified;
+ } twitch;
+};
+
+} // namespace chatterino
\ No newline at end of file
diff --git a/src/common/Aliases.hpp b/src/common/Aliases.hpp
new file mode 100644
index 000000000..8a61a9639
--- /dev/null
+++ b/src/common/Aliases.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include
+#include
+#include
+
+#define QStringAlias(name) \
+ namespace chatterino { \
+ struct name { \
+ QString string; \
+ bool operator==(const name &other) const \
+ { \
+ return this->string == other.string; \
+ } \
+ bool operator!=(const name &other) const \
+ { \
+ return this->string != other.string; \
+ } \
+ }; \
+ } /* namespace chatterino */ \
+ namespace std { \
+ template <> \
+ struct hash { \
+ size_t operator()(const chatterino::name &s) const \
+ { \
+ return qHash(s.string); \
+ } \
+ }; \
+ } /* namespace std */
+
+QStringAlias(UserName);
+QStringAlias(UserId);
+QStringAlias(Url);
+QStringAlias(Tooltip);
diff --git a/src/common/Channel.cpp b/src/common/Channel.cpp
index a0ffc511f..c03019c43 100644
--- a/src/common/Channel.cpp
+++ b/src/common/Channel.cpp
@@ -17,9 +17,9 @@
namespace chatterino {
-Channel::Channel(const QString &_name, Type type)
- : name(_name)
- , completionModel(this->name)
+Channel::Channel(const QString &name, Type type)
+ : completionModel(name)
+ , name_(name)
, type_(type)
{
QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout, [this]() {
@@ -38,6 +38,11 @@ Channel::Type Channel::getType() const
return this->type_;
}
+const QString &Channel::getName() const
+{
+ return this->name_;
+}
+
bool Channel::isTwitchChannel() const
{
return this->type_ >= Type::Twitch && this->type_ < Type::TwitchEnd;
@@ -45,7 +50,7 @@ bool Channel::isTwitchChannel() const
bool Channel::isEmpty() const
{
- return this->name.isEmpty();
+ return this->name_.isEmpty();
}
LimitedQueueSnapshot Channel::getMessageSnapshot()
@@ -66,7 +71,7 @@ void Channel::addMessage(MessagePtr message)
// FOURTF: change this when adding more providers
if (this->isTwitchChannel()) {
- app->logging->addMessage(this->name, message);
+ app->logging->addMessage(this->name_, message);
}
if (this->messages_.pushBack(message, deleted)) {
diff --git a/src/common/Channel.hpp b/src/common/Channel.hpp
index 99172c108..e153914c1 100644
--- a/src/common/Channel.hpp
+++ b/src/common/Channel.hpp
@@ -41,6 +41,7 @@ public:
pajlada::Signals::NoArgSignal destroyed;
Type getType() const;
+ const QString &getName() const;
bool isTwitchChannel() const;
virtual bool isEmpty() const;
LimitedQueueSnapshot getMessageSnapshot();
@@ -52,7 +53,6 @@ public:
void replaceMessage(MessagePtr message, MessagePtr replacement);
virtual void addRecentChatter(const std::shared_ptr &message);
- QString name;
QStringList modList;
virtual bool canSendMessage() const;
@@ -72,6 +72,7 @@ protected:
virtual void onConnected();
private:
+ const QString name_;
LimitedQueue messages_;
Type type_;
QTimer clearCompletionModelTimer_;
diff --git a/src/common/Common.hpp b/src/common/Common.hpp
index 3a573163d..c4abe043f 100644
--- a/src/common/Common.hpp
+++ b/src/common/Common.hpp
@@ -1,9 +1,13 @@
#pragma once
+#include "common/Aliases.hpp"
+#include "common/Outcome.hpp"
+#include "common/ProviderId.hpp"
#include "debug/Log.hpp"
#include
#include
+#include
#include
#include
@@ -27,14 +31,10 @@ const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - ";
-#define return_if(condition) \
- if ((condition)) { \
- return; \
- }
-
-#define return_unless(condition) \
- if (!(condition)) { \
- return; \
- }
+template
+std::weak_ptr weakOf(T *element)
+{
+ return element->shared_from_this();
+}
} // namespace chatterino
diff --git a/src/common/CompletionModel.cpp b/src/common/CompletionModel.cpp
index de5bafce8..f56425d27 100644
--- a/src/common/CompletionModel.cpp
+++ b/src/common/CompletionModel.cpp
@@ -2,8 +2,10 @@
#include "Application.hpp"
#include "common/Common.hpp"
+#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandController.hpp"
#include "debug/Log.hpp"
+#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Emotes.hpp"
#include
@@ -107,41 +109,45 @@ void CompletionModel::refresh()
auto app = getApp();
// User-specific: Twitch Emotes
- // TODO: Fix this so it properly updates with the proper api. oauth token needs proper scope
- for (const auto &m : app->emotes->twitch.emotes) {
- for (const auto &emoteName : m.second.emoteCodes) {
+ if (auto account = app->accounts->twitch.getCurrent()) {
+ for (const auto &emote : account->accessEmotes()->allEmoteNames) {
// XXX: No way to discern between a twitch global emote and sub emote right now
- this->addString(emoteName, TaggedString::Type::TwitchGlobalEmote);
+ this->addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
}
}
- // Global: BTTV Global Emotes
- std::vector &bttvGlobalEmoteCodes = app->emotes->bttv.globalEmoteCodes;
- for (const auto &m : bttvGlobalEmoteCodes) {
- this->addString(m, TaggedString::Type::BTTVGlobalEmote);
+ // // Global: BTTV Global Emotes
+ // std::vector &bttvGlobalEmoteCodes = app->emotes->bttv.globalEmoteNames_;
+ // for (const auto &m : bttvGlobalEmoteCodes) {
+ // this->addString(m, TaggedString::Type::BTTVGlobalEmote);
+ // }
+
+ // // Global: FFZ Global Emotes
+ // std::vector &ffzGlobalEmoteCodes = app->emotes->ffz.globalEmoteCodes;
+ // for (const auto &m : ffzGlobalEmoteCodes) {
+ // this->addString(m, TaggedString::Type::FFZGlobalEmote);
+ // }
+
+ // Channel emotes
+ if (auto channel = dynamic_cast(
+ getApp()->twitch2->getChannelOrEmptyByID(this->channelName_).get())) {
+ auto bttv = channel->accessBttvEmotes();
+ // auto it = bttv->begin();
+ // for (const auto &emote : *bttv) {
+ // }
+ // std::vector &bttvChannelEmoteCodes =
+ // app->emotes->bttv.channelEmoteName_[this->channelName_];
+ // for (const auto &m : bttvChannelEmoteCodes) {
+ // this->addString(m, TaggedString::Type::BTTVChannelEmote);
+ // }
+
+ // Channel-specific: FFZ Channel Emotes
+ for (const auto &emote : *channel->accessFfzEmotes()) {
+ this->addString(emote.second->name.string, TaggedString::Type::FFZChannelEmote);
+ }
}
- // Global: FFZ Global Emotes
- std::vector &ffzGlobalEmoteCodes = app->emotes->ffz.globalEmoteCodes;
- for (const auto &m : ffzGlobalEmoteCodes) {
- this->addString(m, TaggedString::Type::FFZGlobalEmote);
- }
-
- // Channel-specific: BTTV Channel Emotes
- std::vector &bttvChannelEmoteCodes =
- app->emotes->bttv.channelEmoteCodes[this->channelName_];
- for (const auto &m : bttvChannelEmoteCodes) {
- this->addString(m, TaggedString::Type::BTTVChannelEmote);
- }
-
- // Channel-specific: FFZ Channel Emotes
- std::vector &ffzChannelEmoteCodes =
- app->emotes->ffz.channelEmoteCodes[this->channelName_];
- for (const auto &m : ffzChannelEmoteCodes) {
- this->addString(m, TaggedString::Type::FFZChannelEmote);
- }
-
- // Global: Emojis
+ // Emojis
const auto &emojiShortCodes = app->emotes->emojis.shortCodes;
for (const auto &m : emojiShortCodes) {
this->addString(":" + m + ":", TaggedString::Type::Emoji);
diff --git a/src/common/CompletionModel.hpp b/src/common/CompletionModel.hpp
index 5bae63be0..d9baac7b3 100644
--- a/src/common/CompletionModel.hpp
+++ b/src/common/CompletionModel.hpp
@@ -10,6 +10,8 @@
namespace chatterino {
+class TwitchChannel;
+
class CompletionModel : public QAbstractListModel
{
struct TaggedString {
diff --git a/src/common/Emotemap.cpp b/src/common/Emotemap.cpp
index b8903afd9..9bfae628b 100644
--- a/src/common/Emotemap.cpp
+++ b/src/common/Emotemap.cpp
@@ -5,42 +5,42 @@
namespace chatterino {
-EmoteData::EmoteData(Image *image)
- : image1x(image)
-{
-}
+// EmoteData::EmoteData(Image *image)
+// : image1x(image)
+//{
+//}
-// Emotes must have a 1x image to be valid
-bool EmoteData::isValid() const
-{
- return this->image1x != nullptr;
-}
+//// Emotes must have a 1x image to be valid
+// bool EmoteData::isValid() const
+//{
+// return this->image1x != nullptr;
+//}
-Image *EmoteData::getImage(float scale) const
-{
- int quality = getApp()->settings->preferredEmoteQuality;
+// Image *EmoteData::getImage(float scale) const
+//{
+// int quality = getApp()->settings->preferredEmoteQuality;
- if (quality == 0) {
- scale *= getApp()->settings->emoteScale.getValue();
- quality = [&] {
- if (scale <= 1)
- return 1;
- if (scale <= 2)
- return 2;
- return 3;
- }();
- }
+// if (quality == 0) {
+// scale *= getApp()->settings->emoteScale.getValue();
+// quality = [&] {
+// if (scale <= 1)
+// return 1;
+// if (scale <= 2)
+// return 2;
+// return 3;
+// }();
+// }
- Image *_image;
- if (quality == 3 && this->image3x != nullptr) {
- _image = this->image3x;
- } else if (quality >= 2 && this->image2x != nullptr) {
- _image = this->image2x;
- } else {
- _image = this->image1x;
- }
+// Image *_image;
+// if (quality == 3 && this->image3x != nullptr) {
+// _image = this->image3x;
+// } else if (quality >= 2 && this->image2x != nullptr) {
+// _image = this->image2x;
+// } else {
+// _image = this->image1x;
+// }
- return _image;
-}
+// return _image;
+//}
} // namespace chatterino
diff --git a/src/common/Emotemap.hpp b/src/common/Emotemap.hpp
index d4010048d..c57f08e91 100644
--- a/src/common/Emotemap.hpp
+++ b/src/common/Emotemap.hpp
@@ -5,23 +5,23 @@
namespace chatterino {
-struct EmoteData {
- EmoteData() = default;
+// struct EmoteData {
+// EmoteData() = default;
- EmoteData(Image *image);
+// EmoteData(Image *image);
- // Emotes must have a 1x image to be valid
- bool isValid() const;
- Image *getImage(float scale) const;
+// // Emotes must have a 1x image to be valid
+// bool isValid() const;
+// Image *getImage(float scale) const;
- // Link to the emote page i.e. https://www.frankerfacez.com/emoticon/144722-pajaCringe
- QString pageLink;
+// // Link to the emote page i.e. https://www.frankerfacez.com/emoticon/144722-pajaCringe
+// QString pageLink;
- Image *image1x = nullptr;
- Image *image2x = nullptr;
- Image *image3x = nullptr;
-};
+// Image *image1x = nullptr;
+// Image *image2x = nullptr;
+// Image *image3x = nullptr;
+//};
-using EmoteMap = ConcurrentMap;
+// using EmoteMap = ConcurrentMap;
} // namespace chatterino
diff --git a/src/common/NetworkCommon.hpp b/src/common/NetworkCommon.hpp
index 743eeee04..30d1e9267 100644
--- a/src/common/NetworkCommon.hpp
+++ b/src/common/NetworkCommon.hpp
@@ -2,13 +2,15 @@
#include
+#include "Common.hpp"
+
class QNetworkReply;
namespace chatterino {
class NetworkResult;
-using NetworkSuccessCallback = std::function;
+using NetworkSuccessCallback = std::function;
using NetworkErrorCallback = std::function;
using NetworkReplyCreatedCallback = std::function;
diff --git a/src/common/NetworkData.cpp b/src/common/NetworkData.cpp
index 878bda409..12aec944d 100644
--- a/src/common/NetworkData.cpp
+++ b/src/common/NetworkData.cpp
@@ -2,12 +2,23 @@
#include "Application.hpp"
#include "singletons/Paths.hpp"
+#include "util/DebugCount.hpp"
#include
#include
namespace chatterino {
+NetworkData::NetworkData()
+{
+ DebugCount::increase("NetworkData");
+}
+
+NetworkData::~NetworkData()
+{
+ DebugCount::decrease("NetworkData");
+}
+
QString NetworkData::getHash()
{
if (this->hash_.isEmpty()) {
diff --git a/src/common/NetworkData.hpp b/src/common/NetworkData.hpp
index d3cecc046..d7fbf84ba 100644
--- a/src/common/NetworkData.hpp
+++ b/src/common/NetworkData.hpp
@@ -13,6 +13,9 @@ namespace chatterino {
class NetworkResult;
struct NetworkData {
+ NetworkData();
+ ~NetworkData();
+
QNetworkRequest request_;
const QObject *caller_ = nullptr;
bool useQuickLoadCache_{};
diff --git a/src/common/NetworkRequest.cpp b/src/common/NetworkRequest.cpp
index 62929fe2a..976c0fa14 100644
--- a/src/common/NetworkRequest.cpp
+++ b/src/common/NetworkRequest.cpp
@@ -129,7 +129,7 @@ void NetworkRequest::execute()
}
}
-bool NetworkRequest::tryLoadCachedFile()
+Outcome NetworkRequest::tryLoadCachedFile()
{
auto app = getApp();
@@ -137,24 +137,24 @@ bool NetworkRequest::tryLoadCachedFile()
if (!cachedFile.exists()) {
// File didn't exist
- return false;
+ return Failure;
}
if (!cachedFile.open(QIODevice::ReadOnly)) {
// File could not be opened
- return false;
+ return Failure;
}
QByteArray bytes = cachedFile.readAll();
NetworkResult result(bytes);
- bool success = this->data->onSuccess_(result);
+ auto outcome = this->data->onSuccess_(result);
cachedFile.close();
// XXX: If success is false, we should invalidate the cache file somehow/somewhere
- return success;
+ return outcome;
}
void NetworkRequest::doRequest()
@@ -167,20 +167,21 @@ void NetworkRequest::doRequest()
this->timer->start();
auto onUrlRequested = [data = this->data, timer = this->timer, worker]() mutable {
- QNetworkReply *reply = nullptr;
- switch (data->requestType_) {
- case NetworkRequestType::Get: {
- reply = NetworkManager::NaM.get(data->request_);
- } break;
+ auto reply = [&]() -> QNetworkReply * {
+ switch (data->requestType_) {
+ case NetworkRequestType::Get:
+ return NetworkManager::NaM.get(data->request_);
- case NetworkRequestType::Put: {
- reply = NetworkManager::NaM.put(data->request_, data->payload_);
- } break;
+ case NetworkRequestType::Put:
+ return NetworkManager::NaM.put(data->request_, data->payload_);
- case NetworkRequestType::Delete: {
- reply = NetworkManager::NaM.deleteResource(data->request_);
- } break;
- }
+ case NetworkRequestType::Delete:
+ return NetworkManager::NaM.deleteResource(data->request_);
+
+ default:
+ return nullptr;
+ }
+ }();
if (reply == nullptr) {
Log("Unhandled request type");
@@ -201,8 +202,6 @@ void NetworkRequest::doRequest()
data->onReplyCreated_(reply);
}
- bool directAction = (data->caller_ == nullptr);
-
auto handleReply = [data, timer, reply]() mutable {
// TODO(pajlada): A reply was received, kill the timeout timer
if (reply->error() != QNetworkReply::NetworkError::NoError) {
@@ -222,8 +221,7 @@ void NetworkRequest::doRequest()
};
if (data->caller_ != nullptr) {
- QObject::connect(worker, &NetworkWorker::doneUrl, data->caller_,
- std::move(handleReply));
+ QObject::connect(worker, &NetworkWorker::doneUrl, data->caller_, handleReply);
QObject::connect(reply, &QNetworkReply::finished, worker, [worker]() mutable {
emit worker->doneUrl();
@@ -231,7 +229,7 @@ void NetworkRequest::doRequest()
});
} else {
QObject::connect(reply, &QNetworkReply::finished, worker,
- [handleReply = std::move(handleReply), worker]() mutable {
+ [handleReply, worker]() mutable {
handleReply();
delete worker;
diff --git a/src/common/NetworkRequest.hpp b/src/common/NetworkRequest.hpp
index e2fd2e7e4..049c2cba1 100644
--- a/src/common/NetworkRequest.hpp
+++ b/src/common/NetworkRequest.hpp
@@ -33,7 +33,7 @@ public:
explicit NetworkRequest(const std::string &url,
NetworkRequestType requestType = NetworkRequestType::Get);
- NetworkRequest(QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
+ explicit NetworkRequest(QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
~NetworkRequest();
@@ -58,7 +58,7 @@ private:
// Returns true if the file was successfully loaded from cache
// Returns false if the cache file either didn't exist, or it contained "invalid" data
// "invalid" is specified by the onSuccess callback
- bool tryLoadCachedFile();
+ Outcome tryLoadCachedFile();
void doRequest();
diff --git a/src/common/NetworkResult.cpp b/src/common/NetworkResult.cpp
index 6d8b70067..0620bf3c2 100644
--- a/src/common/NetworkResult.cpp
+++ b/src/common/NetworkResult.cpp
@@ -38,7 +38,7 @@ rapidjson::Document NetworkResult::parseRapidJson() const
return ret;
}
-QByteArray NetworkResult::getData() const
+const QByteArray &NetworkResult::getData() const
{
return this->data_;
}
diff --git a/src/common/NetworkResult.hpp b/src/common/NetworkResult.hpp
index 5631e1fe4..36a23de22 100644
--- a/src/common/NetworkResult.hpp
+++ b/src/common/NetworkResult.hpp
@@ -7,14 +7,15 @@ namespace chatterino {
class NetworkResult
{
- QByteArray data_;
-
public:
NetworkResult(const QByteArray &data);
QJsonObject parseJson() const;
rapidjson::Document parseRapidJson() const;
- QByteArray getData() const;
+ const QByteArray &getData() const;
+
+private:
+ QByteArray data_;
};
} // namespace chatterino
diff --git a/src/common/NullablePtr.hpp b/src/common/NullablePtr.hpp
index f38ecfa5d..fb60af0d8 100644
--- a/src/common/NullablePtr.hpp
+++ b/src/common/NullablePtr.hpp
@@ -1,5 +1,7 @@
#pragma once
+#include
+
namespace chatterino {
template
@@ -23,7 +25,7 @@ public:
return element_;
}
- T &operator*() const
+ typename std::add_lvalue_reference::type operator*() const
{
assert(this->hasElement());
@@ -52,6 +54,17 @@ public:
return this->hasElement();
}
+ bool operator!() const
+ {
+ return !this->hasElement();
+ }
+
+ template ::value>>
+ operator NullablePtr() const
+ {
+ return NullablePtr(this->element_);
+ }
+
private:
T *element_;
};
diff --git a/src/common/Outcome.hpp b/src/common/Outcome.hpp
new file mode 100644
index 000000000..01be69fd8
--- /dev/null
+++ b/src/common/Outcome.hpp
@@ -0,0 +1,51 @@
+#pragma once
+
+namespace chatterino {
+
+struct SuccessTag {
+};
+
+struct FailureTag {
+};
+
+const SuccessTag Success{};
+const FailureTag Failure{};
+
+class Outcome
+{
+public:
+ Outcome(SuccessTag)
+ : success_(true)
+ {
+ }
+
+ Outcome(FailureTag)
+ : success_(false)
+ {
+ }
+
+ explicit operator bool() const
+ {
+ return this->success_;
+ }
+
+ bool operator!() const
+ {
+ return !this->success_;
+ }
+
+ bool operator==(const Outcome &other) const
+ {
+ return this->success_ == other.success_;
+ }
+
+ bool operator!=(const Outcome &other) const
+ {
+ return !this->operator==(other);
+ }
+
+private:
+ bool success_;
+};
+
+} // namespace chatterino
diff --git a/src/common/Singleton.hpp b/src/common/Singleton.hpp
index 474c31f0f..a98dedac2 100644
--- a/src/common/Singleton.hpp
+++ b/src/common/Singleton.hpp
@@ -4,14 +4,18 @@
namespace chatterino {
-class Application;
+class Settings;
+class Paths;
class Singleton : boost::noncopyable
{
public:
- virtual void initialize(Application &app)
+ virtual ~Singleton() = default;
+
+ virtual void initialize(Settings &settings, Paths &paths)
{
- (void)(app);
+ (void)(settings);
+ (void)(paths);
}
virtual void save()
diff --git a/src/common/UniqueAccess.hpp b/src/common/UniqueAccess.hpp
index c4013af3e..529ac0623 100644
--- a/src/common/UniqueAccess.hpp
+++ b/src/common/UniqueAccess.hpp
@@ -10,52 +10,52 @@ class AccessGuard
{
public:
AccessGuard(T &element, std::mutex &mutex)
- : element_(element)
- , mutex_(mutex)
+ : element_(&element)
+ , mutex_(&mutex)
{
- this->mutex_.lock();
+ this->mutex_->lock();
+ }
+
+ AccessGuard(AccessGuard &&other)
+ : element_(other.element_)
+ , mutex_(other.mutex_)
+ {
+ other.isValid_ = false;
+ }
+
+ AccessGuard &operator=(AccessGuard &&other)
+ {
+ other.isValid_ = false;
+ this->element_ = other.element_;
+ this->mutex_ = other.element_;
}
~AccessGuard()
{
- this->mutex_.unlock();
+ if (this->isValid_) this->mutex_->unlock();
}
- const T *operator->() const
- {
- return &this->element_;
- }
-
- T *operator->()
- {
- return &this->element_;
- }
-
- const T &operator*() const
+ T *operator->() const
{
return this->element_;
}
- T &operator*()
+ T &operator*() const
{
- return this->element_;
- }
-
- T clone() const
- {
- return T(this->element_);
+ return *this->element_;
}
private:
- T &element_;
- std::mutex &mutex_;
+ T *element_;
+ std::mutex *mutex_;
+ bool isValid_ = true;
};
template
class UniqueAccess
{
public:
- template
+ // template
UniqueAccess()
: element_(T())
{
@@ -83,14 +83,15 @@ public:
return *this;
}
- AccessGuard access()
+ AccessGuard access() const
{
return AccessGuard(this->element_, this->mutex_);
}
- const AccessGuard access() const
+ template >>
+ AccessGuard accessConst() const
{
- return AccessGuard(this->element_, this->mutex_);
+ return AccessGuard(this->element_, this->mutex_);
}
private:
diff --git a/src/controllers/accounts/AccountController.cpp b/src/controllers/accounts/AccountController.cpp
index 0341a06a6..888c21152 100644
--- a/src/controllers/accounts/AccountController.cpp
+++ b/src/controllers/accounts/AccountController.cpp
@@ -33,7 +33,7 @@ AccountController::AccountController()
});
}
-void AccountController::initialize(Application &app)
+void AccountController::initialize(Settings &settings, Paths &paths)
{
this->twitch.load();
}
diff --git a/src/controllers/accounts/AccountController.hpp b/src/controllers/accounts/AccountController.hpp
index 69fc7f092..25f0fee5c 100644
--- a/src/controllers/accounts/AccountController.hpp
+++ b/src/controllers/accounts/AccountController.hpp
@@ -11,16 +11,19 @@
namespace chatterino {
+class Settings;
+class Paths;
+
class AccountModel;
-class AccountController : public Singleton
+class AccountController final : public Singleton
{
public:
AccountController();
AccountModel *createModel(QObject *parent);
- virtual void initialize(Application &app) override;
+ virtual void initialize(Settings &settings, Paths &paths) override;
TwitchAccountManager twitch;
diff --git a/src/controllers/commands/CommandController.cpp b/src/controllers/commands/CommandController.cpp
index c558ec376..1d85052e1 100644
--- a/src/controllers/commands/CommandController.cpp
+++ b/src/controllers/commands/CommandController.cpp
@@ -11,6 +11,7 @@
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Paths.hpp"
#include "singletons/Settings.hpp"
+#include "util/CombinePath.hpp"
#include "widgets/dialogs/LogsPopup.hpp"
#include
@@ -44,15 +45,14 @@ CommandController::CommandController()
this->items.itemRemoved.connect(addFirstMatchToMap);
}
-void CommandController::initialize(Application &app)
+void CommandController::initialize(Settings &, Paths &paths)
{
- this->load();
+ this->load(paths);
}
-void CommandController::load()
+void CommandController::load(Paths &paths)
{
- auto app = getApp();
- this->filePath_ = app->paths->settingsDirectory + "/commands.txt";
+ this->filePath_ = combinePath(paths.settingsDirectory, "commands.txt");
QFile textFile(this->filePath_);
if (!textFile.open(QIODevice::ReadOnly)) {
@@ -140,7 +140,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
app->twitch.server->whispersChannel->addMessage(b.getMessage());
- app->twitch.server->getWriteConnection()->sendRaw("PRIVMSG #jtv :" + text + "\r\n");
+ app->twitch.server->sendMessage("jtv", text);
if (getSettings()->inlineWhispers) {
app->twitch.server->forEachChannel(
diff --git a/src/controllers/commands/CommandController.hpp b/src/controllers/commands/CommandController.hpp
index c1cbfd855..45aba5bcc 100644
--- a/src/controllers/commands/CommandController.hpp
+++ b/src/controllers/commands/CommandController.hpp
@@ -10,11 +10,14 @@
#include "controllers/commands/Command.hpp"
namespace chatterino {
+
+class Settings;
+class Paths;
class Channel;
class CommandModel;
-class CommandController : public Singleton
+class CommandController final : public Singleton
{
public:
CommandController();
@@ -22,7 +25,7 @@ public:
QString execCommand(const QString &text, std::shared_ptr channel, bool dryRun);
QStringList getDefaultTwitchCommandList();
- virtual void initialize(Application &app) override;
+ virtual void initialize(Settings &settings, Paths &paths) override;
virtual void save() override;
CommandModel *createModel(QObject *parent);
@@ -30,7 +33,7 @@ public:
UnsortedSignalVector items;
private:
- void load();
+ void load(Paths &paths);
QMap commandsMap_;
diff --git a/src/controllers/highlights/HighlightController.cpp b/src/controllers/highlights/HighlightController.cpp
index 04aac6603..4829e981b 100644
--- a/src/controllers/highlights/HighlightController.cpp
+++ b/src/controllers/highlights/HighlightController.cpp
@@ -12,7 +12,7 @@ HighlightController::HighlightController()
{
}
-void HighlightController::initialize(Application &app)
+void HighlightController::initialize(Settings &settings, Paths &paths)
{
assert(!this->initialized_);
this->initialized_ = true;
diff --git a/src/controllers/highlights/HighlightController.hpp b/src/controllers/highlights/HighlightController.hpp
index 80a1f7564..09d686cb2 100644
--- a/src/controllers/highlights/HighlightController.hpp
+++ b/src/controllers/highlights/HighlightController.hpp
@@ -10,16 +10,19 @@
namespace chatterino {
+class Settings;
+class Paths;
+
class UserHighlightModel;
class HighlightModel;
class HighlightBlacklistModel;
-class HighlightController : public Singleton
+class HighlightController final : public Singleton
{
public:
HighlightController();
- virtual void initialize(Application &app) override;
+ virtual void initialize(Settings &settings, Paths &paths) override;
UnsortedSignalVector phrases;
UnsortedSignalVector blacklistedUsers;
diff --git a/src/controllers/ignores/IgnoreController.cpp b/src/controllers/ignores/IgnoreController.cpp
index 5aa271cb9..819779eff 100644
--- a/src/controllers/ignores/IgnoreController.cpp
+++ b/src/controllers/ignores/IgnoreController.cpp
@@ -7,7 +7,7 @@
namespace chatterino {
-void IgnoreController::initialize(Application &)
+void IgnoreController::initialize(Settings &, Paths &)
{
assert(!this->initialized_);
this->initialized_ = true;
diff --git a/src/controllers/ignores/IgnoreController.hpp b/src/controllers/ignores/IgnoreController.hpp
index f03460131..09c109186 100644
--- a/src/controllers/ignores/IgnoreController.hpp
+++ b/src/controllers/ignores/IgnoreController.hpp
@@ -8,12 +8,15 @@
namespace chatterino {
+class Settings;
+class Paths;
+
class IgnoreModel;
-class IgnoreController : public Singleton
+class IgnoreController final : public Singleton
{
public:
- virtual void initialize(Application &app) override;
+ virtual void initialize(Settings &settings, Paths &paths) override;
UnsortedSignalVector phrases;
diff --git a/src/controllers/moderationactions/ModerationAction.cpp b/src/controllers/moderationactions/ModerationAction.cpp
index eb477602f..d3f07b172 100644
--- a/src/controllers/moderationactions/ModerationAction.cpp
+++ b/src/controllers/moderationactions/ModerationAction.cpp
@@ -1,5 +1,6 @@
#include "ModerationAction.hpp"
+#include
#include "Application.hpp"
#include "singletons/Resources.hpp"
@@ -57,7 +58,7 @@ ModerationAction::ModerationAction(const QString &action)
// this->_moderationActions.emplace_back(app->resources->buttonTimeout, str);
// }
} else if (action.startsWith("/ban ")) {
- this->image_ = getApp()->resources->buttonBan;
+ this->image_ = Image::fromNonOwningPixmap(&getApp()->resources->buttons.ban);
} else {
QString xD = action;
@@ -75,10 +76,10 @@ bool ModerationAction::operator==(const ModerationAction &other) const
bool ModerationAction::isImage() const
{
- return this->image_ != nullptr;
+ return bool(this->image_);
}
-Image *ModerationAction::getImage() const
+const boost::optional &ModerationAction::getImage() const
{
return this->image_;
}
diff --git a/src/controllers/moderationactions/ModerationAction.hpp b/src/controllers/moderationactions/ModerationAction.hpp
index bde1040c9..f509f79c5 100644
--- a/src/controllers/moderationactions/ModerationAction.hpp
+++ b/src/controllers/moderationactions/ModerationAction.hpp
@@ -1,14 +1,14 @@
#pragma once
#include
+#include
#include
+#include "messages/Image.hpp"
#include "util/RapidjsonHelpers.hpp"
namespace chatterino {
-class Image;
-
class ModerationAction
{
public:
@@ -17,13 +17,13 @@ public:
bool operator==(const ModerationAction &other) const;
bool isImage() const;
- Image *getImage() const;
+ const boost::optional &getImage() const;
const QString &getLine1() const;
const QString &getLine2() const;
const QString &getAction() const;
private:
- Image *image_ = nullptr;
+ boost::optional image_;
QString line1_;
QString line2_;
QString action_;
diff --git a/src/controllers/moderationactions/ModerationActions.cpp b/src/controllers/moderationactions/ModerationActions.cpp
index c130e65ad..68070f6b2 100644
--- a/src/controllers/moderationactions/ModerationActions.cpp
+++ b/src/controllers/moderationactions/ModerationActions.cpp
@@ -12,7 +12,7 @@ ModerationActions::ModerationActions()
{
}
-void ModerationActions::initialize(Application &app)
+void ModerationActions::initialize(Settings &settings, Paths &paths)
{
assert(!this->initialized_);
this->initialized_ = true;
diff --git a/src/controllers/moderationactions/ModerationActions.hpp b/src/controllers/moderationactions/ModerationActions.hpp
index 934e2f95e..6ecc3281d 100644
--- a/src/controllers/moderationactions/ModerationActions.hpp
+++ b/src/controllers/moderationactions/ModerationActions.hpp
@@ -8,6 +8,9 @@
namespace chatterino {
+class Settings;
+class Paths;
+
class ModerationActionModel;
class ModerationActions final : public Singleton
@@ -15,7 +18,7 @@ class ModerationActions final : public Singleton
public:
ModerationActions();
- virtual void initialize(Application &app) override;
+ virtual void initialize(Settings &settings, Paths &paths) override;
UnsortedSignalVector items;
diff --git a/src/controllers/taggedusers/TaggedUsersController.hpp b/src/controllers/taggedusers/TaggedUsersController.hpp
index cfd98b620..263ff391c 100644
--- a/src/controllers/taggedusers/TaggedUsersController.hpp
+++ b/src/controllers/taggedusers/TaggedUsersController.hpp
@@ -9,7 +9,7 @@ namespace chatterino {
class TaggedUsersModel;
-class TaggedUsersController : public Singleton
+class TaggedUsersController final : public Singleton
{
public:
TaggedUsersController();
diff --git a/src/debug/Log.hpp b/src/debug/Log.hpp
index 6889139ee..51ef0f053 100644
--- a/src/debug/Log.hpp
+++ b/src/debug/Log.hpp
@@ -14,4 +14,11 @@ inline void Log(const std::string &formatString, Args &&... args)
<< fS(formatString, std::forward(args)...).c_str();
}
+template
+inline void Warn(const std::string &formatString, Args &&... args)
+{
+ qWarning() << QTime::currentTime().toString("hh:mm:ss.zzz")
+ << fS(formatString, std::forward(args)...).c_str();
+}
+
} // namespace chatterino
diff --git a/src/main.cpp b/src/main.cpp
index 9be014123..4ebb846a3 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,221 +1,28 @@
-#include "Application.hpp"
-#include "common/NetworkManager.hpp"
-#include "singletons/NativeMessaging.hpp"
+#include "BrowserExtension.hpp"
+#include "RunGui.hpp"
#include "singletons/Paths.hpp"
-#include "singletons/Updates.hpp"
-#include "util/DebugCount.hpp"
-#include "widgets/dialogs/LastRunCrashDialog.hpp"
+#include "singletons/Settings.hpp"
-#include
#include
-#include
-#include
#include
-#include
-#include
-#include
-#include
+using namespace chatterino;
-#ifdef Q_OS_WIN
-#include
-#include
-#include
-#endif
-
-#ifdef C_USE_BREAKPAD
-#include
-#endif
-
-int runGui(QApplication &a, int argc, char *argv[]);
-void runNativeMessagingHost();
-void installCustomPalette();
-
-//
-// Main entry point of the application.
-// Decides if it should run in gui mode, daemon mode, ...
-// Sets up the QApplication
-//
-int main(int argc, char *argv[])
+int main(int argc, char **argv)
{
- // set up the QApplication flags
- QApplication::setAttribute(Qt::AA_Use96Dpi, true);
-#ifdef Q_OS_WIN32
- QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true);
-#endif
- // QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL, true);
-
- // instanciate the QApplication
QApplication a(argc, argv);
- // FOURTF: might get arguments from the commandline passed in the future
- chatterino::Paths::initInstance();
+ // convert char[][] to QStringList
+ auto args = QStringList();
+ std::transform(argv + 1, argv + argc, std::back_inserter(args), [&](auto s) { return s; });
- // read args
- QStringList args;
-
- for (int i = 1; i < argc; i++) {
- args << argv[i];
- }
-
- // run native messaging host for the browser extension
- if (args.size() > 0 &&
- (args[0].startsWith("chrome-extension://") || args[0].endsWith(".json"))) {
- runNativeMessagingHost();
- return 0;
- }
-
- // run gui
- return runGui(a, argc, argv);
-}
-
-int runGui(QApplication &a, int argc, char *argv[])
-{
- QApplication::setStyle(QStyleFactory::create("Fusion"));
-
- installCustomPalette();
-
- // Initialize NetworkManager
- chatterino::NetworkManager::init();
-
- // Check for upates
- chatterino::Updates::getInstance().checkForUpdates();
-
- // Initialize application
- chatterino::Application::instantiate(argc, argv);
- auto app = chatterino::getApp();
-
- app->construct();
-
-#ifdef C_USE_BREAKPAD
- QBreakpadInstance.setDumpPath(app->paths->settingsFolderPath + "/Crashes");
-#endif
-
- auto &pathMan = *app->paths;
- // Running file
- auto runningPath = pathMan.miscDirectory + "/running_" + pathMan.applicationFilePathHash;
-
- if (QFile::exists(runningPath)) {
-#ifndef C_DISABLE_CRASH_DIALOG
- chatterino::LastRunCrashDialog dialog;
-
- switch (dialog.exec()) {
- case QDialog::Accepted: {
- }; break;
- default: {
- _exit(0);
- }
- }
-#endif
+ // run in gui mode or browser extension host mode
+ if (shouldRunBrowserExtensionHost(args)) {
+ runBrowserExtensionHost();
} else {
- QFile runningFile(runningPath);
+ Paths paths;
+ Settings settings(paths);
- runningFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
- runningFile.flush();
- runningFile.close();
- }
-
- app->initialize();
-
- // Start the application
- // This is a blocking call
- app->run(a);
-
- // We have finished our application, make sure we save stuff
- app->save();
-
- // Running file
- QFile::remove(runningPath);
-
- // Save settings
- pajlada::Settings::SettingManager::gSave();
-
- // Deinitialize NetworkManager (stop thread and wait for finish, should be instant)
- chatterino::NetworkManager::deinit();
-
- // None of the singletons has a proper destructor
- _exit(0);
-}
-
-void runNativeMessagingHost()
-{
- auto *nm = new chatterino::NativeMessaging;
-
-#ifdef Q_OS_WIN
- _setmode(_fileno(stdin), _O_BINARY);
- _setmode(_fileno(stdout), _O_BINARY);
-#endif
-
-#if 0
- bool bigEndian = isBigEndian();
-#endif
-
- std::atomic ping(false);
-
- QTimer timer;
- QObject::connect(&timer, &QTimer::timeout, [&ping] {
- if (!ping.exchange(false)) {
- _exit(0);
- }
- });
- timer.setInterval(11000);
- timer.start();
-
- while (true) {
- char size_c[4];
- std::cin.read(size_c, 4);
-
- if (std::cin.eof()) {
- break;
- }
-
- uint32_t size = *reinterpret_cast(size_c);
-#if 0
- // To avoid breaking strict-aliasing rules and potentially inducing undefined behaviour, the following code can be run instead
- uint32_t size = 0;
- if (bigEndian) {
- size = size_c[3] | static_cast(size_c[2]) << 8 |
- static_cast(size_c[1]) << 16 | static_cast(size_c[0]) << 24;
- } else {
- size = size_c[0] | static_cast(size_c[1]) << 8 |
- static_cast(size_c[2]) << 16 | static_cast(size_c[3]) << 24;
- }
-#endif
-
- std::unique_ptr b(new char[size + 1]);
- std::cin.read(b.get(), size);
- *(b.get() + size) = '\0';
-
- nm->sendToGuiProcess(QByteArray::fromRawData(b.get(), static_cast(size)));
+ runGui(a, paths, settings);
}
}
-
-void installCustomPalette()
-{
- // borrowed from
- // https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
- QPalette darkPalette = qApp->palette();
-
- darkPalette.setColor(QPalette::Window, QColor(22, 22, 22));
- darkPalette.setColor(QPalette::WindowText, Qt::white);
- darkPalette.setColor(QPalette::Text, Qt::white);
- darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127));
- darkPalette.setColor(QPalette::Base, QColor("#333"));
- darkPalette.setColor(QPalette::AlternateBase, QColor("#444"));
- darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
- darkPalette.setColor(QPalette::ToolTipText, Qt::white);
- darkPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127));
- darkPalette.setColor(QPalette::Dark, QColor(35, 35, 35));
- darkPalette.setColor(QPalette::Shadow, QColor(20, 20, 20));
- darkPalette.setColor(QPalette::Button, QColor(70, 70, 70));
- darkPalette.setColor(QPalette::ButtonText, Qt::white);
- darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127));
- darkPalette.setColor(QPalette::BrightText, Qt::red);
- darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
- darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
- darkPalette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80));
- darkPalette.setColor(QPalette::HighlightedText, Qt::white);
- darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127));
-
- qApp->setPalette(darkPalette);
-}
diff --git a/src/messages/Emote.cpp b/src/messages/Emote.cpp
new file mode 100644
index 000000000..369725426
--- /dev/null
+++ b/src/messages/Emote.cpp
@@ -0,0 +1,75 @@
+#include "Emote.hpp"
+
+#include
+
+namespace chatterino {
+
+bool operator==(const Emote &a, const Emote &b)
+{
+ return std::tie(a.homePage, a.name, a.tooltip, a.images) ==
+ std::tie(b.homePage, b.name, b.tooltip, b.images);
+}
+
+bool operator!=(const Emote &a, const Emote &b)
+{
+ return !(a == b);
+}
+
+// EmotePtr Emote::create(const EmoteData2 &data)
+//{
+//}
+
+// EmotePtr Emote::create(EmoteData2 &&data)
+//{
+//}
+
+// Emote::Emote(EmoteData2 &&data)
+// : data_(data)
+//{
+//}
+//
+// Emote::Emote(const EmoteData2 &data)
+// : data_(data)
+//{
+//}
+//
+// const Url &Emote::getHomePage() const
+//{
+// return this->data_.homePage;
+//}
+//
+// const EmoteName &Emote::getName() const
+//{
+// return this->data_.name;
+//}
+//
+// const Tooltip &Emote::getTooltip() const
+//{
+// return this->data_.tooltip;
+//}
+//
+// const ImageSet &Emote::getImages() const
+//{
+// return this->data_.images;
+//}
+//
+// const QString &Emote::getCopyString() const
+//{
+// return this->data_.name.string;
+//}
+//
+// bool Emote::operator==(const Emote &other) const
+//{
+// auto &a = this->data_;
+// auto &b = other.data_;
+//
+// return std::tie(a.homePage, a.name, a.tooltip, a.images) ==
+// std::tie(b.homePage, b.name, b.tooltip, b.images);
+//}
+//
+// bool Emote::operator!=(const Emote &other) const
+//{
+// return !this->operator==(other);
+//}
+
+} // namespace chatterino
diff --git a/src/messages/Emote.hpp b/src/messages/Emote.hpp
new file mode 100644
index 000000000..c5817752e
--- /dev/null
+++ b/src/messages/Emote.hpp
@@ -0,0 +1,65 @@
+#pragma once
+
+#include "messages/Image.hpp"
+#include "messages/ImageSet.hpp"
+
+#include
+#include
+#include
+
+QStringAlias(EmoteId);
+QStringAlias(EmoteName);
+
+namespace chatterino {
+
+struct Emote {
+ EmoteName name;
+ ImageSet images;
+ Tooltip tooltip;
+ Url homePage;
+
+ // FOURTF: no solution yet, to be refactored later
+ const QString &getCopyString() const
+ {
+ return name.string;
+ }
+};
+
+bool operator==(const Emote &a, const Emote &b);
+bool operator!=(const Emote &a, const Emote &b);
+
+using EmotePtr = std::shared_ptr;
+
+class EmoteMap : public std::unordered_map
+{
+};
+using EmoteIdMap = std::unordered_map;
+using WeakEmoteMap = std::unordered_map>;
+using WeakEmoteIdMap = std::unordered_map>;
+
+// struct EmoteData2 {
+// EmoteName name;
+// ImageSet images;
+// Tooltip tooltip;
+// Url homePage;
+//};
+//
+// class Emote
+//{
+// public:
+// Emote(EmoteData2 &&data);
+// Emote(const EmoteData2 &data);
+//
+// const Url &getHomePage() const;
+// const EmoteName &getName() const;
+// const Tooltip &getTooltip() const;
+// const ImageSet &getImages() const;
+// const QString &getCopyString() const;
+// bool operator==(const Emote &other) const;
+// bool operator!=(const Emote &other) const;
+//
+// private:
+// EmoteData2 data_;
+//};
+
+} // namespace chatterino
diff --git a/src/messages/EmoteCache.hpp b/src/messages/EmoteCache.hpp
new file mode 100644
index 000000000..1bbb01fd4
--- /dev/null
+++ b/src/messages/EmoteCache.hpp
@@ -0,0 +1,93 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include "common/UniqueAccess.hpp"
+#include "messages/Emote.hpp"
+
+namespace chatterino {
+
+template
+class MapReplacement
+{
+public:
+ MapReplacement(std::unordered_map &items)
+ : oldItems_(items)
+ {
+ }
+
+ void add(const TKey &key, const Emote &data)
+ {
+ this->add(key, Emote(data));
+ }
+
+ void add(const TKey &key, Emote &&data)
+ {
+ auto it = this->oldItems_.find(key);
+ if (it != this->oldItems_.end() && *it->second == data) {
+ this->newItems_[key] = it->second;
+ } else {
+ this->newItems_[key] = std::make_shared(std::move(data));
+ }
+ }
+
+ void apply()
+ {
+ this->oldItems_ = std::move(this->newItems_);
+ }
+
+private:
+ std::unordered_map &oldItems_;
+ std::unordered_map newItems_;
+};
+
+template
+class EmoteCache
+{
+public:
+ using Iterator = typename std::unordered_map::iterator;
+ using ConstIterator = typename std::unordered_map::iterator;
+
+ Iterator begin()
+ {
+ return this->items_.begin();
+ }
+
+ ConstIterator begin() const
+ {
+ return this->items_.begin();
+ }
+
+ Iterator end()
+ {
+ return this->items_.end();
+ }
+
+ ConstIterator end() const
+ {
+ return this->items_.end();
+ }
+
+ boost::optional get(const TKey &key) const
+ {
+ auto it = this->items_.find(key);
+
+ if (it == this->items_.end())
+ return boost::none;
+ else
+ return it->second;
+ }
+
+ MapReplacement makeReplacment()
+ {
+ return MapReplacement(this->items_);
+ }
+
+private:
+ std::unordered_map items_;
+};
+
+} // namespace chatterino
diff --git a/src/messages/EmoteMap.cpp b/src/messages/EmoteMap.cpp
new file mode 100644
index 000000000..f940de852
--- /dev/null
+++ b/src/messages/EmoteMap.cpp
@@ -0,0 +1,44 @@
+#include "EmoteMap.hpp"
+
+#include "Application.hpp"
+#include "singletons/Settings.hpp"
+
+namespace chatterino {
+
+// EmoteData::EmoteData(Image *image)
+// : image1x(image)
+//{
+//}
+
+//// Emotes must have a 1x image to be valid
+// bool EmoteData::isValid() const
+//{
+// return this->image1x != nullptr;
+//}
+
+// Image *EmoteData::getImage(float scale) const
+//{
+// int quality = getApp()->settings->preferredEmoteQuality;
+
+// if (quality == 0) {
+// scale *= getApp()->settings->emoteScale.getValue();
+// quality = [&] {
+// if (scale <= 1) return 1;
+// if (scale <= 2) return 2;
+// return 3;
+// }();
+// }
+
+// Image *_image;
+// if (quality == 3 && this->image3x != nullptr) {
+// _image = this->image3x;
+// } else if (quality >= 2 && this->image2x != nullptr) {
+// _image = this->image2x;
+// } else {
+// _image = this->image1x;
+// }
+
+// return _image;
+//}
+
+} // namespace chatterino
diff --git a/src/messages/EmoteMap.hpp b/src/messages/EmoteMap.hpp
new file mode 100644
index 000000000..30a0dfdd1
--- /dev/null
+++ b/src/messages/EmoteMap.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "boost/optional.hpp"
+#include "messages/Emote.hpp"
+
+namespace chatterino {
+
+// class EmoteMap
+//{
+// public:
+// void add(Emote emote);
+// void remove(const Emote &emote);
+// void remove(const QString &name);
+
+// private:
+//};
+
+// using EmoteMap = ConcurrentMap;
+
+} // namespace chatterino
diff --git a/src/messages/Image.cpp b/src/messages/Image.cpp
index 11cd4525c..309d2f0a0 100644
--- a/src/messages/Image.cpp
+++ b/src/messages/Image.cpp
@@ -2,6 +2,7 @@
#include "Application.hpp"
#include "common/NetworkRequest.hpp"
+#include "debug/AssertInGuiThread.hpp"
#include "debug/Log.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/WindowManager.hpp"
@@ -18,259 +19,287 @@
#include
namespace chatterino {
+namespace {
+// Frame
+Frame::Frame(const QPixmap *nonOwning, int duration)
+ : nonOwning_(nonOwning)
+ , duration_(duration)
+{
+}
-bool Image::loadedEventQueued = false;
+Frame::Frame(std::unique_ptr owning, int duration)
+ : owning_(std::move(owning))
+ , duration_(duration)
+{
+}
-Image::Image(const QString &url, qreal scale, const QString &name, const QString &tooltip,
- const QMargins &margin, bool isHat)
- : url(url)
- , name(name)
- , tooltip(tooltip)
- , margin(margin)
- , ishat(isHat)
- , scale(scale)
+int Frame::duration() const
+{
+ return this->duration_;
+}
+
+const QPixmap *Frame::pixmap() const
+{
+ if (this->nonOwning_) return this->nonOwning_;
+ return this->owning_.get();
+}
+
+// Frames
+Frames::Frames()
{
DebugCount::increase("images");
}
-Image::Image(QPixmap *image, qreal scale, const QString &name, const QString &tooltip,
- const QMargins &margin, bool isHat)
- : currentPixmap(image)
- , name(name)
- , tooltip(tooltip)
- , margin(margin)
- , ishat(isHat)
- , scale(scale)
- , isLoading(true)
- , isLoaded(true)
+Frames::Frames(std::vector &&frames)
+ : items_(std::move(frames))
{
DebugCount::increase("images");
+ if (this->animated()) DebugCount::increase("animated images");
}
-Image::~Image()
+Frames::~Frames()
{
DebugCount::decrease("images");
+ if (this->animated()) DebugCount::decrease("animated images");
+}
- if (this->isAnimated()) {
- DebugCount::decrease("animated images");
- }
+void Frames::advance()
+{
+ this->timeOffset_ += GIF_FRAME_LENGTH;
- if (this->isLoaded) {
- DebugCount::decrease("loaded images");
+ while (true) {
+ this->index_ %= this->items_.size();
+ if (this->timeOffset_ > this->items_[this->index_].duration()) {
+ this->timeOffset_ -= this->items_[this->index_].duration();
+ this->index_ = (this->index_ + 1) % this->items_.size();
+ } else {
+ break;
+ }
}
}
-void Image::loadImage()
+bool Frames::animated() const
{
- NetworkRequest req(this->getUrl());
- req.setCaller(this);
- req.setUseQuickLoadCache(true);
- req.onSuccess([this](auto result) -> bool {
- auto bytes = result.getData();
- QByteArray copy = QByteArray::fromRawData(bytes.constData(), bytes.length());
- QBuffer buffer(©);
- buffer.open(QIODevice::ReadOnly);
+ return this->items_.size() > 1;
+}
- QImage image;
+const QPixmap *Frames::current() const
+{
+ if (this->items_.size() == 0) return nullptr;
+ return this->items_[this->index_].pixmap();
+}
+
+const QPixmap *Frames::first() const
+{
+ if (this->items_.size() == 0) return nullptr;
+ return this->items_.front().pixmap();
+}
+
+// functions
+std::vector readFrames(QImageReader &reader, const Url &url)
+{
+ std::vector frames;
+
+ if (reader.imageCount() <= 0) {
+ Log("Error while reading image {}: '{}'", url.string, reader.errorString());
+ return frames;
+ }
+
+ QImage image;
+ for (int index = 0; index < reader.imageCount(); ++index) {
+ if (reader.read(&image)) {
+ auto pixmap = std::make_unique(QPixmap::fromImage(image));
+
+ int duration = std::max(20, reader.nextImageDelay());
+ frames.push_back(Frame(std::move(pixmap), duration));
+ }
+ }
+
+ if (frames.size() != 0) {
+ Log("Error while reading image {}: '{}'", url.string, reader.errorString());
+ }
+
+ return frames;
+}
+
+void queueLoadedEvent()
+{
+ static auto eventQueued = false;
+
+ if (!eventQueued) {
+ eventQueued = true;
+
+ QTimer::singleShot(250, [] {
+ getApp()->windows->incGeneration();
+ getApp()->windows->layoutChannelViews();
+ eventQueued = false;
+ });
+ }
+}
+} // namespace
+
+// IMAGE2
+std::atomic Image::loadedEventQueued{false};
+
+ImagePtr Image::fromUrl(const Url &url, qreal scale)
+{
+ static std::unordered_map> cache;
+ static std::mutex mutex;
+
+ std::lock_guard lock(mutex);
+
+ auto shared = cache[url].lock();
+
+ if (!shared) {
+ cache[url] = shared = ImagePtr(new Image(url, scale));
+ } else {
+ Warn("same image loaded multiple times: {}", url.string);
+ }
+
+ return shared;
+}
+
+ImagePtr Image::fromOwningPixmap(std::unique_ptr pixmap, qreal scale)
+{
+ return ImagePtr(new Image(std::move(pixmap), scale));
+}
+
+ImagePtr Image::fromNonOwningPixmap(QPixmap *pixmap, qreal scale)
+{
+ return ImagePtr(new Image(pixmap, scale));
+}
+
+ImagePtr Image::getEmpty()
+{
+ static auto empty = ImagePtr(new Image);
+ return empty;
+}
+
+Image::Image()
+ : empty_(true)
+{
+}
+
+Image::Image(const Url &url, qreal scale)
+ : url_(url)
+ , scale_(scale)
+ , shouldLoad_(true)
+{
+}
+
+Image::Image(std::unique_ptr owning, qreal scale)
+ : scale_(scale)
+{
+ std::vector vec;
+ vec.push_back(Frame(std::move(owning)));
+ this->frames_ = std::move(vec);
+}
+
+Image::Image(QPixmap *nonOwning, qreal scale)
+ : scale_(scale)
+{
+ std::vector vec;
+ vec.push_back(Frame(nonOwning));
+ this->frames_ = std::move(vec);
+}
+
+const Url &Image::url() const
+{
+ return this->url_;
+}
+
+const QPixmap *Image::pixmap() const
+{
+ assertInGuiThread();
+
+ if (this->shouldLoad_) {
+ const_cast(this)->shouldLoad_ = false;
+ const_cast(this)->load();
+ }
+
+ return this->frames_.current();
+}
+
+qreal Image::scale() const
+{
+ return this->scale_;
+}
+
+bool Image::empty() const
+{
+ return this->empty_;
+}
+
+bool Image::animated() const
+{
+ assertInGuiThread();
+
+ return this->frames_.animated();
+}
+
+int Image::width() const
+{
+ assertInGuiThread();
+
+ if (auto pixmap = this->frames_.first())
+ return pixmap->width() * this->scale_;
+ else
+ return 16;
+}
+
+int Image::height() const
+{
+ assertInGuiThread();
+
+ if (auto pixmap = this->frames_.first())
+ return pixmap->height() * this->scale_;
+ else
+ return 16;
+}
+
+void Image::load()
+{
+ NetworkRequest req(this->url().string);
+ req.setCaller(&this->object_);
+ req.setUseQuickLoadCache(true);
+ req.onSuccess([this, weak = weakOf(this)](auto result) -> Outcome {
+ assertInGuiThread();
+
+ auto shared = weak.lock();
+ if (!shared) return Failure;
+
+ // const cast since we are only reading from it
+ QBuffer buffer(const_cast(&result.getData()));
+ buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer);
- bool first = true;
+ this->frames_ = readFrames(reader, this->url());
+ return Success;
+ });
+ req.onError([this, weak = weakOf(this)](int) {
+ auto shared = weak.lock();
+ if (!shared) return false;
- // clear stuff before loading the image again
- this->allFrames.clear();
- if (this->isAnimated()) {
- DebugCount::decrease("animated images");
- }
- if (this->isLoaded) {
- DebugCount::decrease("loaded images");
- }
+ this->frames_ = std::vector();
- if (reader.imageCount() == -1) {
- // An error occured in the reader
- Log("An error occured reading the image: '{}'", reader.errorString());
- Log("Image url: {}", this->url);
- return false;
- }
-
- if (reader.imageCount() == 0) {
- Log("Error: No images read in the buffer");
- // No images read in the buffer. maybe a cache error?
- return false;
- }
-
- for (int index = 0; index < reader.imageCount(); ++index) {
- if (reader.read(&image)) {
- auto pixmap = new QPixmap(QPixmap::fromImage(image));
-
- if (first) {
- first = false;
- this->loadedPixmap = pixmap;
- }
-
- Image::FrameData data;
- data.duration = std::max(20, reader.nextImageDelay());
- data.image = pixmap;
-
- this->allFrames.push_back(data);
- }
- }
-
- if (this->allFrames.size() != reader.imageCount()) {
- // Log("Error: Wrong amount of images read");
- // One or more images failed to load from the buffer
- // return false;
- }
-
- if (this->allFrames.size() > 1) {
- if (!this->animated) {
- postToThread([this] {
- getApp()->emotes->gifTimer.signal.connect([=]() {
- this->gifUpdateTimout();
- }); // For some reason when Boost signal is in
- // thread scope and thread deletes the signal
- // doesn't work, so this is the fix.
- });
- }
-
- this->animated = true;
-
- DebugCount::increase("animated images");
- }
-
- this->currentPixmap = this->loadedPixmap;
-
- this->isLoaded = true;
- DebugCount::increase("loaded images");
-
- if (!loadedEventQueued) {
- loadedEventQueued = true;
-
- QTimer::singleShot(500, [] {
- getApp()->windows->incGeneration();
-
- auto app = getApp();
- app->windows->layoutChannelViews();
- loadedEventQueued = false;
- });
- }
-
- return true;
+ return false;
});
req.execute();
}
-void Image::gifUpdateTimout()
+bool Image::operator==(const Image &other) const
{
- if (this->animated) {
- this->currentFrameOffset += GIF_FRAME_LENGTH;
+ if (this->empty() && other.empty()) return true;
+ if (!this->url_.string.isEmpty() && this->url_ == other.url_) return true;
+ if (this->frames_.first() == other.frames_.first()) return true;
- while (true) {
- if (this->currentFrameOffset > this->allFrames.at(this->currentFrame).duration) {
- this->currentFrameOffset -= this->allFrames.at(this->currentFrame).duration;
- this->currentFrame = (this->currentFrame + 1) % this->allFrames.size();
- } else {
- break;
- }
- }
-
- this->currentPixmap = this->allFrames[this->currentFrame].image;
- }
+ return false;
}
-const QPixmap *Image::getPixmap()
+bool Image::operator!=(const Image &other) const
{
- if (!this->isLoading) {
- this->isLoading = true;
-
- this->loadImage();
-
- return nullptr;
- }
-
- if (this->isLoaded) {
- return this->currentPixmap;
- } else {
- return nullptr;
- }
-}
-
-qreal Image::getScale() const
-{
- return this->scale;
-}
-
-const QString &Image::getUrl() const
-{
- return this->url;
-}
-
-const QString &Image::getName() const
-{
- return this->name;
-}
-
-const QString &Image::getCopyString() const
-{
- if (this->copyString.isEmpty()) {
- return this->name;
- }
-
- return this->copyString;
-}
-
-const QString &Image::getTooltip() const
-{
- return this->tooltip;
-}
-
-const QMargins &Image::getMargin() const
-{
- return this->margin;
-}
-
-bool Image::isAnimated() const
-{
- return this->animated;
-}
-
-bool Image::isHat() const
-{
- return this->ishat;
-}
-
-int Image::getWidth() const
-{
- if (this->currentPixmap == nullptr) {
- return 16;
- }
-
- return this->currentPixmap->width();
-}
-
-int Image::getScaledWidth() const
-{
- return static_cast((float)this->getWidth() * this->scale *
- getApp()->settings->emoteScale.getValue());
-}
-
-int Image::getHeight() const
-{
- if (this->currentPixmap == nullptr) {
- return 16;
- }
- return this->currentPixmap->height();
-}
-
-int Image::getScaledHeight() const
-{
- return static_cast((float)this->getHeight() * this->scale *
- getApp()->settings->emoteScale.getValue());
-}
-
-void Image::setCopyString(const QString &newCopyString)
-{
- this->copyString = newCopyString;
+ return !this->operator==(other);
}
} // namespace chatterino
diff --git a/src/messages/Image.hpp b/src/messages/Image.hpp
index c25c4db45..7db43580c 100644
--- a/src/messages/Image.hpp
+++ b/src/messages/Image.hpp
@@ -1,69 +1,90 @@
#pragma once
+#include "common/Common.hpp"
+
#include
#include
-#include
-
#include
+#include
+#include
+#include
+
+#include "common/NullablePtr.hpp"
namespace chatterino {
-
-class Image : public QObject, boost::noncopyable
+namespace {
+class Frame
{
public:
- explicit Image(const QString &_url, qreal _scale = 1, const QString &_name = "",
- const QString &_tooltip = "", const QMargins &_margin = QMargins(),
- bool isHat = false);
+ explicit Frame(const QPixmap *nonOwning, int duration = 1);
+ explicit Frame(std::unique_ptr owning, int duration = 1);
- explicit Image(QPixmap *_currentPixmap, qreal _scale = 1, const QString &_name = "",
- const QString &_tooltip = "", const QMargins &_margin = QMargins(),
- bool isHat = false);
- ~Image();
-
- const QPixmap *getPixmap();
- qreal getScale() const;
- const QString &getUrl() const;
- const QString &getName() const;
- const QString &getCopyString() const;
- const QString &getTooltip() const;
- const QMargins &getMargin() const;
- bool isAnimated() const;
- bool isHat() const;
- int getWidth() const;
- int getScaledWidth() const;
- int getHeight() const;
- int getScaledHeight() const;
-
- void setCopyString(const QString &newCopyString);
+ const QPixmap *pixmap() const;
+ int duration() const;
private:
- struct FrameData {
- QPixmap *image;
- int duration;
- };
-
- static bool loadedEventQueued;
-
- QPixmap *currentPixmap = nullptr;
- QPixmap *loadedPixmap = nullptr;
- std::vector allFrames;
- int currentFrame = 0;
- int currentFrameOffset = 0;
-
- QString url;
- QString name;
- QString copyString;
- QString tooltip;
- bool animated = false;
- QMargins margin;
- bool ishat;
- qreal scale;
-
- bool isLoading = false;
- std::atomic isLoaded{false};
-
- void loadImage();
- void gifUpdateTimout();
+ const QPixmap *nonOwning_{nullptr};
+ std::unique_ptr owning_{};
+ int duration_{};
};
+class Frames
+{
+public:
+ Frames();
+ Frames(std::vector &&frames);
+ ~Frames();
+ Frames(Frames &&other) = default;
+ Frames &operator=(Frames &&other) = default;
+ bool animated() const;
+ void advance();
+ const QPixmap *current() const;
+ const QPixmap *first() const;
+
+private:
+ std::vector items_;
+ int index_{0};
+ int timeOffset_{0};
+};
+} // namespace
+
+class Image;
+using ImagePtr = std::shared_ptr;
+
+class Image : public std::enable_shared_from_this, boost::noncopyable
+{
+public:
+ static ImagePtr fromUrl(const Url &url, qreal scale = 1);
+ static ImagePtr fromOwningPixmap(std::unique_ptr pixmap, qreal scale = 1);
+ static ImagePtr fromNonOwningPixmap(QPixmap *pixmap, qreal scale = 1);
+ static ImagePtr getEmpty();
+
+ const Url &url() const;
+ const QPixmap *pixmap() const;
+ qreal scale() const;
+ bool empty() const;
+ int width() const;
+ int height() const;
+ bool animated() const;
+
+ bool operator==(const Image &image) const;
+ bool operator!=(const Image &image) const;
+
+private:
+ Image();
+ Image(const Url &url, qreal scale);
+ Image(std::unique_ptr owning, qreal scale);
+ Image(QPixmap *nonOwning, qreal scale);
+
+ void load();
+
+ Url url_{};
+ qreal scale_{1};
+ bool empty_{false};
+ bool shouldLoad_{false};
+ Frames frames_{};
+ QObject object_{};
+
+ static std::atomic loadedEventQueued;
+};
} // namespace chatterino
diff --git a/src/messages/ImageSet.cpp b/src/messages/ImageSet.cpp
new file mode 100644
index 000000000..ebb9b77c7
--- /dev/null
+++ b/src/messages/ImageSet.cpp
@@ -0,0 +1,95 @@
+#include "ImageSet.hpp"
+
+#include "Application.hpp"
+#include "singletons/Resources.hpp"
+#include "singletons/Settings.hpp"
+
+namespace chatterino {
+
+ImageSet::ImageSet()
+ : imageX1_(Image::getEmpty())
+ , imageX2_(Image::getEmpty())
+ , imageX3_(Image::getEmpty())
+{
+}
+
+ImageSet::ImageSet(const ImagePtr &image1, const ImagePtr &image2, const ImagePtr &image3)
+ : imageX1_(image1)
+ , imageX2_(image2)
+ , imageX3_(image3)
+{
+}
+
+ImageSet::ImageSet(const Url &image1, const Url &image2, const Url &image3)
+ : imageX1_(Image::fromUrl(image1, 1))
+ , imageX2_(Image::fromUrl(image2, 0.5))
+ , imageX3_(Image::fromUrl(image3, 0.25))
+{
+}
+
+void ImageSet::setImage1(const ImagePtr &image)
+{
+ this->imageX1_ = image;
+}
+
+void ImageSet::setImage2(const ImagePtr &image)
+{
+ this->imageX2_ = image;
+}
+
+void ImageSet::setImage3(const ImagePtr &image)
+{
+ this->imageX3_ = image;
+}
+
+const ImagePtr &ImageSet::getImage1() const
+{
+ return this->imageX1_;
+}
+
+const ImagePtr &ImageSet::getImage2() const
+{
+ return this->imageX2_;
+}
+
+const ImagePtr &ImageSet::getImage3() const
+{
+ return this->imageX3_;
+}
+
+const ImagePtr &ImageSet::getImage(float scale) const
+{
+ int quality = getSettings()->preferredEmoteQuality;
+
+ if (!quality) {
+ if (scale > 3.999)
+ quality = 3;
+ else if (scale > 1.999)
+ quality = 2;
+ else
+ scale = 1;
+ }
+
+ if (!this->imageX3_->empty() && quality == 3) {
+ return this->imageX3_;
+ }
+
+ if (!this->imageX2_->empty() && quality == 2) {
+ return this->imageX3_;
+ }
+
+ return this->imageX1_;
+}
+
+bool ImageSet::operator==(const ImageSet &other) const
+{
+ return std::tie(this->imageX1_, this->imageX2_, this->imageX3_) ==
+ std::tie(other.imageX1_, other.imageX2_, other.imageX3_);
+}
+
+bool ImageSet::operator!=(const ImageSet &other) const
+{
+ return !this->operator==(other);
+}
+
+} // namespace chatterino
diff --git a/src/messages/ImageSet.hpp b/src/messages/ImageSet.hpp
new file mode 100644
index 000000000..8f2efab2d
--- /dev/null
+++ b/src/messages/ImageSet.hpp
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "messages/Image.hpp"
+
+namespace chatterino {
+
+class ImageSet
+{
+public:
+ ImageSet();
+ ImageSet(const ImagePtr &image1, const ImagePtr &image2 = Image::getEmpty(),
+ const ImagePtr &image3 = Image::getEmpty());
+ ImageSet(const Url &image1, const Url &image2 = {}, const Url &image3 = {});
+
+ void setImage1(const ImagePtr &image);
+ void setImage2(const ImagePtr &image);
+ void setImage3(const ImagePtr &image);
+ const ImagePtr &getImage1() const;
+ const ImagePtr &getImage2() const;
+ const ImagePtr &getImage3() const;
+
+ const ImagePtr &getImage(float scale) const;
+
+ ImagePtr getImage(float scale);
+
+ bool operator==(const ImageSet &other) const;
+ bool operator!=(const ImageSet &other) const;
+
+private:
+ ImagePtr imageX1_;
+ ImagePtr imageX2_;
+ ImagePtr imageX3_;
+};
+
+} // namespace chatterino
diff --git a/src/messages/Link.hpp b/src/messages/Link.hpp
index a07d21cf3..a6c503540 100644
--- a/src/messages/Link.hpp
+++ b/src/messages/Link.hpp
@@ -1,6 +1,7 @@
#pragma once
#include
+#include
namespace chatterino {
diff --git a/src/messages/MessageElement.cpp b/src/messages/MessageElement.cpp
index 5081f5e47..ff62aca4e 100644
--- a/src/messages/MessageElement.cpp
+++ b/src/messages/MessageElement.cpp
@@ -60,18 +60,18 @@ MessageElement::Flags MessageElement::getFlags() const
}
// IMAGE
-ImageElement::ImageElement(Image *image, MessageElement::Flags flags)
+ImageElement::ImageElement(ImagePtr image, MessageElement::Flags flags)
: MessageElement(flags)
, image_(image)
{
- this->setTooltip(image->getTooltip());
+ // this->setTooltip(image->getTooltip());
}
void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags)
{
if (flags & this->getFlags()) {
- QSize size(this->image_->getScaledWidth() * container.getScale(),
- this->image_->getScaledHeight() * container.getScale());
+ auto size = QSize(this->image_->width() * container.getScale(),
+ this->image_->height() * container.getScale());
container.addElement(
(new ImageLayoutElement(*this, this->image_, size))->setLink(this->getLink()));
@@ -79,29 +79,29 @@ void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElem
}
// EMOTE
-EmoteElement::EmoteElement(const EmoteData &data, MessageElement::Flags flags)
+EmoteElement::EmoteElement(const EmotePtr &emote, MessageElement::Flags flags)
: MessageElement(flags)
- , data(data)
+ , emote_(emote)
{
- if (data.isValid()) {
- this->setTooltip(data.image1x->getTooltip());
- this->textElement_.reset(
- new TextElement(data.image1x->getCopyString(), MessageElement::Misc));
- }
+ this->textElement_.reset(new TextElement(emote->getCopyString(), MessageElement::Misc));
+
+ this->setTooltip(emote->tooltip.string);
+}
+
+EmotePtr EmoteElement::getEmote() const
+{
+ return this->emote_;
}
void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags)
{
if (flags & this->getFlags()) {
if (flags & MessageElement::EmoteImages) {
- if (!this->data.isValid()) {
- return;
- }
+ auto image = this->emote_->images.getImage(container.getScale());
+ if (image->empty()) return;
- Image *image = this->data.getImage(container.getScale());
-
- QSize size(int(container.getScale() * image->getScaledWidth()),
- int(container.getScale() * image->getScaledHeight()));
+ auto size = QSize(int(container.getScale() * image->width()),
+ int(container.getScale() * image->height()));
container.addElement(
(new ImageLayoutElement(*this, image, size))->setLink(this->getLink()));
@@ -120,7 +120,7 @@ TextElement::TextElement(const QString &text, MessageElement::Flags flags,
, color_(color)
, style_(style)
{
- for (QString word : text.split(' ')) {
+ for (const auto &word : text.split(' ')) {
this->words_.push_back({word, -1});
// fourtf: add logic to store multiple spaces after message
}
@@ -173,7 +173,6 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
int textLength = text.length();
int wordStart = 0;
int width = metrics.width(text[0]);
- int lastWidth = 0;
for (int i = 1; i < textLength; i++) {
int charWidth = metrics.width(text[i]);
@@ -184,7 +183,6 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
container.breakLine();
wordStart = i;
- lastWidth = width;
width = 0;
if (textLength > i + 2) {
width += metrics.width(text[i]);
@@ -196,8 +194,6 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
width += charWidth;
}
- UNUSED(lastWidth); // XXX: What should this be used for (if anything)? KKona
-
container.addElement(
getTextLayoutElement(text.mid(wordStart), width, this->hasTrailingSpace()));
container.breakLine();
@@ -249,14 +245,15 @@ void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
if (flags & MessageElement::ModeratorTools) {
QSize size(int(container.getScale() * 16), int(container.getScale() * 16));
- for (const ModerationAction &m : getApp()->moderationActions->items.getVector()) {
- if (m.isImage()) {
- container.addElement((new ImageLayoutElement(*this, m.getImage(), size))
- ->setLink(Link(Link::UserAction, m.getAction())));
+ for (const auto &action : getApp()->moderationActions->items.getVector()) {
+ if (auto image = action.getImage()) {
+ container.addElement((new ImageLayoutElement(*this, image.get(), size))
+ ->setLink(Link(Link::UserAction, action.getAction())));
} else {
- container.addElement((new TextIconLayoutElement(*this, m.getLine1(), m.getLine2(),
- container.getScale(), size))
- ->setLink(Link(Link::UserAction, m.getAction())));
+ container.addElement(
+ (new TextIconLayoutElement(*this, action.getLine1(), action.getLine2(),
+ container.getScale(), size))
+ ->setLink(Link(Link::UserAction, action.getAction())));
}
}
}
diff --git a/src/messages/MessageElement.hpp b/src/messages/MessageElement.hpp
index 89f2f2f3c..2aa25b7e7 100644
--- a/src/messages/MessageElement.hpp
+++ b/src/messages/MessageElement.hpp
@@ -1,6 +1,7 @@
#pragma once
#include "common/Emotemap.hpp"
+#include "messages/Emote.hpp"
#include "messages/Image.hpp"
#include "messages/Link.hpp"
#include "messages/MessageColor.hpp"
@@ -16,7 +17,6 @@
namespace chatterino {
class Channel;
-struct EmoteData;
struct MessageLayoutContainer;
class MessageElement : boost::noncopyable
@@ -137,12 +137,12 @@ private:
class ImageElement : public MessageElement
{
public:
- ImageElement(Image *image, MessageElement::Flags flags);
+ ImageElement(ImagePtr image, MessageElement::Flags flags);
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
private:
- Image *image_;
+ ImagePtr image_;
};
// contains a text, it will split it into words
@@ -173,15 +173,14 @@ private:
class EmoteElement : public MessageElement
{
public:
- EmoteElement(const EmoteData &data, MessageElement::Flags flags_);
- ~EmoteElement() override = default;
+ EmoteElement(const EmotePtr &data, MessageElement::Flags flags_);
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags_) override;
-
- const EmoteData data;
+ EmotePtr getEmote() const;
private:
std::unique_ptr textElement_;
+ EmotePtr emote_;
};
// contains a text, formated depending on the preferences
diff --git a/src/messages/layouts/MessageLayoutElement.cpp b/src/messages/layouts/MessageLayoutElement.cpp
index c71858eec..3e2c649d2 100644
--- a/src/messages/layouts/MessageLayoutElement.cpp
+++ b/src/messages/layouts/MessageLayoutElement.cpp
@@ -63,16 +63,17 @@ const Link &MessageLayoutElement::getLink() const
// IMAGE
//
-ImageLayoutElement::ImageLayoutElement(MessageElement &_creator, Image *_image, const QSize &_size)
- : MessageLayoutElement(_creator, _size)
- , image(_image)
+ImageLayoutElement::ImageLayoutElement(MessageElement &creator, ImagePtr image, const QSize &size)
+ : MessageLayoutElement(creator, size)
+ , image_(image)
{
- this->trailingSpace = _creator.hasTrailingSpace();
+ this->trailingSpace = creator.hasTrailingSpace();
}
void ImageLayoutElement::addCopyTextToString(QString &str, int from, int to) const
{
- str += this->image->getCopyString();
+ // str += this->image_->getCopyString();
+ str += "not implemented";
if (this->hasTrailingSpace()) {
str += " ";
@@ -86,13 +87,12 @@ int ImageLayoutElement::getSelectionIndexCount()
void ImageLayoutElement::paint(QPainter &painter)
{
- if (this->image == nullptr) {
+ if (this->image_ == nullptr) {
return;
}
- const QPixmap *pixmap = this->image->getPixmap();
-
- if (pixmap != nullptr && !this->image->isAnimated()) {
+ auto pixmap = this->image_->pixmap();
+ if (pixmap && !this->image_->animated()) {
// fourtf: make it use qreal values
painter.drawPixmap(QRectF(this->getRect()), *pixmap, QRectF());
}
@@ -100,19 +100,15 @@ void ImageLayoutElement::paint(QPainter &painter)
void ImageLayoutElement::paintAnimated(QPainter &painter, int yOffset)
{
- if (this->image == nullptr) {
+ if (this->image_ == nullptr) {
return;
}
- if (this->image->isAnimated()) {
- // qDebug() << this->image->getUrl();
- auto pixmap = this->image->getPixmap();
-
- if (pixmap != nullptr) {
- // fourtf: make it use qreal values
- QRect _rect = this->getRect();
- _rect.moveTop(_rect.y() + yOffset);
- painter.drawPixmap(QRectF(_rect), *pixmap, QRectF());
+ if (this->image_->animated()) {
+ if (auto pixmap = this->image_->pixmap()) {
+ auto rect = this->getRect();
+ rect.moveTop(rect.y() + yOffset);
+ painter.drawPixmap(QRectF(rect), *pixmap, QRectF());
}
}
}
diff --git a/src/messages/layouts/MessageLayoutElement.hpp b/src/messages/layouts/MessageLayoutElement.hpp
index 3d14b0f32..d8221726f 100644
--- a/src/messages/layouts/MessageLayoutElement.hpp
+++ b/src/messages/layouts/MessageLayoutElement.hpp
@@ -7,6 +7,7 @@
#include
#include
+#include "messages/Image.hpp"
#include "messages/Link.hpp"
#include "messages/MessageColor.hpp"
#include "singletons/Fonts.hpp"
@@ -15,7 +16,6 @@ class QPainter;
namespace chatterino {
class MessageElement;
-class Image;
class MessageLayoutElement : boost::noncopyable
{
@@ -52,7 +52,7 @@ private:
class ImageLayoutElement : public MessageLayoutElement
{
public:
- ImageLayoutElement(MessageElement &creator_, Image *image, const QSize &size);
+ ImageLayoutElement(MessageElement &creator, ImagePtr image, const QSize &size);
protected:
void addCopyTextToString(QString &str, int from = 0, int to = INT_MAX) const override;
@@ -63,7 +63,7 @@ protected:
int getXFromIndex(int index) override;
private:
- Image *image;
+ ImagePtr image_;
};
// TEXT
diff --git a/src/providers/bttv/BttvEmotes.cpp b/src/providers/bttv/BttvEmotes.cpp
index 391be6e6f..570879210 100644
--- a/src/providers/bttv/BttvEmotes.cpp
+++ b/src/providers/bttv/BttvEmotes.cpp
@@ -3,122 +3,107 @@
#include "common/NetworkRequest.hpp"
#include "debug/Log.hpp"
#include "messages/Image.hpp"
+#include "messages/ImageSet.hpp"
+#include "providers/twitch/TwitchChannel.hpp"
+
+#include
+#include
namespace chatterino {
namespace {
-QString getEmoteLink(QString urlTemplate, const QString &id, const QString &emoteScale)
+Url getEmoteLink(QString urlTemplate, const EmoteId &id, const QString &emoteScale)
{
urlTemplate.detach();
- return urlTemplate.replace("{{id}}", id).replace("{{image}}", emoteScale);
+ return {urlTemplate.replace("{{id}}", id.string).replace("{{image}}", emoteScale)};
}
} // namespace
-void BTTVEmotes::loadGlobalEmotes()
+AccessGuard BttvEmotes::accessGlobalEmotes() const
{
- QString url("https://api.betterttv.net/2/emotes");
+ return this->globalEmotes_.accessConst();
+}
+
+boost::optional BttvEmotes::getGlobalEmote(const EmoteName &name)
+{
+ auto emotes = this->globalEmotes_.access();
+ auto it = emotes->find(name);
+
+ if (it == emotes->end()) return boost::none;
+ return it->second;
+}
+
+// FOURTF: never returns anything
+// boost::optional BttvEmotes::getEmote(const EmoteId &id)
+//{
+// auto cache = this->channelEmoteCache_.access();
+// auto it = cache->find(id);
+//
+// if (it != cache->end()) {
+// auto shared = it->second.lock();
+// if (shared) {
+// return shared;
+// }
+// }
+//
+// return boost::none;
+//}
+
+void BttvEmotes::loadGlobalEmotes()
+{
+ auto request = NetworkRequest(QString(globalEmoteApiUrl));
- NetworkRequest request(url);
request.setCaller(QThread::currentThread());
request.setTimeout(30000);
- request.onSuccess([this](auto result) {
- auto root = result.parseJson();
- auto emotes = root.value("emotes").toArray();
+ request.onSuccess([this](auto result) -> Outcome {
+ // if (auto shared = weak.lock()) {
+ auto currentEmotes = this->globalEmotes_.access();
- QString urlTemplate = "https:" + root.value("urlTemplate").toString();
+ auto pair = this->parseGlobalEmotes(result.parseJson(), *currentEmotes);
- std::vector codes;
- for (const QJsonValue &emote : emotes) {
- QString id = emote.toObject().value("id").toString();
- QString code = emote.toObject().value("code").toString();
-
- EmoteData emoteData;
- emoteData.image1x = new Image(getEmoteLink(urlTemplate, id, "1x"), 1, code,
- code + "
Global BTTV Emote");
- emoteData.image2x = new Image(getEmoteLink(urlTemplate, id, "2x"), 0.5, code,
- code + "
Global BTTV Emote");
- emoteData.image3x = new Image(getEmoteLink(urlTemplate, id, "3x"), 0.25, code,
- code + "
Global BTTV Emote");
- emoteData.pageLink = "https://manage.betterttv.net/emotes/" + id;
-
- this->globalEmotes.insert(code, emoteData);
- codes.push_back(code);
+ if (pair.first) {
+ *currentEmotes = std::move(pair.second);
}
- this->globalEmoteCodes = codes;
-
- return true;
+ return pair.first;
+ // }
+ return Failure;
});
request.execute();
}
-void BTTVEmotes::loadChannelEmotes(const QString &channelName, std::weak_ptr _map)
+std::pair BttvEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot,
+ const EmoteMap ¤tEmotes)
{
- printf("[BTTVEmotes] Reload BTTV Channel Emotes for channel %s\n", qPrintable(channelName));
+ auto emotes = EmoteMap();
+ auto jsonEmotes = jsonRoot.value("emotes").toArray();
+ auto urlTemplate = QString("https:" + jsonRoot.value("urlTemplate").toString());
- QString url("https://api.betterttv.net/2/channels/" + channelName);
+ for (const QJsonValue &jsonEmote : jsonEmotes) {
+ auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
+ auto name = EmoteName{jsonEmote.toObject().value("code").toString()};
- Log("Request bttv channel emotes for {}", channelName);
+ auto emote = Emote({name,
+ ImageSet{Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
+ Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
+ Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
+ Tooltip{name.string + "
Global Bttv Emote"},
+ Url{"https://manage.betterttv.net/emotes/" + id.string}});
- NetworkRequest request(url);
- request.setCaller(QThread::currentThread());
- request.setTimeout(3000);
- request.onSuccess([this, channelName, _map](auto result) {
- auto rootNode = result.parseJson();
- auto map = _map.lock();
-
- if (_map.expired()) {
- return false;
+ auto it = currentEmotes.find(name);
+ if (it != currentEmotes.end() && *it->second == emote) {
+ // reuse old shared_ptr if nothing changed
+ emotes[name] = it->second;
+ } else {
+ emotes[name] = std::make_shared(std::move(emote));
}
+ }
- map->clear();
-
- auto emotesNode = rootNode.value("emotes").toArray();
-
- QString linkTemplate = "https:" + rootNode.value("urlTemplate").toString();
-
- std::vector codes;
- for (const QJsonValue &emoteNode : emotesNode) {
- QJsonObject emoteObject = emoteNode.toObject();
-
- QString id = emoteObject.value("id").toString();
- QString code = emoteObject.value("code").toString();
- // emoteObject.value("imageType").toString();
-
- auto emote = this->channelEmoteCache_.getOrAdd(id, [&] {
- EmoteData emoteData;
- QString link = linkTemplate;
- link.detach();
- emoteData.image1x = new Image(link.replace("{{id}}", id).replace("{{image}}", "1x"),
- 1, code, code + "
Channel BTTV Emote");
- link = linkTemplate;
- link.detach();
- emoteData.image2x = new Image(link.replace("{{id}}", id).replace("{{image}}", "2x"),
- 0.5, code, code + "
Channel BTTV Emote");
- link = linkTemplate;
- link.detach();
- emoteData.image3x = new Image(link.replace("{{id}}", id).replace("{{image}}", "3x"),
- 0.25, code, code + "
Channel BTTV Emote");
- emoteData.pageLink = "https://manage.betterttv.net/emotes/" + id;
-
- return emoteData;
- });
-
- this->channelEmotes.insert(code, emote);
- map->insert(code, emote);
- codes.push_back(code);
- }
-
- this->channelEmoteCodes[channelName] = codes;
-
- return true;
- });
-
- request.execute();
+ return {Success, std::move(emotes)};
}
} // namespace chatterino
diff --git a/src/providers/bttv/BttvEmotes.hpp b/src/providers/bttv/BttvEmotes.hpp
index 134e5a157..f8273afb8 100644
--- a/src/providers/bttv/BttvEmotes.hpp
+++ b/src/providers/bttv/BttvEmotes.hpp
@@ -1,27 +1,32 @@
#pragma once
-#include "common/Emotemap.hpp"
-#include "common/SimpleSignalVector.hpp"
-#include "util/ConcurrentMap.hpp"
+#include
-#include