Merge branch 'master' of https://github.com/fourtf/chatterino2 into blacklist

This commit is contained in:
hemirt 2018-08-11 22:38:16 +02:00
commit a63454f00d
352 changed files with 9117 additions and 6492 deletions

View file

@ -1,23 +1,14 @@
IndentCaseLabels: true
BasedOnStyle: Google
IndentWidth: 4
Standard: Auto
PointerBindsToType: false
Language: Cpp
SpacesBeforeTrailingComments: 2
AccessModifierOffset: -1
AccessModifierOffset: -4
AlignEscapedNewlinesLeft: true
AllowShortFunctionsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: false
AlwaysBreakBeforeMultilineStrings: false
BreakConstructorInitializersBeforeComma: true
# BreakBeforeBraces: Linux
BreakBeforeBraces: Custom
AccessModifierOffset: -4
ConstructorInitializerAllOnOneLineOrOnePerLine: false
AllowShortFunctionsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
DerivePointerBinding: false
BasedOnStyle: Google
BraceWrapping: {
AfterNamespace: 'false'
AfterClass: 'true'
@ -26,5 +17,14 @@ BraceWrapping: {
AfterFunction: 'true'
BeforeCatch: 'false'
}
ColumnLimit: 100
BreakBeforeBraces: Custom
BreakConstructorInitializersBeforeComma: true
ColumnLimit: 80
ConstructorInitializerAllOnOneLineOrOnePerLine: false
DerivePointerBinding: false
FixNamespaceComments: true
IndentCaseLabels: true
IndentWidth: 4
PointerBindsToType: false
SpacesBeforeTrailingComments: 2
Standard: Auto

View file

@ -3,7 +3,9 @@
1. Install Xcode and Xcode Command Line Utilites
2. Start Xcode, settings -> Locations, activate your Command Line Tools
3. Install brew https://brew.sh/
4. `brew install boost openssl rapidjson qt`
4. `brew install boost openssl rapidjson`
5. `brew install qt`
6. Step 5 should output some directions to add qt to your path, you will need to do this for qmake
5. Go into project directory
6. Create build folder `mkdir build && cd build`
7. `qmake .. && make`

20
Jenkinsfile vendored Normal file
View file

@ -0,0 +1,20 @@
pipeline {
agent any
stages {
stage('Build') {
parallel {
stage('GCC') {
steps {
sh 'mkdir -p build-linux-gcc && cd build-linux-gcc && make distclean; qmake .. && make'
}
}
stage('Clang') {
steps {
sh 'mkdir -p build-linux-clang && cd build-linux-clang && make distclean; qmake -spec linux-clang .. && make'
}
}
}
}
}
}

View file

@ -23,8 +23,6 @@ Before building run `git submodule update --init --recursive` to get required su
## Code style
The code is formated using clang format in Qt Creator. [.clang-format](https://github.com/fourtf/chatterino2/blob/master/.clang-format) contains the style file for clang format.
To setup automatic code formating with QT Creator, see [this guide](https://gist.github.com/pajlada/0296454198eb8f8789fd6fe7ea660c5b).
### Get it automated with QT Creator + Beautifier + Clang Format
1. Download LLVM: http://releases.llvm.org/6.0.1/LLVM-6.0.1-win64.exe
2. During the installation, make sure to add it to your path

View file

@ -6,7 +6,7 @@
message(----)
QT += widgets core gui network multimedia svg
QT += widgets core gui network multimedia svg concurrent
CONFIG += communi
COMMUNI += core model util
CONFIG += c++14
@ -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)
@ -101,7 +105,6 @@ SOURCES += \
src/Application.cpp \
src/common/Channel.cpp \
src/common/CompletionModel.cpp \
src/common/Emotemap.cpp \
src/common/NetworkData.cpp \
src/common/NetworkManager.cpp \
src/common/NetworkRequest.cpp \
@ -132,9 +135,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 \
@ -184,8 +185,6 @@ SOURCES += \
src/widgets/helper/NotebookButton.cpp \
src/widgets/helper/NotebookTab.cpp \
src/widgets/helper/ResizingTextEdit.cpp \
src/widgets/helper/RippleEffectButton.cpp \
src/widgets/helper/RippleEffectLabel.cpp \
src/widgets/helper/ScrollbarHighlight.cpp \
src/widgets/helper/SearchPopup.cpp \
src/widgets/helper/SettingsDialogTab.cpp \
@ -231,17 +230,35 @@ SOURCES += \
src/util/InitUpdateButton.cpp \
src/widgets/dialogs/UpdateDialog.cpp \
src/widgets/settingspages/IgnoresPage.cpp \
src/providers/twitch/PubsubClient.cpp
src/providers/twitch/PubsubClient.cpp \
src/providers/twitch/TwitchApi.cpp \
src/messages/Emote.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 \
src/util/FormatTime.cpp \
src/util/FunctionEventFilter.cpp \
src/widgets/helper/EffectLabel.cpp \
src/widgets/helper/Button.cpp \
src/messages/MessageContainer.cpp \
src/debug/Benchmark.cpp
HEADERS += \
src/Application.hpp \
src/common/Channel.hpp \
src/common/Common.hpp \
src/common/CompletionModel.hpp \
src/common/Emotemap.hpp \
src/common/FlagsEnum.hpp \
src/common/LockedObject.hpp \
src/common/MutexValue.hpp \
src/common/Atomic.hpp \
src/common/NetworkCommon.hpp \
src/common/NetworkData.hpp \
src/common/NetworkManager.hpp \
@ -253,9 +270,8 @@ HEADERS += \
src/common/NullablePtr.hpp \
src/common/Property.hpp \
src/common/ProviderId.hpp \
src/common/SerializeCustom.hpp \
src/util/RapidJsonSerializeQString.hpp \
src/common/SignalVectorModel.hpp \
src/common/UrlFetch.hpp \
src/common/Version.hpp \
src/controllers/accounts/Account.hpp \
src/controllers/accounts/AccountController.hpp \
@ -289,12 +305,9 @@ HEADERS += \
src/messages/MessageBuilder.hpp \
src/messages/MessageColor.hpp \
src/messages/MessageElement.hpp \
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 \
@ -358,8 +371,6 @@ HEADERS += \
src/widgets/helper/NotebookButton.hpp \
src/widgets/helper/NotebookTab.hpp \
src/widgets/helper/ResizingTextEdit.hpp \
src/widgets/helper/RippleEffectButton.hpp \
src/widgets/helper/RippleEffectLabel.hpp \
src/widgets/helper/ScrollbarHighlight.hpp \
src/widgets/helper/SearchPopup.hpp \
src/widgets/helper/SettingsDialogTab.hpp \
@ -402,7 +413,6 @@ HEADERS += \
src/singletons/Updates.hpp \
src/singletons/NativeMessaging.hpp \
src/singletons/Theme.hpp \
src/common/SimpleSignalVector.hpp \
src/common/SignalVector.hpp \
src/widgets/dialogs/LogsPopup.hpp \
src/common/Singleton.hpp \
@ -412,10 +422,34 @@ HEADERS += \
src/util/InitUpdateButton.hpp \
src/widgets/dialogs/UpdateDialog.hpp \
src/widgets/settingspages/IgnoresPage.hpp \
src/providers/twitch/PubsubClient.hpp
src/providers/twitch/PubsubClient.hpp \
src/providers/twitch/TwitchApi.hpp \
src/messages/Emote.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 \
src/util/FormatTime.hpp \
src/util/FunctionEventFilter.hpp \
src/widgets/helper/EffectLabel.hpp \
src/util/LayoutHelper.hpp \
src/widgets/helper/Button.hpp \
src/messages/MessageContainer.hpp
RESOURCES += \
resources/resources.qrc \
resources/resources_autogenerated.qrc
DISTFILES +=
@ -450,7 +484,6 @@ win32-msvc* {
QMAKE_CXXFLAGS_WARN_ON += -Wno-deprecated-declarations
QMAKE_CXXFLAGS_WARN_ON += -Wno-sign-compare
QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-variable
QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-private-field
# Disabling strict-aliasing warnings for now, although we probably want to re-enable this in the future
QMAKE_CXXFLAGS_WARN_ON += -Wno-strict-aliasing
@ -459,6 +492,7 @@ win32-msvc* {
equals(QMAKE_CXX, "clang++") {
QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-local-typedef
QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-private-field
} else {
QMAKE_CXXFLAGS_WARN_ON += -Wno-class-memaccess
}

@ -1 +1 @@
Subproject commit 29accdf9dea05947d687112594ad06bf6001ee0a
Subproject commit 7f0db95f245fb726e756ecde15a800c0928b054b

@ -1 +1 @@
Subproject commit 3f6645c615ff7bf412c05fe322e589cbdd34ff9b
Subproject commit e03c868ec922027a0e672b64388808beb1297816

View file

@ -0,0 +1,38 @@
resources_header = \
'''<RCC>
<qresource prefix="/">'''
resources_footer = \
''' </qresource>
</RCC>'''
header_header = \
'''#include <QPixmap>
#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'''

View file

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

Before

Width:  |  Height:  |  Size: 847 B

After

Width:  |  Height:  |  Size: 847 B

View file

Before

Width:  |  Height:  |  Size: 847 B

After

Width:  |  Height:  |  Size: 847 B

View file

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 307 B

View file

Before

Width:  |  Height:  |  Size: 328 B

After

Width:  |  Height:  |  Size: 328 B

View file

Before

Width:  |  Height:  |  Size: 376 B

After

Width:  |  Height:  |  Size: 376 B

View file

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 349 B

View file

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 344 B

View file

Before

Width:  |  Height:  |  Size: 332 B

After

Width:  |  Height:  |  Size: 332 B

BIN
resources/error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

61
resources/generate_resources.py Executable file
View file

@ -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" <file>{str(file)}</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)

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Cal Henderson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

BIN
resources/pajaDank.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -1,81 +1,5 @@
<RCC>
<qresource prefix="/">
<file>images/AppearanceEditorPart_16x.png</file>
<file>images/BrowserLink_16x.png</file>
<file>images/cheer1.png</file>
<file>images/cheer100.png</file>
<file>images/cheer1000.png</file>
<file>images/cheer10000.png</file>
<file>images/cheer100000.png</file>
<file>images/cheer5000.png</file>
<file>images/verified.png</file>
<file>images/CopyLongTextToClipboard_16x.png</file>
<file>images/CustomActionEditor_16x.png</file>
<file>images/Emoji_Color_1F60A_19.png</file>
<file>images/Filter_16x.png</file>
<file>images/format_Bold_16xLG.png</file>
<file>images/Message_16xLG.png</file>
<file>images/settings.png</file>
<file>images/tool_moreCollapser_off16.png</file>
<file>images/twitchprime_bg.png</file>
<file>qss/settings.qss</file>
<file>images/admin_bg.png</file>
<file>images/broadcaster_bg.png</file>
<file>images/globalmod_bg.png</file>
<file>images/moderator_bg.png</file>
<file>images/staff_bg.png</file>
<file>images/turbo_bg.png</file>
<file>emojidata.txt</file>
<file>images/button_ban.png</file>
<file>images/button_timeout.png</file>
<file>images/StatusAnnotations_Blocked_16xLG_color.png</file>
<file>images/UserProfile_22x.png</file>
<file>images/VSO_Link_blue_16x.png</file>
<file>sounds/ping2.wav</file>
<file>images/subscriber.png</file>
<file>images/collapse.png</file>
<file>images/emote.svg</file>
<file>images/notifications.svg</file>
<file>images/behave.svg</file>
<file>images/theme.svg</file>
<file>images/accounts.svg</file>
<file>images/chatterino2.icns</file>
<file>images/icon.png</file>
<file>images/commands.svg</file>
<file>images/aboutlogo.png</file>
<file>images/about.svg</file>
<file>images/moderatormode_disabled.png</file>
<file>images/moderatormode_enabled.png</file>
<file>images/split/splitdown.png</file>
<file>images/split/splitleft.png</file>
<file>images/split/splitright.png</file>
<file>images/split/splitup.png</file>
<file>images/split/splitmove.png</file>
<file>licenses/boost_boost.txt</file>
<file>licenses/fmt_bsd2.txt</file>
<file>licenses/libcommuni_BSD3.txt</file>
<file>licenses/openssl.txt</file>
<file>licenses/pajlada_settings.txt</file>
<file>licenses/pajlada_signals.txt</file>
<file>licenses/qt_lgpl-3.0.txt</file>
<file>licenses/rapidjson.txt</file>
<file>licenses/websocketpp.txt</file>
<file>emoji.json</file>
<file>images/buttons/ban.png</file>
<file>images/buttons/mod.png</file>
<file>images/buttons/unban.png</file>
<file>images/buttons/unmod.png</file>
<file>images/emote_dark.svg</file>
<file>tlds.txt</file>
<file>images/menu_black.png</file>
<file>images/menu_white.png</file>
<file>contributors.txt</file>
<file>avatars/fourtf.png</file>
<file>avatars/pajlada.png</file>
<file>images/download_update.png</file>
<file>images/download_update_error.png</file>
</qresource>
<qresource prefix="/qt/etc">
<file>qt.conf</file>
</qresource>
<qresource prefix="/qt/etc">
<file>qt.conf</file>
</qresource>
</RCC>

View file

@ -0,0 +1,64 @@
<RCC>
<qresource prefix="/"> <file>pajaDank.png</file>
<file>icon.png</file>
<file>emojidata.txt</file>
<file>contributors.txt</file>
<file>error.png</file>
<file>emoji.json</file>
<file>icon.ico</file>
<file>tlds.txt</file>
<file>chatterino2.icns</file>
<file>qss/settings.qss</file>
<file>__pycache__/_generate_resources.cpython-36.pyc</file>
<file>licenses/fmt_bsd2.txt</file>
<file>licenses/openssl.txt</file>
<file>licenses/pajlada_settings.txt</file>
<file>licenses/qt_lgpl-3.0.txt</file>
<file>licenses/pajlada_signals.txt</file>
<file>licenses/rapidjson.txt</file>
<file>licenses/websocketpp.txt</file>
<file>licenses/boost_boost.txt</file>
<file>licenses/libcommuni_BSD3.txt</file>
<file>settings/aboutlogo.png</file>
<file>settings/behave.svg</file>
<file>settings/accounts.svg</file>
<file>settings/about.svg</file>
<file>settings/notifications.svg</file>
<file>settings/commands.svg</file>
<file>settings/theme.svg</file>
<file>split/up.png</file>
<file>split/left.png</file>
<file>split/move.png</file>
<file>split/right.png</file>
<file>split/down.png</file>
<file>buttons/unban.png</file>
<file>buttons/menuDark.png</file>
<file>buttons/mod.png</file>
<file>buttons/emote.svg</file>
<file>buttons/modModeEnabled2.png</file>
<file>buttons/ban.png</file>
<file>buttons/unmod.png</file>
<file>buttons/emoteDark.svg</file>
<file>buttons/updateError.png</file>
<file>buttons/modModeDisabled.png</file>
<file>buttons/modModeDisabled2.png</file>
<file>buttons/modModeEnabled.png</file>
<file>buttons/menuLight.png</file>
<file>buttons/update.png</file>
<file>buttons/timeout.png</file>
<file>buttons/banRed.png</file>
<file>sounds/ping2.wav</file>
<file>twitch/prime.png</file>
<file>twitch/verified.png</file>
<file>twitch/admin.png</file>
<file>twitch/subscriber.png</file>
<file>twitch/turbo.png</file>
<file>twitch/moderator.png</file>
<file>twitch/globalmod.png</file>
<file>twitch/cheer1.png</file>
<file>twitch/broadcaster.png</file>
<file>twitch/staff.png</file>
<file>avatars/fourtf.png</file>
<file>avatars/pajlada.png</file>
</qresource>
</RCC>

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View file

Before

Width:  |  Height:  |  Size: 805 B

After

Width:  |  Height:  |  Size: 805 B

View file

Before

Width:  |  Height:  |  Size: 820 B

After

Width:  |  Height:  |  Size: 820 B

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M50,100c27.7,0,50-22.3,50-50S77.7,0,50,0S0,22.3,0,50S22.3,100,50,100z M33.3,46.2c4.2,0,8.3-3.3,8.3-8.3
s-4.2-8.3-8.3-8.3S25,32.9,25,37.9S29.2,46.2,33.3,46.2z M50,91.7C27,91.7,8.3,73,8.3,50S27,8.3,50,8.3S91.7,27,91.7,50
S73,91.7,50,91.7z M23.3,63.1c16.2,10.3,37.1,10.4,53.2,0.1l-4.3-7c-13.7,8.5-31,8.4-44.5-0.1L23.3,63.1z M67.1,46.2
c4.2,0,8.3-3.3,8.3-8.3s-4.2-8.3-8.3-8.3s-8.3,3.3-8.3,8.3S62.9,46.2,67.1,46.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 847 B

View file

Before

Width:  |  Height:  |  Size: 955 B

After

Width:  |  Height:  |  Size: 955 B

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 361 B

After

Width:  |  Height:  |  Size: 361 B

View file

Before

Width:  |  Height:  |  Size: 437 B

After

Width:  |  Height:  |  Size: 437 B

View file

Before

Width:  |  Height:  |  Size: 772 B

After

Width:  |  Height:  |  Size: 772 B

View file

Before

Width:  |  Height:  |  Size: 494 B

After

Width:  |  Height:  |  Size: 494 B

View file

Before

Width:  |  Height:  |  Size: 361 B

After

Width:  |  Height:  |  Size: 361 B

View file

Before

Width:  |  Height:  |  Size: 439 B

After

Width:  |  Height:  |  Size: 439 B

View file

Before

Width:  |  Height:  |  Size: 191 B

After

Width:  |  Height:  |  Size: 191 B

View file

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 255 B

View file

Before

Width:  |  Height:  |  Size: 397 B

After

Width:  |  Height:  |  Size: 397 B

View file

Before

Width:  |  Height:  |  Size: 376 B

After

Width:  |  Height:  |  Size: 376 B

View file

Before

Width:  |  Height:  |  Size: 116 B

After

Width:  |  Height:  |  Size: 116 B

View file

Before

Width:  |  Height:  |  Size: 269 B

After

Width:  |  Height:  |  Size: 269 B

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

Before

Width:  |  Height:  |  Size: 257 B

After

Width:  |  Height:  |  Size: 257 B

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -6,9 +6,11 @@
#include "controllers/ignores/IgnoreController.hpp"
#include "controllers/moderationactions/ModerationActions.hpp"
#include "controllers/taggedusers/TaggedUsersController.hpp"
#include "messages/MessageBuilder.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 +26,77 @@
namespace chatterino {
static std::atomic<bool> isAppConstructed{false};
static std::atomic<bool> 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
// 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<Resources2>())
, themes(&this->emplace<Theme>())
, fonts(&this->emplace<Fonts>())
, emotes(&this->emplace<Emotes>())
, windows(&this->emplace<WindowManager>())
, accounts(&this->emplace<AccountController>())
, commands(&this->emplace<CommandController>())
, highlights(&this->emplace<HighlightController>())
, ignores(&this->emplace<IgnoreController>())
, taggedUsers(&this->emplace<TaggedUsersController>())
, moderationActions(&this->emplace<ModerationActions>())
, twitch2(&this->emplace<TwitchServer>())
, logging(&this->emplace<Logging>())
{
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,49 +108,61 @@ 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"); //
log("WHISPER SENT LOL"); //
});
this->twitch.pubsub->signals_.whisper.received.connect([](const auto &msg) {
Log("WHISPER RECEIVED LOL"); //
log("WHISPER RECEIVED LOL"); //
});
this->twitch.pubsub->signals_.moderation.chatCleared.connect([this](const auto &action) {
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) {
return;
}
this->twitch.pubsub->signals_.moderation.chatCleared.connect(
[this](const auto &action) {
auto chan =
this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) {
return;
}
QString text = QString("%1 cleared the chat").arg(action.source.name);
QString text =
QString("%1 cleared the chat").arg(action.source.name);
auto msg = Message::createSystemMessage(text);
postToThread([chan, msg] { chan->addMessage(msg); });
});
auto msg = makeSystemMessage(text);
postToThread([chan, msg] { chan->addMessage(msg); });
});
this->twitch.pubsub->signals_.moderation.modeChanged.connect([this](const auto &action) {
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) {
return;
}
this->twitch.pubsub->signals_.moderation.modeChanged.connect(
[this](const auto &action) {
auto chan =
this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) {
return;
}
QString text = QString("%1 turned %2 %3 mode") //
.arg(action.source.name)
.arg(action.state == ModeChangedAction::State::On ? "on" : "off")
.arg(action.getModeName());
QString text =
QString("%1 turned %2 %3 mode") //
.arg(action.source.name)
.arg(action.state == ModeChangedAction::State::On ? "on"
: "off")
.arg(action.getModeName());
if (action.duration > 0) {
text.append(" (" + QString::number(action.duration) + " seconds)");
}
if (action.duration > 0) {
text.append(" (" + QString::number(action.duration) +
" seconds)");
}
auto msg = Message::createSystemMessage(text);
postToThread([chan, msg] { chan->addMessage(msg); });
});
auto msg = makeSystemMessage(text);
postToThread([chan, msg] { chan->addMessage(msg); });
});
this->twitch.pubsub->signals_.moderation.moderationStateChanged.connect(
[this](const auto &action) {
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
auto chan =
this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) {
return;
}
@ -148,48 +170,57 @@ void Application::initialize()
QString text;
if (action.modded) {
text = QString("%1 modded %2").arg(action.source.name, action.target.name);
text = QString("%1 modded %2")
.arg(action.source.name, action.target.name);
} else {
text = QString("%1 unmodded %2").arg(action.source.name, action.target.name);
text = QString("%1 unmodded %2")
.arg(action.source.name, action.target.name);
}
auto msg = Message::createSystemMessage(text);
auto msg = makeSystemMessage(text);
postToThread([chan, msg] { chan->addMessage(msg); });
});
this->twitch.pubsub->signals_.moderation.userBanned.connect([&](const auto &action) {
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
this->twitch.pubsub->signals_.moderation.userBanned.connect(
[&](const auto &action) {
auto chan =
this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) {
return;
}
if (chan->isEmpty()) {
return;
}
auto msg = Message::createTimeoutMessage(action);
msg->flags |= Message::PubSub;
MessageBuilder msg(action);
msg->flags.set(MessageFlag::PubSub);
postToThread([chan, msg] { chan->addOrReplaceTimeout(msg); });
});
postToThread([chan, msg = msg.release()] {
chan->addOrReplaceTimeout(msg);
});
});
this->twitch.pubsub->signals_.moderation.userUnbanned.connect([&](const auto &action) {
auto chan = this->twitch.server->getChannelOrEmptyByID(action.roomID);
this->twitch.pubsub->signals_.moderation.userUnbanned.connect(
[&](const auto &action) {
auto chan =
this->twitch.server->getChannelOrEmptyByID(action.roomID);
if (chan->isEmpty()) {
return;
}
if (chan->isEmpty()) {
return;
}
auto msg = Message::createUntimeoutMessage(action);
auto msg = MessageBuilder(action).release();
postToThread([chan, msg] { chan->addMessage(msg); });
});
postToThread([chan, msg] { chan->addMessage(msg); });
});
this->twitch.pubsub->start();
auto RequestModerationActions = [=]() {
this->twitch.server->pubsub->unlistenAllModerationActions();
// TODO(pajlada): Unlisten to all authed topics instead of only moderation topics
// this->twitch.pubsub->UnlistenAllAuthedTopics();
// TODO(pajlada): Unlisten to all authed topics instead of only
// moderation topics this->twitch.pubsub->UnlistenAllAuthedTopics();
this->twitch.server->pubsub->listenToWhispers(this->accounts->twitch.getCurrent()); //
this->twitch.server->pubsub->listenToWhispers(
this->accounts->twitch.getCurrent()); //
};
this->accounts->twitch.currentUserChanged.connect(RequestModerationActions);
@ -197,39 +228,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

View file

@ -1,13 +1,13 @@
#pragma once
#include "common/Singleton.hpp"
#include "singletons/Resources.hpp"
#include <QApplication>
#include <memory>
namespace chatterino {
class Singleton;
class TwitchServer;
class PubSub;
@ -24,68 +24,69 @@ 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<std::unique_ptr<Singleton>> 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{};
Paths *const paths{};
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{};
Fonts *const fonts{};
Emotes *const emotes{};
WindowManager *const windows{};
AccountController *const accounts{};
CommandController *const commands{};
HighlightController *const highlights{};
IgnoreController *const ignores{};
TaggedUsersController *const taggedUsers{};
ModerationActions *const moderationActions{};
TwitchServer *const twitch2{};
/*[[deprecated]]*/ Logging *const logging{};
/// Provider-specific
struct {
[[deprecated("use twitch2 instead")]] TwitchServer *server = nullptr;
[[deprecated("use twitch2->pubsub instead")]] PubSub *pubsub = nullptr;
/*[[deprecated("use twitch2 instead")]]*/ TwitchServer *server{};
/*[[deprecated("use twitch2->pubsub instead")]]*/ PubSub *pubsub{};
} 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<Singleton *> singletons_;
template <typename T,
typename = std::enable_if_t<std::is_base_of<Singleton, T>::value>>
T &emplace()
{
auto t = new T;
this->singletons_.push_back(std::unique_ptr<T>(t));
return *t;
}
};
Application *getApp();
bool appInitialized();
} // namespace chatterino

87
src/BrowserExtension.cpp Normal file
View file

@ -0,0 +1,87 @@
#include "BrowserExtension.hpp"
#include "singletons/NativeMessaging.hpp"
#include <QStringList>
#include <QTimer>
#include <fstream>
#include <iostream>
#include <memory>
#ifdef Q_OS_WIN
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#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;
auto size = *reinterpret_cast<uint32_t *>(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<uint32_t>(size_c[2]) << 8 |
static_cast<uint32_t>(size_c[1]) << 16 | static_cast<uint32_t>(size_c[0]) << 24;
} else {
size = size_c[0] | static_cast<uint32_t>(size_c[1]) << 8 |
static_cast<uint32_t>(size_c[2]) << 16 | static_cast<uint32_t>(size_c[3]) << 24;
}
#endif
std::unique_ptr<char[]> buffer(new char[size + 1]);
std::cin.read(buffer.get(), size);
*(buffer.get() + size) = '\0';
client.sendMessage(
QByteArray::fromRawData(buffer.get(), static_cast<int32_t>(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<bool> 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

10
src/BrowserExtension.hpp Normal file
View file

@ -0,0 +1,10 @@
#pragma once
class QStringList;
namespace chatterino {
bool shouldRunBrowserExtensionHost(const QStringList &args);
void runBrowserExtensionHost();
} // namespace chatterino

136
src/RunGui.cpp Normal file
View file

@ -0,0 +1,136 @@
#include "RunGui.hpp"
#include <QApplication>
#include <QFile>
#include <QPalette>
#include <QStyleFactory>
#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 <QBreakpadHandler.h>
#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
auto dark = qApp->palette();
dark.setColor(QPalette::Window, QColor(22, 22, 22));
dark.setColor(QPalette::WindowText, Qt::white);
dark.setColor(QPalette::Text, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::WindowText,
QColor(127, 127, 127));
dark.setColor(QPalette::Base, QColor("#333"));
dark.setColor(QPalette::AlternateBase, QColor("#444"));
dark.setColor(QPalette::ToolTipBase, Qt::white);
dark.setColor(QPalette::ToolTipText, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127));
dark.setColor(QPalette::Dark, QColor(35, 35, 35));
dark.setColor(QPalette::Shadow, QColor(20, 20, 20));
dark.setColor(QPalette::Button, QColor(70, 70, 70));
dark.setColor(QPalette::ButtonText, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::ButtonText,
QColor(127, 127, 127));
dark.setColor(QPalette::BrightText, Qt::red);
dark.setColor(QPalette::Link, QColor(42, 130, 218));
dark.setColor(QPalette::Highlight, QColor(42, 130, 218));
dark.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80));
dark.setColor(QPalette::HighlightedText, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::HighlightedText,
QColor(127, 127, 127));
qApp->setPalette(dark);
}
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)
{
initQt();
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

10
src/RunGui.hpp Normal file
View file

@ -0,0 +1,10 @@
#pragma once
class QApplication;
namespace chatterino {
class Paths;
class Settings;
void runGui(QApplication &a, Paths &paths, Settings &settings);
} // namespace chatterino

View file

@ -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

View file

@ -0,0 +1,58 @@
#include <QPixmap>
#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

34
src/common/Aliases.hpp Normal file
View file

@ -0,0 +1,34 @@
#pragma once
#include <QHash>
#include <QString>
#include <functional>
#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<chatterino::name> { \
size_t operator()(const chatterino::name &s) const \
{ \
return qHash(s.string); \
} \
}; \
} /* namespace std */
QStringAlias(UserName);
QStringAlias(UserId);
QStringAlias(Url);
QStringAlias(Tooltip);

View file

@ -6,14 +6,14 @@
namespace chatterino {
template <typename T>
class MutexValue : boost::noncopyable
class Atomic : boost::noncopyable
{
public:
MutexValue()
Atomic()
{
}
MutexValue(T &&val)
Atomic(T &&val)
: value_(val)
{
}
@ -32,6 +32,13 @@ public:
this->value_ = val;
}
void set(T &&val)
{
std::lock_guard<std::mutex> guard(this->mutex_);
this->value_ = std::move(val);
}
private:
mutable std::mutex mutex_;
T value_;

View file

@ -3,6 +3,7 @@
#include "Application.hpp"
#include "debug/Log.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Logging.hpp"
#include "singletons/WindowManager.hpp"
@ -17,14 +18,18 @@
namespace chatterino {
Channel::Channel(const QString &_name, Type type)
: name(_name)
, completionModel(this->name)
//
// Channel
//
Channel::Channel(const QString &name, Type type)
: completionModel(name)
, name_(name)
, type_(type)
{
QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout, [this]() {
this->completionModel.clearExpiredStrings(); //
});
QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout,
[this]() {
this->completionModel.clearExpiredStrings(); //
});
this->clearCompletionModelTimer_.start(60 * 1000);
}
@ -38,6 +43,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 +55,7 @@ bool Channel::isTwitchChannel() const
bool Channel::isEmpty() const
{
return this->name.isEmpty();
return this->name_.isEmpty();
}
LimitedQueueSnapshot<MessagePtr> Channel::getMessageSnapshot()
@ -60,13 +70,13 @@ void Channel::addMessage(MessagePtr message)
const QString &username = message->loginName;
if (!username.isEmpty()) {
// TODO: Add recent chatters display name. This should maybe be a setting
// TODO: Add recent chatters display name
this->addRecentChatter(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)) {
@ -90,37 +100,43 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
for (int i = snapshotLength - 1; i >= end; --i) {
auto &s = snapshot[i];
qDebug() << s->parseTime << minimumTime;
if (s->parseTime < minimumTime) {
break;
}
if (s->flags.HasFlag(Message::Untimeout) && s->timeoutUser == message->timeoutUser) {
if (s->flags.has(MessageFlag::Untimeout) &&
s->timeoutUser == message->timeoutUser) {
break;
}
if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == message->timeoutUser) {
if (message->flags.HasFlag(Message::PubSub) && !s->flags.HasFlag(Message::PubSub)) {
if (s->flags.has(MessageFlag::Timeout) &&
s->timeoutUser == message->timeoutUser) //
{
if (message->flags.has(MessageFlag::PubSub) &&
!s->flags.has(MessageFlag::PubSub)) //
{
this->replaceMessage(s, message);
addMessage = false;
break;
}
if (!message->flags.HasFlag(Message::PubSub) && s->flags.HasFlag(Message::PubSub)) {
if (!message->flags.has(MessageFlag::PubSub) &&
s->flags.has(MessageFlag::PubSub)) //
{
addMessage = false;
break;
}
int count = s->count + 1;
MessagePtr replacement(Message::createSystemMessage(
message->searchText + QString(" (") + QString::number(count) + " times)"));
MessageBuilder replacement(systemMessage,
message->searchText + QString(" (") +
QString::number(count) + " times)");
replacement->timeoutUser = message->timeoutUser;
replacement->count = count;
replacement->flags = message->flags;
this->replaceMessage(s, replacement);
this->replaceMessage(s, replacement.release());
return;
}
@ -129,9 +145,10 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
// disable the messages from the user
for (int i = 0; i < snapshotLength; i++) {
auto &s = snapshot[i];
if ((s->flags & (Message::Timeout | Message::Untimeout)) == 0 &&
if (s->flags.hasNone({MessageFlag::Timeout, MessageFlag::Untimeout}) &&
s->loginName == message->timeoutUser) {
s->flags.EnableFlag(Message::Disabled);
// FOURTF: disabled for now
// s->flags.EnableFlag(MessageFlag::Disabled);
}
}
@ -149,17 +166,19 @@ void Channel::disableAllMessages()
int snapshotLength = snapshot.getLength();
for (int i = 0; i < snapshotLength; i++) {
auto &s = snapshot[i];
if (s->flags & Message::System || s->flags & Message::Timeout) {
if (s->flags.hasAny({MessageFlag::System, MessageFlag::Timeout})) {
continue;
}
s->flags.EnableFlag(Message::Disabled);
// FOURTF: disabled for now
// s->flags.EnableFlag(MessageFlag::Disabled);
}
}
void Channel::addMessagesAtStart(std::vector<MessagePtr> &_messages)
{
std::vector<MessagePtr> addedMessages = this->messages_.pushFront(_messages);
std::vector<MessagePtr> addedMessages =
this->messages_.pushFront(_messages);
if (addedMessages.size() != 0) {
this->messagesAddedAtStart.invoke(addedMessages);
@ -175,9 +194,8 @@ void Channel::replaceMessage(MessagePtr message, MessagePtr replacement)
}
}
void Channel::addRecentChatter(const std::shared_ptr<Message> &message)
void Channel::addRecentChatter(const MessagePtr &message)
{
// Do nothing by default
}
bool Channel::canSendMessage() const
@ -215,4 +233,45 @@ void Channel::onConnected()
{
}
//
// Indirect channel
//
IndirectChannel::Data::Data(ChannelPtr _channel, Channel::Type _type)
: channel(_channel)
, type(_type)
{
}
IndirectChannel::IndirectChannel(ChannelPtr channel, Channel::Type type)
: data_(std::make_unique<Data>(channel, type))
{
}
ChannelPtr IndirectChannel::get()
{
return data_->channel;
}
void IndirectChannel::reset(ChannelPtr channel)
{
assert(this->data_->type != Channel::Type::Direct);
this->data_->channel = channel;
this->data_->changed.invoke();
}
pajlada::Signals::NoArgSignal &IndirectChannel::getChannelChanged()
{
return this->data_->changed;
}
Channel::Type IndirectChannel::getType()
{
if (this->data_->type == Channel::Type::Direct) {
return this->get()->getType();
} else {
return this->data_->type;
}
}
} // namespace chatterino

View file

@ -4,7 +4,6 @@
#include "messages/Image.hpp"
#include "messages/LimitedQueue.hpp"
#include "messages/Message.hpp"
#include "util/ConcurrentMap.hpp"
#include <QString>
#include <QTimer>
@ -32,7 +31,8 @@ public:
explicit Channel(const QString &name, Type type);
virtual ~Channel();
pajlada::Signals::Signal<const QString &, const QString &, bool &> sendMessageSignal;
pajlada::Signals::Signal<const QString &, const QString &, bool &>
sendMessageSignal;
pajlada::Signals::Signal<MessagePtr &> messageRemovedFromStart;
pajlada::Signals::Signal<MessagePtr &> messageAppended;
@ -41,6 +41,7 @@ public:
pajlada::Signals::NoArgSignal destroyed;
Type getType() const;
const QString &getName() const;
bool isTwitchChannel() const;
virtual bool isEmpty() const;
LimitedQueueSnapshot<MessagePtr> getMessageSnapshot();
@ -50,9 +51,8 @@ public:
void addOrReplaceTimeout(MessagePtr message);
void disableAllMessages();
void replaceMessage(MessagePtr message, MessagePtr replacement);
virtual void addRecentChatter(const std::shared_ptr<Message> &message);
virtual void addRecentChatter(const MessagePtr &message);
QString name;
QStringList modList;
virtual bool canSendMessage() const;
@ -69,6 +69,7 @@ protected:
virtual void onConnected();
private:
const QString name_;
LimitedQueue<MessagePtr> messages_;
Type type_;
QTimer clearCompletionModelTimer_;
@ -83,46 +84,17 @@ class IndirectChannel
Channel::Type type;
pajlada::Signals::NoArgSignal changed;
Data() = delete;
Data(ChannelPtr _channel, Channel::Type _type)
: channel(_channel)
, type(_type)
{
}
Data(ChannelPtr channel, Channel::Type type);
};
public:
IndirectChannel(ChannelPtr channel, Channel::Type type = Channel::Type::Direct)
: data_(new Data(channel, type))
{
}
IndirectChannel(ChannelPtr channel,
Channel::Type type = Channel::Type::Direct);
ChannelPtr get()
{
return data_->channel;
}
void update(ChannelPtr ptr)
{
assert(this->data_->type != Channel::Type::Direct);
this->data_->channel = ptr;
this->data_->changed.invoke();
}
pajlada::Signals::NoArgSignal &getChannelChanged()
{
return this->data_->changed;
}
Channel::Type getType()
{
if (this->data_->type == Channel::Type::Direct) {
return this->get()->getType();
} else {
return this->data_->type;
}
}
ChannelPtr get();
void reset(ChannelPtr channel);
pajlada::Signals::NoArgSignal &getChannelChanged();
Channel::Type getType();
private:
std::shared_ptr<Data> data_;

View file

@ -41,7 +41,7 @@ public:
using pajlada::Settings::Setting<Type>::operator==;
using pajlada::Settings::Setting<Type>::operator!=;
using pajlada::Settings::Setting<Type>::operator const Type;
using pajlada::Settings::Setting<Type>::operator Type;
};
using BoolSetting = ChatterinoSetting<bool>;

View file

@ -1,9 +1,13 @@
#pragma once
#include "common/Aliases.hpp"
#include "common/Outcome.hpp"
#include "common/ProviderId.hpp"
#include "debug/Log.hpp"
#include <QString>
#include <QWidget>
#include <boost/optional.hpp>
#include <boost/preprocessor.hpp>
#include <string>
@ -21,20 +25,18 @@ inline QString qS(const std::string &string)
return QString::fromStdString(string);
}
const Qt::KeyboardModifiers showSplitOverlayModifiers = Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showAddSplitRegions = Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showSplitOverlayModifiers =
Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showAddSplitRegions =
Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
static const char *ANONYMOUS_USERNAME_LABEL ATTR_UNUSED = " - anonymous - ";
#define return_if(x) \
if ((x)) { \
return; \
}
#define return_unless(x) \
if (!(x)) { \
return; \
}
template <typename T>
std::weak_ptr<T> weakOf(T *element)
{
return element->shared_from_this();
}
} // namespace chatterino

View file

@ -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 <QtAlgorithms>
@ -102,46 +104,57 @@ int CompletionModel::rowCount(const QModelIndex &) const
void CompletionModel::refresh()
{
Log("[CompletionModel:{}] Refreshing...]", this->channelName_);
log("[CompletionModel:{}] Refreshing...]", this->channelName_);
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) {
// XXX: No way to discern between a twitch global emote and sub emote right now
this->addString(emoteName, TaggedString::Type::TwitchGlobalEmote);
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(emote.string,
TaggedString::Type::TwitchGlobalEmote);
}
}
// Global: BTTV Global Emotes
std::vector<QString> &bttvGlobalEmoteCodes = app->emotes->bttv.globalEmoteCodes;
for (const auto &m : bttvGlobalEmoteCodes) {
this->addString(m, TaggedString::Type::BTTVGlobalEmote);
// // Global: BTTV Global Emotes
// std::vector<QString> &bttvGlobalEmoteCodes =
// app->emotes->bttv.globalEmoteNames_; for (const auto &m :
// bttvGlobalEmoteCodes) {
// this->addString(m, TaggedString::Type::BTTVGlobalEmote);
// }
// // Global: FFZ Global Emotes
// std::vector<QString> &ffzGlobalEmoteCodes =
// app->emotes->ffz.globalEmoteCodes; for (const auto &m :
// ffzGlobalEmoteCodes) {
// this->addString(m, TaggedString::Type::FFZGlobalEmote);
// }
// Channel emotes
if (auto channel = dynamic_cast<TwitchChannel *>(
getApp()
->twitch2->getChannelOrEmptyByID(this->channelName_)
.get())) {
auto bttv = channel->bttvEmotes();
// auto it = bttv->begin();
// for (const auto &emote : *bttv) {
// }
// std::vector<QString> &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->ffzEmotes()) {
this->addString(emote.second->name.string,
TaggedString::Type::FFZChannelEmote);
}
}
// Global: FFZ Global Emotes
std::vector<QString> &ffzGlobalEmoteCodes = app->emotes->ffz.globalEmoteCodes;
for (const auto &m : ffzGlobalEmoteCodes) {
this->addString(m, TaggedString::Type::FFZGlobalEmote);
}
// Channel-specific: BTTV Channel Emotes
std::vector<QString> &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<QString> &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);
@ -158,7 +171,8 @@ void CompletionModel::refresh()
// Channel-specific: Usernames
// fourtf: only works with twitch chat
// auto c = ChannelManager::getInstance().getTwitchChannel(this->channelName);
// auto c =
// ChannelManager::getInstance().getTwitchChannel(this->channelName);
// auto usernames = c->getUsernamesForCompletions();
// for (const auto &name : usernames) {
// assert(!name.displayName.isEmpty());
@ -185,9 +199,11 @@ void CompletionModel::addUser(const QString &username)
auto add = [this](const QString &str) {
auto ts = this->createUser(str + " ");
// Always add a space at the end of completions
std::pair<std::set<TaggedString>::iterator, bool> p = this->emotes_.insert(ts);
std::pair<std::set<TaggedString>::iterator, bool> p =
this->emotes_.insert(ts);
if (!p.second) {
// No inseration was made, figure out if we need to replace the username.
// No inseration was made, figure out if we need to replace the
// username.
if (p.first->str > ts.str) {
// Replace lowercase version of name with mixed-case version

View file

@ -10,6 +10,8 @@
namespace chatterino {
class TwitchChannel;
class CompletionModel : public QAbstractListModel
{
struct TaggedString {

View file

@ -65,7 +65,8 @@ public:
this->data.insert(name, value);
}
void each(std::function<void(const TKey &name, const TValue &value)> func) const
void each(
std::function<void(const TKey &name, const TValue &value)> func) const
{
QMutexLocker lock(&this->mutex);

View file

@ -1,46 +0,0 @@
#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

View file

@ -1,27 +0,0 @@
#pragma once
#include "messages/Image.hpp"
#include "util/ConcurrentMap.hpp"
namespace chatterino {
struct EmoteData {
EmoteData() = default;
EmoteData(Image *image);
// 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;
Image *image1x = nullptr;
Image *image2x = nullptr;
Image *image3x = nullptr;
};
using EmoteMap = ConcurrentMap<QString, EmoteData>;
} // namespace chatterino

View file

@ -4,62 +4,78 @@
namespace chatterino {
// = std::enable_if<std::is_enum<T>::value>::type
template <typename T, typename Q = typename std::underlying_type<T>::type>
class FlagsEnum
{
public:
FlagsEnum()
: value(static_cast<T>(0))
: value_(static_cast<T>(0))
{
}
FlagsEnum(T _value)
: value(_value)
FlagsEnum(T value)
: value_(value)
{
}
inline T operator~() const
FlagsEnum(std::initializer_list<T> flags)
{
return (T) ~(Q)this->value;
}
inline T operator|(Q a) const
{
return (T)((Q)a | (Q)this->value);
}
inline T operator&(Q a) const
{
return (T)((Q)a & (Q)this->value);
}
inline T operator^(Q a) const
{
return (T)((Q)a ^ (Q)this->value);
}
inline T &operator|=(const Q &a)
{
return (T &)((Q &)this->value |= (Q)a);
}
inline T &operator&=(const Q &a)
{
return (T &)((Q &)this->value &= (Q)a);
}
inline T &operator^=(const Q &a)
{
return (T &)((Q &)this->value ^= (Q)a);
for (auto flag : flags) {
this->set(flag);
}
}
void EnableFlag(T flag)
bool operator==(const FlagsEnum<T> &other)
{
reinterpret_cast<Q &>(this->value) |= static_cast<Q>(flag);
return this->value_ == other.value_;
}
bool HasFlag(Q flag) const
bool operator!=(const FlagsEnum &other)
{
return (this->value & flag) == flag;
return this->value_ != other.value_;
}
T value;
void set(T flag)
{
reinterpret_cast<Q &>(this->value_) |= static_cast<Q>(flag);
}
void unset(T flag)
{
reinterpret_cast<Q &>(this->value_) &= ~static_cast<Q>(flag);
}
void set(T flag, bool value)
{
if (value)
this->set(flag);
else
this->unset(flag);
}
bool has(T flag) const
{
return static_cast<Q>(this->value_) & static_cast<Q>(flag);
}
bool hasAny(FlagsEnum flags) const
{
return static_cast<Q>(this->value_) & static_cast<Q>(flags.value_);
}
bool hasAll(FlagsEnum<T> flags) const
{
return (static_cast<Q>(this->value_) & static_cast<Q>(flags.value_)) &&
static_cast<Q>(flags->value);
}
bool hasNone(std::initializer_list<T> flags) const
{
return !this->hasAny(flags);
}
private:
T value_{};
};
} // namespace chatterino

View file

@ -5,58 +5,63 @@
#include <QString>
#include <QTextStream>
// ip 0.0.0.0 - 224.0.0.0
#define IP \
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" \
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" \
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))"
#define PORT "(?::\\d{2,5})"
#define WEB_CHAR1 "[_a-z\\x{00a1}-\\x{ffff}0-9]"
#define WEB_CHAR2 "[a-z\\x{00a1}-\\x{ffff}0-9]"
#define SPOTIFY_1 "(?:artist|album|track|user:[^:]+:playlist):[a-zA-Z0-9]+"
#define SPOTIFY_2 "user:[^:]+"
#define SPOTIFY_3 "search:(?:[-\\w$\\.+!*'(),]+|%[a-fA-F0-9]{2})+"
#define SPOTIFY_PARAMS "(?:" SPOTIFY_1 "|" SPOTIFY_2 "|" SPOTIFY_3 ")"
#define SPOTIFY_LINK "(?x-mi:(spotify:" SPOTIFY_PARAMS "))"
#define WEB_PROTOCOL "(?:(?:https?|ftps?)://)?"
#define WEB_USER "(?:\\S+(?::\\S*)?@)?"
#define WEB_HOST "(?:(?:" WEB_CHAR1 "-*)*" WEB_CHAR2 "+)"
#define WEB_DOMAIN "(?:\\.(?:" WEB_CHAR2 "-*)*" WEB_CHAR2 "+)*"
#define WEB_TLD "(?:" + tldData + ")"
#define WEB_RESOURCE_PATH "(?:[/?#]\\S*)"
#define WEB_LINK \
WEB_PROTOCOL WEB_USER "(?:" IP "|" WEB_HOST WEB_DOMAIN "\\." WEB_TLD PORT \
"?" WEB_RESOURCE_PATH "?)"
#define LINK "^(?:" SPOTIFY_LINK "|" WEB_LINK ")$"
namespace chatterino {
LinkParser::LinkParser(const QString &unparsedString)
{
static QRegularExpression linkRegex = [] {
static QRegularExpression newLineRegex("\r?\n");
QFile tldFile(":/tlds.txt");
tldFile.open(QFile::ReadOnly);
QFile file(":/tlds.txt");
file.open(QFile::ReadOnly);
QTextStream tlds(&file);
tlds.setCodec("UTF-8");
QTextStream t1(&tldFile);
t1.setCodec("UTF-8");
// tldData gets injected into the LINK macro
auto tldData = tlds.readAll().replace(newLineRegex, "|");
(void)tldData;
// Read the TLDs in and replace the newlines with pipes
QString tldData = t1.readAll().replace(newLineRegex, "|");
const QString urlRegExp =
"^"
// protocol identifier
"(?:(?:https?|ftps?)://)?"
// user:pass authentication
"(?:\\S+(?::\\S*)?@)?"
"(?:"
// IP address dotted notation octets
// excludes loopback network 0.0.0.0
// excludes reserved space >= 224.0.0.0
// excludes network & broacast addresses
// (first & last IP address of each class)
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])"
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}"
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))"
"|"
// host name
"(?:(?:[_a-z\\x{00a1}-\\x{ffff}0-9]-*)*[a-z\\x{00a1}-\\x{ffff}0-9]+)"
// domain name
"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}0-9]-*)*[a-z\\x{00a1}-\\x{ffff}0-9]+)*"
// TLD identifier
//"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}]{2,}))"
"(?:[\\.](?:" +
tldData +
"))"
"\\.?"
")"
// port number
"(?::\\d{2,5})?"
// resource path
"(?:[/?#]\\S*)?"
"$";
return QRegularExpression(urlRegExp, QRegularExpression::CaseInsensitiveOption);
return QRegularExpression(LINK,
QRegularExpression::CaseInsensitiveOption);
}();
this->match_ = linkRegex.match(unparsedString);
}
bool LinkParser::hasMatch() const
{
return this->match_.hasMatch();
}
QString LinkParser::getCaptured() const
{
return this->match_.captured();
}
} // namespace chatterino

View file

@ -10,15 +10,8 @@ class LinkParser
public:
explicit LinkParser(const QString &unparsedString);
bool hasMatch() const
{
return this->match_.hasMatch();
}
QString getCaptured() const
{
return this->match_.captured();
}
bool hasMatch() const;
QString getCaptured() const;
private:
QRegularExpressionMatch match_;

View file

@ -1,38 +0,0 @@
#pragma once
#include <mutex>
namespace chatterino {
template <typename Type>
class LockedObject
{
public:
LockedObject &operator=(const LockedObject<Type> &other)
{
this->mutex_.lock();
this->data = other.getValue();
this->mutex_.unlock();
return *this;
}
LockedObject &operator=(const Type &other)
{
this->mutex_.lock();
this->data = other;
this->mutex_.unlock();
return *this;
}
private:
Type value_;
std::mutex mutex_;
};
} // namespace chatterino

View file

@ -2,13 +2,15 @@
#include <functional>
#include "Common.hpp"
class QNetworkReply;
namespace chatterino {
class NetworkResult;
using NetworkSuccessCallback = std::function<bool(NetworkResult)>;
using NetworkSuccessCallback = std::function<Outcome(NetworkResult)>;
using NetworkErrorCallback = std::function<bool(int)>;
using NetworkReplyCreatedCallback = std::function<void(QNetworkReply *)>;

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