mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Merge branch 'master' into git_is_pepega
This commit is contained in:
commit
d7a7df7427
100 changed files with 2424 additions and 5215 deletions
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
|
@ -30,12 +30,25 @@ jobs:
|
||||||
modules: qtwebengine
|
modules: qtwebengine
|
||||||
|
|
||||||
# WINDOWS
|
# WINDOWS
|
||||||
|
- name: Cache conan
|
||||||
|
if: startsWith(matrix.os, 'windows')
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
key: ${{ runner.os }}-conan-root-${{ hashFiles('**/conanfile.txt') }}
|
||||||
|
path: ~/.conan
|
||||||
|
|
||||||
|
- name: Cache conan packages
|
||||||
|
if: startsWith(matrix.os, 'windows')
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
key: ${{ runner.os }}-conan-pkg-${{ hashFiles('**/conanfile.txt') }}
|
||||||
|
path: C:/.conan/
|
||||||
|
|
||||||
- name: Install dependencies (Windows)
|
- name: Install dependencies (Windows)
|
||||||
if: startsWith(matrix.os, 'windows')
|
if: startsWith(matrix.os, 'windows')
|
||||||
run: |
|
run: |
|
||||||
REM We use this source (temporarily?) until choco has updated their version of conan
|
REM We use this source (temporarily?) until choco has updated their version of conan
|
||||||
choco source add -n=AFG -s="https://api.bintray.com/nuget/anotherfoxguy/choco-pkg"
|
choco install conan -y -s="https://api.bintray.com/nuget/anotherfoxguy/choco-pkg"
|
||||||
choco install conan -y
|
|
||||||
|
|
||||||
refreshenv
|
refreshenv
|
||||||
shell: cmd
|
shell: cmd
|
||||||
|
|
|
@ -32,6 +32,7 @@ matrix:
|
||||||
- git config --global user.email "builds@travis-ci.com"
|
- git config --global user.email "builds@travis-ci.com"
|
||||||
- git config --global user.name "Travis CI"
|
- git config --global user.name "Travis CI"
|
||||||
- export GIT_TAG=nightly-build
|
- export GIT_TAG=nightly-build
|
||||||
|
- export TRAVIS_TAG=nightly-build
|
||||||
- git tag $GIT_TAG -f
|
- git tag $GIT_TAG -f
|
||||||
- sh ./.CI/CreateAppImage.sh
|
- sh ./.CI/CreateAppImage.sh
|
||||||
|
|
||||||
|
@ -83,6 +84,7 @@ matrix:
|
||||||
- git config --global user.email "builds@travis-ci.com"
|
- git config --global user.email "builds@travis-ci.com"
|
||||||
- git config --global user.name "Travis CI"
|
- git config --global user.name "Travis CI"
|
||||||
- export GIT_TAG=nightly-build
|
- export GIT_TAG=nightly-build
|
||||||
|
- export TRAVIS_TAG=nightly-build
|
||||||
- git tag $GIT_TAG -f
|
- git tag $GIT_TAG -f
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
|
|
|
@ -36,7 +36,7 @@ git submodule update --init --recursive
|
||||||
The code is formatted using clang format in Qt Creator. [.clang-format](src/.clang-format) contains the style file for clang format.
|
The code is formatted using clang format in Qt Creator. [.clang-format](src/.clang-format) contains the style file for clang format.
|
||||||
|
|
||||||
### Get it automated with QT Creator + Beautifier + Clang Format
|
### 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
|
1. Download LLVM: https://releases.llvm.org/9.0.0/LLVM-9.0.0-win64.exe
|
||||||
2. During the installation, make sure to add it to your path
|
2. During the installation, make sure to add it to your path
|
||||||
3. In QT Creator, select `Help` > `About Plugins` > `C++` > `Beautifier` to enable the plugin
|
3. In QT Creator, select `Help` > `About Plugins` > `C++` > `Beautifier` to enable the plugin
|
||||||
4. Restart QT Creator
|
4. Restart QT Creator
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
# Exposed build flags:
|
# Exposed build flags:
|
||||||
# from lib/fmt.pri
|
|
||||||
# - FMT_PREFIX ($$PWD by default)
|
|
||||||
# - FMT_SYSTEM (1 = true) (Linux only, uses pkg-config)
|
|
||||||
# from lib/websocketpp.pri
|
# from lib/websocketpp.pri
|
||||||
# - WEBSOCKETPP_PREFIX ($$PWD by default)
|
# - WEBSOCKETPP_PREFIX ($$PWD by default)
|
||||||
# - WEBSOCKETPP_SYSTEM (1 = true) (unix only)
|
# - WEBSOCKETPP_SYSTEM (1 = true) (unix only)
|
||||||
|
@ -76,7 +73,6 @@ CONFIG(debug, debug|release) {
|
||||||
|
|
||||||
# Submodules
|
# Submodules
|
||||||
include(lib/warnings.pri)
|
include(lib/warnings.pri)
|
||||||
include(lib/fmt.pri)
|
|
||||||
include(lib/humanize.pri)
|
include(lib/humanize.pri)
|
||||||
include(lib/libcommuni.pri)
|
include(lib/libcommuni.pri)
|
||||||
include(lib/websocketpp.pri)
|
include(lib/websocketpp.pri)
|
||||||
|
@ -137,6 +133,7 @@ SOURCES += \
|
||||||
src/controllers/highlights/HighlightBlacklistModel.cpp \
|
src/controllers/highlights/HighlightBlacklistModel.cpp \
|
||||||
src/controllers/highlights/HighlightController.cpp \
|
src/controllers/highlights/HighlightController.cpp \
|
||||||
src/controllers/highlights/HighlightModel.cpp \
|
src/controllers/highlights/HighlightModel.cpp \
|
||||||
|
src/controllers/highlights/HighlightPhrase.cpp \
|
||||||
src/controllers/highlights/UserHighlightModel.cpp \
|
src/controllers/highlights/UserHighlightModel.cpp \
|
||||||
src/controllers/ignores/IgnoreController.cpp \
|
src/controllers/ignores/IgnoreController.cpp \
|
||||||
src/controllers/ignores/IgnoreModel.cpp \
|
src/controllers/ignores/IgnoreModel.cpp \
|
||||||
|
@ -170,6 +167,7 @@ SOURCES += \
|
||||||
src/providers/bttv/BttvEmotes.cpp \
|
src/providers/bttv/BttvEmotes.cpp \
|
||||||
src/providers/bttv/LoadBttvChannelEmote.cpp \
|
src/providers/bttv/LoadBttvChannelEmote.cpp \
|
||||||
src/providers/chatterino/ChatterinoBadges.cpp \
|
src/providers/chatterino/ChatterinoBadges.cpp \
|
||||||
|
src/providers/colors/ColorProvider.cpp \
|
||||||
src/providers/emoji/Emojis.cpp \
|
src/providers/emoji/Emojis.cpp \
|
||||||
src/providers/ffz/FfzEmotes.cpp \
|
src/providers/ffz/FfzEmotes.cpp \
|
||||||
src/providers/irc/AbstractIrcServer.cpp \
|
src/providers/irc/AbstractIrcServer.cpp \
|
||||||
|
@ -214,6 +212,7 @@ SOURCES += \
|
||||||
src/singletons/TooltipPreviewImage.cpp \
|
src/singletons/TooltipPreviewImage.cpp \
|
||||||
src/singletons/Updates.cpp \
|
src/singletons/Updates.cpp \
|
||||||
src/singletons/WindowManager.cpp \
|
src/singletons/WindowManager.cpp \
|
||||||
|
src/util/Clipboard.cpp \
|
||||||
src/util/DebugCount.cpp \
|
src/util/DebugCount.cpp \
|
||||||
src/util/FormatTime.cpp \
|
src/util/FormatTime.cpp \
|
||||||
src/util/FunctionEventFilter.cpp \
|
src/util/FunctionEventFilter.cpp \
|
||||||
|
@ -232,6 +231,7 @@ SOURCES += \
|
||||||
src/widgets/BasePopup.cpp \
|
src/widgets/BasePopup.cpp \
|
||||||
src/widgets/BaseWidget.cpp \
|
src/widgets/BaseWidget.cpp \
|
||||||
src/widgets/BaseWindow.cpp \
|
src/widgets/BaseWindow.cpp \
|
||||||
|
src/widgets/dialogs/ColorPickerDialog.cpp \
|
||||||
src/widgets/dialogs/EmotePopup.cpp \
|
src/widgets/dialogs/EmotePopup.cpp \
|
||||||
src/widgets/dialogs/IrcConnectionEditor.cpp \
|
src/widgets/dialogs/IrcConnectionEditor.cpp \
|
||||||
src/widgets/dialogs/LastRunCrashDialog.cpp \
|
src/widgets/dialogs/LastRunCrashDialog.cpp \
|
||||||
|
@ -247,12 +247,14 @@ SOURCES += \
|
||||||
src/widgets/dialogs/WelcomeDialog.cpp \
|
src/widgets/dialogs/WelcomeDialog.cpp \
|
||||||
src/widgets/helper/Button.cpp \
|
src/widgets/helper/Button.cpp \
|
||||||
src/widgets/helper/ChannelView.cpp \
|
src/widgets/helper/ChannelView.cpp \
|
||||||
|
src/widgets/helper/ColorButton.cpp \
|
||||||
src/widgets/helper/ComboBoxItemDelegate.cpp \
|
src/widgets/helper/ComboBoxItemDelegate.cpp \
|
||||||
src/widgets/helper/DebugPopup.cpp \
|
src/widgets/helper/DebugPopup.cpp \
|
||||||
src/widgets/helper/EditableModelView.cpp \
|
src/widgets/helper/EditableModelView.cpp \
|
||||||
src/widgets/helper/EffectLabel.cpp \
|
src/widgets/helper/EffectLabel.cpp \
|
||||||
src/widgets/helper/NotebookButton.cpp \
|
src/widgets/helper/NotebookButton.cpp \
|
||||||
src/widgets/helper/NotebookTab.cpp \
|
src/widgets/helper/NotebookTab.cpp \
|
||||||
|
src/widgets/helper/QColorPicker.cpp \
|
||||||
src/widgets/helper/ResizingTextEdit.cpp \
|
src/widgets/helper/ResizingTextEdit.cpp \
|
||||||
src/widgets/helper/ScrollbarHighlight.cpp \
|
src/widgets/helper/ScrollbarHighlight.cpp \
|
||||||
src/widgets/helper/SearchPopup.cpp \
|
src/widgets/helper/SearchPopup.cpp \
|
||||||
|
@ -345,7 +347,6 @@ HEADERS += \
|
||||||
src/controllers/taggedusers/TaggedUsersModel.hpp \
|
src/controllers/taggedusers/TaggedUsersModel.hpp \
|
||||||
src/debug/AssertInGuiThread.hpp \
|
src/debug/AssertInGuiThread.hpp \
|
||||||
src/debug/Benchmark.hpp \
|
src/debug/Benchmark.hpp \
|
||||||
src/debug/Log.hpp \
|
|
||||||
src/ForwardDecl.hpp \
|
src/ForwardDecl.hpp \
|
||||||
src/messages/Emote.hpp \
|
src/messages/Emote.hpp \
|
||||||
src/messages/Image.hpp \
|
src/messages/Image.hpp \
|
||||||
|
@ -371,6 +372,7 @@ HEADERS += \
|
||||||
src/providers/bttv/BttvEmotes.hpp \
|
src/providers/bttv/BttvEmotes.hpp \
|
||||||
src/providers/bttv/LoadBttvChannelEmote.hpp \
|
src/providers/bttv/LoadBttvChannelEmote.hpp \
|
||||||
src/providers/chatterino/ChatterinoBadges.hpp \
|
src/providers/chatterino/ChatterinoBadges.hpp \
|
||||||
|
src/providers/colors/ColorProvider.hpp \
|
||||||
src/providers/emoji/Emojis.hpp \
|
src/providers/emoji/Emojis.hpp \
|
||||||
src/providers/ffz/FfzEmotes.hpp \
|
src/providers/ffz/FfzEmotes.hpp \
|
||||||
src/providers/irc/AbstractIrcServer.hpp \
|
src/providers/irc/AbstractIrcServer.hpp \
|
||||||
|
@ -418,6 +420,7 @@ HEADERS += \
|
||||||
src/singletons/Updates.hpp \
|
src/singletons/Updates.hpp \
|
||||||
src/singletons/WindowManager.hpp \
|
src/singletons/WindowManager.hpp \
|
||||||
src/util/Clamp.hpp \
|
src/util/Clamp.hpp \
|
||||||
|
src/util/Clipboard.hpp \
|
||||||
src/util/CombinePath.hpp \
|
src/util/CombinePath.hpp \
|
||||||
src/util/ConcurrentMap.hpp \
|
src/util/ConcurrentMap.hpp \
|
||||||
src/util/DebugCount.hpp \
|
src/util/DebugCount.hpp \
|
||||||
|
@ -455,6 +458,7 @@ HEADERS += \
|
||||||
src/widgets/BasePopup.hpp \
|
src/widgets/BasePopup.hpp \
|
||||||
src/widgets/BaseWidget.hpp \
|
src/widgets/BaseWidget.hpp \
|
||||||
src/widgets/BaseWindow.hpp \
|
src/widgets/BaseWindow.hpp \
|
||||||
|
src/widgets/dialogs/ColorPickerDialog.hpp \
|
||||||
src/widgets/dialogs/EmotePopup.hpp \
|
src/widgets/dialogs/EmotePopup.hpp \
|
||||||
src/widgets/dialogs/IrcConnectionEditor.hpp \
|
src/widgets/dialogs/IrcConnectionEditor.hpp \
|
||||||
src/widgets/dialogs/LastRunCrashDialog.hpp \
|
src/widgets/dialogs/LastRunCrashDialog.hpp \
|
||||||
|
@ -470,6 +474,7 @@ HEADERS += \
|
||||||
src/widgets/dialogs/WelcomeDialog.hpp \
|
src/widgets/dialogs/WelcomeDialog.hpp \
|
||||||
src/widgets/helper/Button.hpp \
|
src/widgets/helper/Button.hpp \
|
||||||
src/widgets/helper/ChannelView.hpp \
|
src/widgets/helper/ChannelView.hpp \
|
||||||
|
src/widgets/helper/ColorButton.hpp \
|
||||||
src/widgets/helper/ComboBoxItemDelegate.hpp \
|
src/widgets/helper/ComboBoxItemDelegate.hpp \
|
||||||
src/widgets/helper/CommonTexts.hpp \
|
src/widgets/helper/CommonTexts.hpp \
|
||||||
src/widgets/helper/DebugPopup.hpp \
|
src/widgets/helper/DebugPopup.hpp \
|
||||||
|
@ -478,6 +483,7 @@ HEADERS += \
|
||||||
src/widgets/helper/Line.hpp \
|
src/widgets/helper/Line.hpp \
|
||||||
src/widgets/helper/NotebookButton.hpp \
|
src/widgets/helper/NotebookButton.hpp \
|
||||||
src/widgets/helper/NotebookTab.hpp \
|
src/widgets/helper/NotebookTab.hpp \
|
||||||
|
src/widgets/helper/QColorPicker.hpp \
|
||||||
src/widgets/helper/ResizingTextEdit.hpp \
|
src/widgets/helper/ResizingTextEdit.hpp \
|
||||||
src/widgets/helper/ScrollbarHighlight.hpp \
|
src/widgets/helper/ScrollbarHighlight.hpp \
|
||||||
src/widgets/helper/SearchPopup.hpp \
|
src/widgets/helper/SearchPopup.hpp \
|
||||||
|
|
18
lib/fmt.pri
18
lib/fmt.pri
|
@ -1,18 +0,0 @@
|
||||||
# fmt
|
|
||||||
# Chatterino2 is tested with FMT 4.0
|
|
||||||
# Exposed build flags:
|
|
||||||
# - FMT_PREFIX ($$PWD by default)
|
|
||||||
# - FMT_SYSTEM (1 = true) (Linux only, uses pkg-config)
|
|
||||||
|
|
||||||
!defined(FMT_PREFIX) {
|
|
||||||
FMT_PREFIX = $$PWD
|
|
||||||
}
|
|
||||||
|
|
||||||
linux:equals(FMT_SYSTEM, "1") {
|
|
||||||
message("Building with system FMT")
|
|
||||||
PKGCONFIG += fmt
|
|
||||||
} else {
|
|
||||||
SOURCES += $$FMT_PREFIX/fmt/fmt/format.cpp
|
|
||||||
|
|
||||||
INCLUDEPATH += $$PWD/fmt/
|
|
||||||
}
|
|
|
@ -1,535 +0,0 @@
|
||||||
/*
|
|
||||||
Formatting library for C++
|
|
||||||
|
|
||||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "format.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <cctype>
|
|
||||||
#include <cerrno>
|
|
||||||
#include <climits>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdarg>
|
|
||||||
#include <cstddef> // for std::ptrdiff_t
|
|
||||||
|
|
||||||
#if defined(_WIN32) && defined(__MINGW32__)
|
|
||||||
# include <cstring>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if FMT_USE_WINDOWS_H
|
|
||||||
# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN)
|
|
||||||
# define WIN32_LEAN_AND_MEAN
|
|
||||||
# endif
|
|
||||||
# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX)
|
|
||||||
# include <windows.h>
|
|
||||||
# else
|
|
||||||
# define NOMINMAX
|
|
||||||
# include <windows.h>
|
|
||||||
# undef NOMINMAX
|
|
||||||
# endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if FMT_EXCEPTIONS
|
|
||||||
# define FMT_TRY try
|
|
||||||
# define FMT_CATCH(x) catch (x)
|
|
||||||
#else
|
|
||||||
# define FMT_TRY if (true)
|
|
||||||
# define FMT_CATCH(x) if (false)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
# pragma warning(push)
|
|
||||||
# pragma warning(disable: 4127) // conditional expression is constant
|
|
||||||
# pragma warning(disable: 4702) // unreachable code
|
|
||||||
// Disable deprecation warning for strerror. The latter is not called but
|
|
||||||
// MSVC fails to detect it.
|
|
||||||
# pragma warning(disable: 4996)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Dummy implementations of strerror_r and strerror_s called if corresponding
|
|
||||||
// system functions are not available.
|
|
||||||
static inline fmt::internal::Null<> strerror_r(int, char *, ...) {
|
|
||||||
return fmt::internal::Null<>();
|
|
||||||
}
|
|
||||||
static inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) {
|
|
||||||
return fmt::internal::Null<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace fmt {
|
|
||||||
|
|
||||||
FMT_FUNC internal::RuntimeError::~RuntimeError() FMT_DTOR_NOEXCEPT {}
|
|
||||||
FMT_FUNC FormatError::~FormatError() FMT_DTOR_NOEXCEPT {}
|
|
||||||
FMT_FUNC SystemError::~SystemError() FMT_DTOR_NOEXCEPT {}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
#ifndef _MSC_VER
|
|
||||||
# define FMT_SNPRINTF snprintf
|
|
||||||
#else // _MSC_VER
|
|
||||||
inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, format);
|
|
||||||
int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args);
|
|
||||||
va_end(args);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
# define FMT_SNPRINTF fmt_snprintf
|
|
||||||
#endif // _MSC_VER
|
|
||||||
|
|
||||||
#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)
|
|
||||||
# define FMT_SWPRINTF snwprintf
|
|
||||||
#else
|
|
||||||
# define FMT_SWPRINTF swprintf
|
|
||||||
#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT)
|
|
||||||
|
|
||||||
const char RESET_COLOR[] = "\x1b[0m";
|
|
||||||
|
|
||||||
typedef void (*FormatFunc)(Writer &, int, StringRef);
|
|
||||||
|
|
||||||
// Portable thread-safe version of strerror.
|
|
||||||
// Sets buffer to point to a string describing the error code.
|
|
||||||
// This can be either a pointer to a string stored in buffer,
|
|
||||||
// or a pointer to some static immutable string.
|
|
||||||
// Returns one of the following values:
|
|
||||||
// 0 - success
|
|
||||||
// ERANGE - buffer is not large enough to store the error message
|
|
||||||
// other - failure
|
|
||||||
// Buffer should be at least of size 1.
|
|
||||||
int safe_strerror(
|
|
||||||
int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT {
|
|
||||||
FMT_ASSERT(buffer != 0 && buffer_size != 0, "invalid buffer");
|
|
||||||
|
|
||||||
class StrError {
|
|
||||||
private:
|
|
||||||
int error_code_;
|
|
||||||
char *&buffer_;
|
|
||||||
std::size_t buffer_size_;
|
|
||||||
|
|
||||||
// A noop assignment operator to avoid bogus warnings.
|
|
||||||
void operator=(const StrError &) {}
|
|
||||||
|
|
||||||
// Handle the result of XSI-compliant version of strerror_r.
|
|
||||||
int handle(int result) {
|
|
||||||
// glibc versions before 2.13 return result in errno.
|
|
||||||
return result == -1 ? errno : result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the result of GNU-specific version of strerror_r.
|
|
||||||
int handle(char *message) {
|
|
||||||
// If the buffer is full then the message is probably truncated.
|
|
||||||
if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1)
|
|
||||||
return ERANGE;
|
|
||||||
buffer_ = message;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the case when strerror_r is not available.
|
|
||||||
int handle(internal::Null<>) {
|
|
||||||
return fallback(strerror_s(buffer_, buffer_size_, error_code_));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to strerror_s when strerror_r is not available.
|
|
||||||
int fallback(int result) {
|
|
||||||
// If the buffer is full then the message is probably truncated.
|
|
||||||
return result == 0 && strlen(buffer_) == buffer_size_ - 1 ?
|
|
||||||
ERANGE : result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to strerror if strerror_r and strerror_s are not available.
|
|
||||||
int fallback(internal::Null<>) {
|
|
||||||
errno = 0;
|
|
||||||
buffer_ = strerror(error_code_);
|
|
||||||
return errno;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
StrError(int err_code, char *&buf, std::size_t buf_size)
|
|
||||||
: error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {}
|
|
||||||
|
|
||||||
int run() {
|
|
||||||
// Suppress a warning about unused strerror_r.
|
|
||||||
strerror_r(0, FMT_NULL, "");
|
|
||||||
return handle(strerror_r(error_code_, buffer_, buffer_size_));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return StrError(error_code, buffer, buffer_size).run();
|
|
||||||
}
|
|
||||||
|
|
||||||
void format_error_code(Writer &out, int error_code,
|
|
||||||
StringRef message) FMT_NOEXCEPT {
|
|
||||||
// Report error code making sure that the output fits into
|
|
||||||
// INLINE_BUFFER_SIZE to avoid dynamic memory allocation and potential
|
|
||||||
// bad_alloc.
|
|
||||||
out.clear();
|
|
||||||
static const char SEP[] = ": ";
|
|
||||||
static const char ERROR_STR[] = "error ";
|
|
||||||
// Subtract 2 to account for terminating null characters in SEP and ERROR_STR.
|
|
||||||
std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;
|
|
||||||
typedef internal::IntTraits<int>::MainType MainType;
|
|
||||||
MainType abs_value = static_cast<MainType>(error_code);
|
|
||||||
if (internal::is_negative(error_code)) {
|
|
||||||
abs_value = 0 - abs_value;
|
|
||||||
++error_code_size;
|
|
||||||
}
|
|
||||||
error_code_size += internal::count_digits(abs_value);
|
|
||||||
if (message.size() <= internal::INLINE_BUFFER_SIZE - error_code_size)
|
|
||||||
out << message << SEP;
|
|
||||||
out << ERROR_STR << error_code;
|
|
||||||
assert(out.size() <= internal::INLINE_BUFFER_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void report_error(FormatFunc func, int error_code,
|
|
||||||
StringRef message) FMT_NOEXCEPT {
|
|
||||||
MemoryWriter full_message;
|
|
||||||
func(full_message, error_code, message);
|
|
||||||
// Use Writer::data instead of Writer::c_str to avoid potential memory
|
|
||||||
// allocation.
|
|
||||||
std::fwrite(full_message.data(), full_message.size(), 1, stderr);
|
|
||||||
std::fputc('\n', stderr);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
FMT_FUNC void SystemError::init(
|
|
||||||
int err_code, CStringRef format_str, ArgList args) {
|
|
||||||
error_code_ = err_code;
|
|
||||||
MemoryWriter w;
|
|
||||||
format_system_error(w, err_code, format(format_str, args));
|
|
||||||
std::runtime_error &base = *this;
|
|
||||||
base = std::runtime_error(w.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
int internal::CharTraits<char>::format_float(
|
|
||||||
char *buffer, std::size_t size, const char *format,
|
|
||||||
unsigned width, int precision, T value) {
|
|
||||||
if (width == 0) {
|
|
||||||
return precision < 0 ?
|
|
||||||
FMT_SNPRINTF(buffer, size, format, value) :
|
|
||||||
FMT_SNPRINTF(buffer, size, format, precision, value);
|
|
||||||
}
|
|
||||||
return precision < 0 ?
|
|
||||||
FMT_SNPRINTF(buffer, size, format, width, value) :
|
|
||||||
FMT_SNPRINTF(buffer, size, format, width, precision, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
int internal::CharTraits<wchar_t>::format_float(
|
|
||||||
wchar_t *buffer, std::size_t size, const wchar_t *format,
|
|
||||||
unsigned width, int precision, T value) {
|
|
||||||
if (width == 0) {
|
|
||||||
return precision < 0 ?
|
|
||||||
FMT_SWPRINTF(buffer, size, format, value) :
|
|
||||||
FMT_SWPRINTF(buffer, size, format, precision, value);
|
|
||||||
}
|
|
||||||
return precision < 0 ?
|
|
||||||
FMT_SWPRINTF(buffer, size, format, width, value) :
|
|
||||||
FMT_SWPRINTF(buffer, size, format, width, precision, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
const char internal::BasicData<T>::DIGITS[] =
|
|
||||||
"0001020304050607080910111213141516171819"
|
|
||||||
"2021222324252627282930313233343536373839"
|
|
||||||
"4041424344454647484950515253545556575859"
|
|
||||||
"6061626364656667686970717273747576777879"
|
|
||||||
"8081828384858687888990919293949596979899";
|
|
||||||
|
|
||||||
#define FMT_POWERS_OF_10(factor) \
|
|
||||||
factor * 10, \
|
|
||||||
factor * 100, \
|
|
||||||
factor * 1000, \
|
|
||||||
factor * 10000, \
|
|
||||||
factor * 100000, \
|
|
||||||
factor * 1000000, \
|
|
||||||
factor * 10000000, \
|
|
||||||
factor * 100000000, \
|
|
||||||
factor * 1000000000
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
const uint32_t internal::BasicData<T>::POWERS_OF_10_32[] = {
|
|
||||||
0, FMT_POWERS_OF_10(1)
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
const uint64_t internal::BasicData<T>::POWERS_OF_10_64[] = {
|
|
||||||
0,
|
|
||||||
FMT_POWERS_OF_10(1),
|
|
||||||
FMT_POWERS_OF_10(ULongLong(1000000000)),
|
|
||||||
// Multiply several constants instead of using a single long long constant
|
|
||||||
// to avoid warnings about C++98 not supporting long long.
|
|
||||||
ULongLong(1000000000) * ULongLong(1000000000) * 10
|
|
||||||
};
|
|
||||||
|
|
||||||
FMT_FUNC void internal::report_unknown_type(char code, const char *type) {
|
|
||||||
(void)type;
|
|
||||||
if (std::isprint(static_cast<unsigned char>(code))) {
|
|
||||||
FMT_THROW(FormatError(
|
|
||||||
format("unknown format code '{}' for {}", code, type)));
|
|
||||||
}
|
|
||||||
FMT_THROW(FormatError(
|
|
||||||
format("unknown format code '\\x{:02x}' for {}",
|
|
||||||
static_cast<unsigned>(code), type)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#if FMT_USE_WINDOWS_H
|
|
||||||
|
|
||||||
FMT_FUNC internal::UTF8ToUTF16::UTF8ToUTF16(StringRef s) {
|
|
||||||
static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16";
|
|
||||||
if (s.size() > INT_MAX)
|
|
||||||
FMT_THROW(WindowsError(ERROR_INVALID_PARAMETER, ERROR_MSG));
|
|
||||||
int s_size = static_cast<int>(s.size());
|
|
||||||
int length = MultiByteToWideChar(
|
|
||||||
CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0);
|
|
||||||
if (length == 0)
|
|
||||||
FMT_THROW(WindowsError(GetLastError(), ERROR_MSG));
|
|
||||||
buffer_.resize(length + 1);
|
|
||||||
length = MultiByteToWideChar(
|
|
||||||
CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length);
|
|
||||||
if (length == 0)
|
|
||||||
FMT_THROW(WindowsError(GetLastError(), ERROR_MSG));
|
|
||||||
buffer_[length] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_FUNC internal::UTF16ToUTF8::UTF16ToUTF8(WStringRef s) {
|
|
||||||
if (int error_code = convert(s)) {
|
|
||||||
FMT_THROW(WindowsError(error_code,
|
|
||||||
"cannot convert string from UTF-16 to UTF-8"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_FUNC int internal::UTF16ToUTF8::convert(WStringRef s) {
|
|
||||||
if (s.size() > INT_MAX)
|
|
||||||
return ERROR_INVALID_PARAMETER;
|
|
||||||
int s_size = static_cast<int>(s.size());
|
|
||||||
int length = WideCharToMultiByte(
|
|
||||||
CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL);
|
|
||||||
if (length == 0)
|
|
||||||
return GetLastError();
|
|
||||||
buffer_.resize(length + 1);
|
|
||||||
length = WideCharToMultiByte(
|
|
||||||
CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL);
|
|
||||||
if (length == 0)
|
|
||||||
return GetLastError();
|
|
||||||
buffer_[length] = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_FUNC void WindowsError::init(
|
|
||||||
int err_code, CStringRef format_str, ArgList args) {
|
|
||||||
error_code_ = err_code;
|
|
||||||
MemoryWriter w;
|
|
||||||
internal::format_windows_error(w, err_code, format(format_str, args));
|
|
||||||
std::runtime_error &base = *this;
|
|
||||||
base = std::runtime_error(w.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_FUNC void internal::format_windows_error(
|
|
||||||
Writer &out, int error_code, StringRef message) FMT_NOEXCEPT {
|
|
||||||
FMT_TRY {
|
|
||||||
MemoryBuffer<wchar_t, INLINE_BUFFER_SIZE> buffer;
|
|
||||||
buffer.resize(INLINE_BUFFER_SIZE);
|
|
||||||
for (;;) {
|
|
||||||
wchar_t *system_message = &buffer[0];
|
|
||||||
int result = FormatMessageW(
|
|
||||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
||||||
FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
||||||
system_message, static_cast<uint32_t>(buffer.size()), FMT_NULL);
|
|
||||||
if (result != 0) {
|
|
||||||
UTF16ToUTF8 utf8_message;
|
|
||||||
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
|
|
||||||
out << message << ": " << utf8_message;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
|
||||||
break; // Can't get error message, report error code instead.
|
|
||||||
buffer.resize(buffer.size() * 2);
|
|
||||||
}
|
|
||||||
} FMT_CATCH(...) {}
|
|
||||||
fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32.
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // FMT_USE_WINDOWS_H
|
|
||||||
|
|
||||||
FMT_FUNC void format_system_error(
|
|
||||||
Writer &out, int error_code, StringRef message) FMT_NOEXCEPT {
|
|
||||||
FMT_TRY {
|
|
||||||
internal::MemoryBuffer<char, internal::INLINE_BUFFER_SIZE> buffer;
|
|
||||||
buffer.resize(internal::INLINE_BUFFER_SIZE);
|
|
||||||
for (;;) {
|
|
||||||
char *system_message = &buffer[0];
|
|
||||||
int result = safe_strerror(error_code, system_message, buffer.size());
|
|
||||||
if (result == 0) {
|
|
||||||
out << message << ": " << system_message;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (result != ERANGE)
|
|
||||||
break; // Can't get error message, report error code instead.
|
|
||||||
buffer.resize(buffer.size() * 2);
|
|
||||||
}
|
|
||||||
} FMT_CATCH(...) {}
|
|
||||||
fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32.
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Char>
|
|
||||||
void internal::ArgMap<Char>::init(const ArgList &args) {
|
|
||||||
if (!map_.empty())
|
|
||||||
return;
|
|
||||||
typedef internal::NamedArg<Char> NamedArg;
|
|
||||||
const NamedArg *named_arg = FMT_NULL;
|
|
||||||
bool use_values =
|
|
||||||
args.type(ArgList::MAX_PACKED_ARGS - 1) == internal::Arg::NONE;
|
|
||||||
if (use_values) {
|
|
||||||
for (unsigned i = 0;/*nothing*/; ++i) {
|
|
||||||
internal::Arg::Type arg_type = args.type(i);
|
|
||||||
switch (arg_type) {
|
|
||||||
case internal::Arg::NONE:
|
|
||||||
return;
|
|
||||||
case internal::Arg::NAMED_ARG:
|
|
||||||
named_arg = static_cast<const NamedArg*>(args.values_[i].pointer);
|
|
||||||
map_.push_back(Pair(named_arg->name, *named_arg));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
/*nothing*/;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (unsigned i = 0; i != ArgList::MAX_PACKED_ARGS; ++i) {
|
|
||||||
internal::Arg::Type arg_type = args.type(i);
|
|
||||||
if (arg_type == internal::Arg::NAMED_ARG) {
|
|
||||||
named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);
|
|
||||||
map_.push_back(Pair(named_arg->name, *named_arg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (unsigned i = ArgList::MAX_PACKED_ARGS;/*nothing*/; ++i) {
|
|
||||||
switch (args.args_[i].type) {
|
|
||||||
case internal::Arg::NONE:
|
|
||||||
return;
|
|
||||||
case internal::Arg::NAMED_ARG:
|
|
||||||
named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);
|
|
||||||
map_.push_back(Pair(named_arg->name, *named_arg));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
/*nothing*/;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Char>
|
|
||||||
void internal::FixedBuffer<Char>::grow(std::size_t) {
|
|
||||||
FMT_THROW(std::runtime_error("buffer overflow"));
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_FUNC internal::Arg internal::FormatterBase::do_get_arg(
|
|
||||||
unsigned arg_index, const char *&error) {
|
|
||||||
internal::Arg arg = args_[arg_index];
|
|
||||||
switch (arg.type) {
|
|
||||||
case internal::Arg::NONE:
|
|
||||||
error = "argument index out of range";
|
|
||||||
break;
|
|
||||||
case internal::Arg::NAMED_ARG:
|
|
||||||
arg = *static_cast<const internal::Arg*>(arg.pointer);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
/*nothing*/;
|
|
||||||
}
|
|
||||||
return arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_FUNC void report_system_error(
|
|
||||||
int error_code, fmt::StringRef message) FMT_NOEXCEPT {
|
|
||||||
// 'fmt::' is for bcc32.
|
|
||||||
report_error(format_system_error, error_code, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if FMT_USE_WINDOWS_H
|
|
||||||
FMT_FUNC void report_windows_error(
|
|
||||||
int error_code, fmt::StringRef message) FMT_NOEXCEPT {
|
|
||||||
// 'fmt::' is for bcc32.
|
|
||||||
report_error(internal::format_windows_error, error_code, message);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
FMT_FUNC void print(std::FILE *f, CStringRef format_str, ArgList args) {
|
|
||||||
MemoryWriter w;
|
|
||||||
w.write(format_str, args);
|
|
||||||
std::fwrite(w.data(), 1, w.size(), f);
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_FUNC void print(CStringRef format_str, ArgList args) {
|
|
||||||
print(stdout, format_str, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_FUNC void print_colored(Color c, CStringRef format, ArgList args) {
|
|
||||||
char escape[] = "\x1b[30m";
|
|
||||||
escape[3] = static_cast<char>('0' + c);
|
|
||||||
std::fputs(escape, stdout);
|
|
||||||
print(format, args);
|
|
||||||
std::fputs(RESET_COLOR, stdout);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef FMT_HEADER_ONLY
|
|
||||||
|
|
||||||
template struct internal::BasicData<void>;
|
|
||||||
|
|
||||||
// Explicit instantiations for char.
|
|
||||||
|
|
||||||
template void internal::FixedBuffer<char>::grow(std::size_t);
|
|
||||||
|
|
||||||
template void internal::ArgMap<char>::init(const ArgList &args);
|
|
||||||
|
|
||||||
template FMT_API int internal::CharTraits<char>::format_float(
|
|
||||||
char *buffer, std::size_t size, const char *format,
|
|
||||||
unsigned width, int precision, double value);
|
|
||||||
|
|
||||||
template FMT_API int internal::CharTraits<char>::format_float(
|
|
||||||
char *buffer, std::size_t size, const char *format,
|
|
||||||
unsigned width, int precision, long double value);
|
|
||||||
|
|
||||||
// Explicit instantiations for wchar_t.
|
|
||||||
|
|
||||||
template void internal::FixedBuffer<wchar_t>::grow(std::size_t);
|
|
||||||
|
|
||||||
template void internal::ArgMap<wchar_t>::init(const ArgList &args);
|
|
||||||
|
|
||||||
template FMT_API int internal::CharTraits<wchar_t>::format_float(
|
|
||||||
wchar_t *buffer, std::size_t size, const wchar_t *format,
|
|
||||||
unsigned width, int precision, double value);
|
|
||||||
|
|
||||||
template FMT_API int internal::CharTraits<wchar_t>::format_float(
|
|
||||||
wchar_t *buffer, std::size_t size, const wchar_t *format,
|
|
||||||
unsigned width, int precision, long double value);
|
|
||||||
|
|
||||||
#endif // FMT_HEADER_ONLY
|
|
||||||
|
|
||||||
} // namespace fmt
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
# pragma warning(pop)
|
|
||||||
#endif
|
|
4012
lib/fmt/fmt/format.h
4012
lib/fmt/fmt/format.h
File diff suppressed because it is too large
Load diff
|
@ -1,23 +0,0 @@
|
||||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
|
||||||
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -40,7 +40,6 @@
|
||||||
<file>icon.png</file>
|
<file>icon.png</file>
|
||||||
<file>licenses/boost_boost.txt</file>
|
<file>licenses/boost_boost.txt</file>
|
||||||
<file>licenses/emoji-data-source.txt</file>
|
<file>licenses/emoji-data-source.txt</file>
|
||||||
<file>licenses/fmt_bsd2.txt</file>
|
|
||||||
<file>licenses/libcommuni_BSD3.txt</file>
|
<file>licenses/libcommuni_BSD3.txt</file>
|
||||||
<file>licenses/openssl.txt</file>
|
<file>licenses/openssl.txt</file>
|
||||||
<file>licenses/pajlada_settings.txt</file>
|
<file>licenses/pajlada_settings.txt</file>
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
#include "controllers/notifications/NotificationController.hpp"
|
#include "controllers/notifications/NotificationController.hpp"
|
||||||
#include "controllers/pings/PingController.hpp"
|
#include "controllers/pings/PingController.hpp"
|
||||||
#include "controllers/taggedusers/TaggedUsersController.hpp"
|
#include "controllers/taggedusers/TaggedUsersController.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
#include "providers/chatterino/ChatterinoBadges.hpp"
|
#include "providers/chatterino/ChatterinoBadges.hpp"
|
||||||
|
@ -158,11 +157,11 @@ void Application::initNm(Paths &paths)
|
||||||
void Application::initPubsub()
|
void Application::initPubsub()
|
||||||
{
|
{
|
||||||
this->twitch.pubsub->signals_.whisper.sent.connect([](const auto &msg) {
|
this->twitch.pubsub->signals_.whisper.sent.connect([](const auto &msg) {
|
||||||
log("WHISPER SENT LOL"); //
|
qDebug() << "WHISPER SENT LOL"; //
|
||||||
});
|
});
|
||||||
|
|
||||||
this->twitch.pubsub->signals_.whisper.received.connect([](const auto &msg) {
|
this->twitch.pubsub->signals_.whisper.received.connect([](const auto &msg) {
|
||||||
log("WHISPER RECEIVED LOL"); //
|
qDebug() << "WHISPER RECEIVED LOL"; //
|
||||||
});
|
});
|
||||||
|
|
||||||
this->twitch.pubsub->signals_.moderation.chatCleared.connect(
|
this->twitch.pubsub->signals_.moderation.chatCleared.connect(
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
# include <fmt/format.h>
|
|
||||||
# include <irccommand.h>
|
# include <irccommand.h>
|
||||||
# include <ircconnection.h>
|
# include <ircconnection.h>
|
||||||
# include <rapidjson/document.h>
|
# include <rapidjson/document.h>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
#include "common/Channel.hpp"
|
#include "common/Channel.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
|
#include "providers/twitch/IrcMessageHandler.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/Logging.hpp"
|
#include "singletons/Logging.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/commands/CommandController.hpp"
|
#include "controllers/commands/CommandController.hpp"
|
||||||
#include "debug/Benchmark.hpp"
|
#include "debug/Benchmark.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#include "DownloadManager.hpp"
|
#include "DownloadManager.hpp"
|
||||||
|
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
|
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
|
@ -41,7 +40,7 @@ void DownloadManager::setFile(QString fileURL, const QString &channelName)
|
||||||
|
|
||||||
void DownloadManager::onDownloadProgress(qint64 bytesRead, qint64 bytesTotal)
|
void DownloadManager::onDownloadProgress(qint64 bytesRead, qint64 bytesTotal)
|
||||||
{
|
{
|
||||||
log("Download progress: {}/{}", bytesRead, bytesTotal);
|
qDebug() << "Download progress: " << bytesRead << "/" << bytesTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DownloadManager::onFinished(QNetworkReply *reply)
|
void DownloadManager::onFinished(QNetworkReply *reply)
|
||||||
|
|
|
@ -61,7 +61,7 @@ Env::Env()
|
||||||
"https://i.nuuls.com/upload"))
|
"https://i.nuuls.com/upload"))
|
||||||
, twitchServerHost(
|
, twitchServerHost(
|
||||||
readStringEnv("CHATTERINO2_TWITCH_SERVER_HOST", "irc.chat.twitch.tv"))
|
readStringEnv("CHATTERINO2_TWITCH_SERVER_HOST", "irc.chat.twitch.tv"))
|
||||||
, twitchServerPort(readPortEnv("CHATTERINO2_TWITCH_SERVER_PORT", 6697))
|
, twitchServerPort(readPortEnv("CHATTERINO2_TWITCH_SERVER_PORT", 443))
|
||||||
, twitchServerSecure(readBoolEnv("CHATTERINO2_TWITCH_SERVER_SECURE", true))
|
, twitchServerSecure(readBoolEnv("CHATTERINO2_TWITCH_SERVER_SECURE", true))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#include "common/NetworkResult.hpp"
|
#include "common/NetworkResult.hpp"
|
||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
#include "debug/AssertInGuiThread.hpp"
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "util/DebugCount.hpp"
|
#include "util/DebugCount.hpp"
|
||||||
#include "util/PostToThread.hpp"
|
#include "util/PostToThread.hpp"
|
||||||
|
@ -124,7 +123,7 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
|
||||||
|
|
||||||
if (reply == nullptr)
|
if (reply == nullptr)
|
||||||
{
|
{
|
||||||
log("Unhandled request type");
|
qDebug() << "Unhandled request type";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +131,7 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
|
||||||
{
|
{
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
data->timer_, &QTimer::timeout, worker, [reply, data]() {
|
data->timer_, &QTimer::timeout, worker, [reply, data]() {
|
||||||
log("Aborted!");
|
qDebug() << "Aborted!";
|
||||||
reply->abort();
|
reply->abort();
|
||||||
if (data->onError_)
|
if (data->onError_)
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
#include "common/Version.hpp"
|
#include "common/Version.hpp"
|
||||||
#include "debug/AssertInGuiThread.hpp"
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "util/DebugCount.hpp"
|
#include "util/DebugCount.hpp"
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
#include "common/NetworkResult.hpp"
|
#include "common/NetworkResult.hpp"
|
||||||
|
|
||||||
#include "debug/Log.hpp"
|
|
||||||
|
|
||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
#include <rapidjson/error/en.h>
|
#include <rapidjson/error/en.h>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
@ -45,8 +43,9 @@ rapidjson::Document NetworkResult::parseRapidJson() const
|
||||||
|
|
||||||
if (result.Code() != rapidjson::kParseErrorNone)
|
if (result.Code() != rapidjson::kParseErrorNone)
|
||||||
{
|
{
|
||||||
log("JSON parse error: {} ({})",
|
qDebug() << "JSON parse error:"
|
||||||
rapidjson::GetParseError_En(result.Code()), result.Offset());
|
<< rapidjson::GetParseError_En(result.Code()) << "("
|
||||||
|
<< result.Offset() << ")";
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/commands/Command.hpp"
|
#include "controllers/commands/Command.hpp"
|
||||||
#include "controllers/commands/CommandModel.hpp"
|
#include "controllers/commands/CommandModel.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
|
@ -18,6 +17,7 @@
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "util/CombinePath.hpp"
|
#include "util/CombinePath.hpp"
|
||||||
#include "widgets/dialogs/LogsPopup.hpp"
|
#include "widgets/dialogs/LogsPopup.hpp"
|
||||||
|
#include "widgets/dialogs/UserInfoPopup.hpp"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
@ -482,6 +482,21 @@ QString CommandController::execCommand(const QString &textNoEmoji,
|
||||||
channelName + "/viewercard/" + words[1]);
|
channelName + "/viewercard/" + words[1]);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
else if (commandName == "/usercard")
|
||||||
|
{
|
||||||
|
if (words.size() < 2)
|
||||||
|
{
|
||||||
|
channel->addMessage(
|
||||||
|
makeSystemMessage("Usage /usercard [user]"));
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
auto *userPopup = new UserInfoPopup;
|
||||||
|
userPopup->setData(words[1], channel);
|
||||||
|
userPopup->setActionOnFocusLoss(BaseWindow::Delete);
|
||||||
|
userPopup->move(QCursor::pos());
|
||||||
|
userPopup->show();
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,16 +18,17 @@ HighlightBlacklistUser HighlightBlacklistModel::getItemFromRow(
|
||||||
{
|
{
|
||||||
// key, regex
|
// key, regex
|
||||||
|
|
||||||
return HighlightBlacklistUser{row[0]->data(Qt::DisplayRole).toString(),
|
return HighlightBlacklistUser{
|
||||||
row[1]->data(Qt::CheckStateRole).toBool()};
|
row[Column::Pattern]->data(Qt::DisplayRole).toString(),
|
||||||
|
row[Column::UseRegex]->data(Qt::CheckStateRole).toBool()};
|
||||||
}
|
}
|
||||||
|
|
||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
void HighlightBlacklistModel::getRowFromItem(const HighlightBlacklistUser &item,
|
void HighlightBlacklistModel::getRowFromItem(const HighlightBlacklistUser &item,
|
||||||
std::vector<QStandardItem *> &row)
|
std::vector<QStandardItem *> &row)
|
||||||
{
|
{
|
||||||
setStringItem(row[0], item.getPattern());
|
setStringItem(row[Column::Pattern], item.getPattern());
|
||||||
setBoolItem(row[1], item.isRegex());
|
setBoolItem(row[Column::UseRegex], item.isRegex());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -13,6 +13,12 @@ class HighlightBlacklistModel : public SignalVectorModel<HighlightBlacklistUser>
|
||||||
{
|
{
|
||||||
explicit HighlightBlacklistModel(QObject *parent);
|
explicit HighlightBlacklistModel(QObject *parent);
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Column {
|
||||||
|
Pattern = 0,
|
||||||
|
UseRegex = 1,
|
||||||
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// turn a vector item into a model row
|
// turn a vector item into a model row
|
||||||
virtual HighlightBlacklistUser getItemFromRow(
|
virtual HighlightBlacklistUser getItemFromRow(
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace chatterino {
|
||||||
|
|
||||||
// commandmodel
|
// commandmodel
|
||||||
HighlightModel::HighlightModel(QObject *parent)
|
HighlightModel::HighlightModel(QObject *parent)
|
||||||
: SignalVectorModel<HighlightPhrase>(5, parent)
|
: SignalVectorModel<HighlightPhrase>(7, parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,52 +16,103 @@ HighlightModel::HighlightModel(QObject *parent)
|
||||||
HighlightPhrase HighlightModel::getItemFromRow(
|
HighlightPhrase HighlightModel::getItemFromRow(
|
||||||
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
|
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
|
||||||
{
|
{
|
||||||
// key, alert, sound, regex, case-sensitivity
|
// In order for old messages to update their highlight color, we need to
|
||||||
|
// update the highlight color here.
|
||||||
|
auto highlightColor = original.getColor();
|
||||||
|
*highlightColor =
|
||||||
|
row[Column::Color]->data(Qt::DecorationRole).value<QColor>();
|
||||||
|
|
||||||
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
|
return HighlightPhrase{
|
||||||
row[1]->data(Qt::CheckStateRole).toBool(),
|
row[Column::Pattern]->data(Qt::DisplayRole).toString(),
|
||||||
row[2]->data(Qt::CheckStateRole).toBool(),
|
row[Column::FlashTaskbar]->data(Qt::CheckStateRole).toBool(),
|
||||||
row[3]->data(Qt::CheckStateRole).toBool(),
|
row[Column::PlaySound]->data(Qt::CheckStateRole).toBool(),
|
||||||
row[4]->data(Qt::CheckStateRole).toBool()};
|
row[Column::UseRegex]->data(Qt::CheckStateRole).toBool(),
|
||||||
|
row[Column::CaseSensitive]->data(Qt::CheckStateRole).toBool(),
|
||||||
|
row[Column::SoundPath]->data(Qt::UserRole).toString(),
|
||||||
|
highlightColor};
|
||||||
}
|
}
|
||||||
|
|
||||||
// turns a row in the model into a vector item
|
// turns a row in the model into a vector item
|
||||||
void HighlightModel::getRowFromItem(const HighlightPhrase &item,
|
void HighlightModel::getRowFromItem(const HighlightPhrase &item,
|
||||||
std::vector<QStandardItem *> &row)
|
std::vector<QStandardItem *> &row)
|
||||||
{
|
{
|
||||||
setStringItem(row[0], item.getPattern());
|
setStringItem(row[Column::Pattern], item.getPattern());
|
||||||
setBoolItem(row[1], item.getAlert());
|
setBoolItem(row[Column::FlashTaskbar], item.hasAlert());
|
||||||
setBoolItem(row[2], item.getSound());
|
setBoolItem(row[Column::PlaySound], item.hasSound());
|
||||||
setBoolItem(row[3], item.isRegex());
|
setBoolItem(row[Column::UseRegex], item.isRegex());
|
||||||
setBoolItem(row[4], item.isCaseSensitive());
|
setBoolItem(row[Column::CaseSensitive], item.isCaseSensitive());
|
||||||
|
setFilePathItem(row[Column::SoundPath], item.getSoundUrl());
|
||||||
|
setColorItem(row[Column::Color], *item.getColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
void HighlightModel::afterInit()
|
void HighlightModel::afterInit()
|
||||||
{
|
{
|
||||||
|
// Highlight settings for own username
|
||||||
std::vector<QStandardItem *> usernameRow = this->createRow();
|
std::vector<QStandardItem *> usernameRow = this->createRow();
|
||||||
setBoolItem(usernameRow[0], getSettings()->enableSelfHighlight.getValue(),
|
setBoolItem(usernameRow[Column::Pattern],
|
||||||
true, false);
|
getSettings()->enableSelfHighlight.getValue(), true, false);
|
||||||
usernameRow[0]->setData("Your username (automatic)", Qt::DisplayRole);
|
usernameRow[Column::Pattern]->setData("Your username (automatic)",
|
||||||
setBoolItem(usernameRow[1],
|
Qt::DisplayRole);
|
||||||
|
setBoolItem(usernameRow[Column::FlashTaskbar],
|
||||||
getSettings()->enableSelfHighlightTaskbar.getValue(), true,
|
getSettings()->enableSelfHighlightTaskbar.getValue(), true,
|
||||||
false);
|
false);
|
||||||
setBoolItem(usernameRow[2],
|
setBoolItem(usernameRow[Column::PlaySound],
|
||||||
getSettings()->enableSelfHighlightSound.getValue(), true,
|
getSettings()->enableSelfHighlightSound.getValue(), true,
|
||||||
false);
|
false);
|
||||||
usernameRow[3]->setFlags(0);
|
usernameRow[Column::UseRegex]->setFlags(0);
|
||||||
|
usernameRow[Column::CaseSensitive]->setFlags(0);
|
||||||
|
|
||||||
|
QUrl selfSound = QUrl(getSettings()->selfHighlightSoundUrl.getValue());
|
||||||
|
setFilePathItem(usernameRow[Column::SoundPath], selfSound);
|
||||||
|
|
||||||
|
auto selfColor = ColorProvider::instance().color(ColorType::SelfHighlight);
|
||||||
|
setColorItem(usernameRow[Column::Color], *selfColor);
|
||||||
|
|
||||||
this->insertCustomRow(usernameRow, 0);
|
this->insertCustomRow(usernameRow, 0);
|
||||||
|
|
||||||
|
// Highlight settings for whispers
|
||||||
std::vector<QStandardItem *> whisperRow = this->createRow();
|
std::vector<QStandardItem *> whisperRow = this->createRow();
|
||||||
setBoolItem(whisperRow[0], getSettings()->enableWhisperHighlight.getValue(),
|
setBoolItem(whisperRow[Column::Pattern],
|
||||||
true, false);
|
getSettings()->enableWhisperHighlight.getValue(), true, false);
|
||||||
whisperRow[0]->setData("Whispers", Qt::DisplayRole);
|
whisperRow[Column::Pattern]->setData("Whispers", Qt::DisplayRole);
|
||||||
setBoolItem(whisperRow[1],
|
setBoolItem(whisperRow[Column::FlashTaskbar],
|
||||||
getSettings()->enableWhisperHighlightTaskbar.getValue(), true,
|
getSettings()->enableWhisperHighlightTaskbar.getValue(), true,
|
||||||
false);
|
false);
|
||||||
setBoolItem(whisperRow[2],
|
setBoolItem(whisperRow[Column::PlaySound],
|
||||||
getSettings()->enableWhisperHighlightSound.getValue(), true,
|
getSettings()->enableWhisperHighlightSound.getValue(), true,
|
||||||
false);
|
false);
|
||||||
whisperRow[3]->setFlags(0);
|
whisperRow[Column::UseRegex]->setFlags(0);
|
||||||
|
whisperRow[Column::CaseSensitive]->setFlags(0);
|
||||||
|
|
||||||
|
QUrl whisperSound =
|
||||||
|
QUrl(getSettings()->whisperHighlightSoundUrl.getValue());
|
||||||
|
setFilePathItem(whisperRow[Column::SoundPath], whisperSound);
|
||||||
|
|
||||||
|
auto whisperColor = ColorProvider::instance().color(ColorType::Whisper);
|
||||||
|
setColorItem(whisperRow[Column::Color], *whisperColor);
|
||||||
|
|
||||||
this->insertCustomRow(whisperRow, 1);
|
this->insertCustomRow(whisperRow, 1);
|
||||||
|
|
||||||
|
// Highlight settings for subscription messages
|
||||||
|
std::vector<QStandardItem *> subRow = this->createRow();
|
||||||
|
setBoolItem(subRow[Column::Pattern],
|
||||||
|
getSettings()->enableSubHighlight.getValue(), true, false);
|
||||||
|
subRow[Column::Pattern]->setData("Subscriptions", Qt::DisplayRole);
|
||||||
|
setBoolItem(subRow[Column::FlashTaskbar],
|
||||||
|
getSettings()->enableSubHighlightTaskbar.getValue(), true,
|
||||||
|
false);
|
||||||
|
setBoolItem(subRow[Column::PlaySound],
|
||||||
|
getSettings()->enableSubHighlightSound.getValue(), true, false);
|
||||||
|
subRow[Column::UseRegex]->setFlags(0);
|
||||||
|
subRow[Column::CaseSensitive]->setFlags(0);
|
||||||
|
|
||||||
|
QUrl subSound = QUrl(getSettings()->subHighlightSoundUrl.getValue());
|
||||||
|
setFilePathItem(subRow[Column::SoundPath], subSound);
|
||||||
|
|
||||||
|
auto subColor = ColorProvider::instance().color(ColorType::Subscription);
|
||||||
|
setColorItem(subRow[Column::Color], *subColor);
|
||||||
|
|
||||||
|
this->insertCustomRow(subRow, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
|
@ -70,7 +121,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
{
|
{
|
||||||
switch (column)
|
switch (column)
|
||||||
{
|
{
|
||||||
case 0: {
|
case Column::Pattern: {
|
||||||
if (role == Qt::CheckStateRole)
|
if (role == Qt::CheckStateRole)
|
||||||
{
|
{
|
||||||
if (rowIndex == 0)
|
if (rowIndex == 0)
|
||||||
|
@ -82,10 +133,14 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
getSettings()->enableWhisperHighlight.setValue(
|
getSettings()->enableWhisperHighlight.setValue(
|
||||||
value.toBool());
|
value.toBool());
|
||||||
}
|
}
|
||||||
|
else if (rowIndex == 2)
|
||||||
|
{
|
||||||
|
getSettings()->enableSubHighlight.setValue(value.toBool());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 1: {
|
case Column::FlashTaskbar: {
|
||||||
if (role == Qt::CheckStateRole)
|
if (role == Qt::CheckStateRole)
|
||||||
{
|
{
|
||||||
if (rowIndex == 0)
|
if (rowIndex == 0)
|
||||||
|
@ -98,10 +153,15 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
getSettings()->enableWhisperHighlightTaskbar.setValue(
|
getSettings()->enableWhisperHighlightTaskbar.setValue(
|
||||||
value.toBool());
|
value.toBool());
|
||||||
}
|
}
|
||||||
|
else if (rowIndex == 2)
|
||||||
|
{
|
||||||
|
getSettings()->enableSubHighlightTaskbar.setValue(
|
||||||
|
value.toBool());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 2: {
|
case Column::PlaySound: {
|
||||||
if (role == Qt::CheckStateRole)
|
if (role == Qt::CheckStateRole)
|
||||||
{
|
{
|
||||||
if (rowIndex == 0)
|
if (rowIndex == 0)
|
||||||
|
@ -114,11 +174,62 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
||||||
getSettings()->enableWhisperHighlightSound.setValue(
|
getSettings()->enableWhisperHighlightSound.setValue(
|
||||||
value.toBool());
|
value.toBool());
|
||||||
}
|
}
|
||||||
|
else if (rowIndex == 2)
|
||||||
|
{
|
||||||
|
getSettings()->enableSubHighlightSound.setValue(
|
||||||
|
value.toBool());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 3: {
|
case Column::UseRegex: {
|
||||||
// empty element
|
// Regex --> empty
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Column::CaseSensitive: {
|
||||||
|
// Case-sensitivity --> empty
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Column::SoundPath: {
|
||||||
|
// Custom sound file
|
||||||
|
if (role == Qt::UserRole)
|
||||||
|
{
|
||||||
|
if (rowIndex == 0)
|
||||||
|
{
|
||||||
|
getSettings()->selfHighlightSoundUrl.setValue(
|
||||||
|
value.toString());
|
||||||
|
}
|
||||||
|
else if (rowIndex == 1)
|
||||||
|
{
|
||||||
|
getSettings()->whisperHighlightSoundUrl.setValue(
|
||||||
|
value.toString());
|
||||||
|
}
|
||||||
|
else if (rowIndex == 2)
|
||||||
|
{
|
||||||
|
getSettings()->subHighlightSoundUrl.setValue(
|
||||||
|
value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Column::Color: {
|
||||||
|
// Custom color
|
||||||
|
if (role == Qt::DecorationRole)
|
||||||
|
{
|
||||||
|
auto colorName = value.value<QColor>().name(QColor::HexArgb);
|
||||||
|
if (rowIndex == 0)
|
||||||
|
{
|
||||||
|
getSettings()->selfHighlightColor.setValue(colorName);
|
||||||
|
}
|
||||||
|
else if (rowIndex == 1)
|
||||||
|
{
|
||||||
|
getSettings()->whisperHighlightColor.setValue(colorName);
|
||||||
|
}
|
||||||
|
else if (rowIndex == 2)
|
||||||
|
{
|
||||||
|
getSettings()->subHighlightColor.setValue(colorName);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,18 @@ class HighlightModel : public SignalVectorModel<HighlightPhrase>
|
||||||
{
|
{
|
||||||
explicit HighlightModel(QObject *parent);
|
explicit HighlightModel(QObject *parent);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Used here, in HighlightingPage and in UserHighlightModel
|
||||||
|
enum Column {
|
||||||
|
Pattern = 0,
|
||||||
|
FlashTaskbar = 1,
|
||||||
|
PlaySound = 2,
|
||||||
|
UseRegex = 3,
|
||||||
|
CaseSensitive = 4,
|
||||||
|
SoundPath = 5,
|
||||||
|
Color = 6
|
||||||
|
};
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// turn a vector item into a model row
|
// turn a vector item into a model row
|
||||||
virtual HighlightPhrase getItemFromRow(
|
virtual HighlightPhrase getItemFromRow(
|
||||||
|
|
103
src/controllers/highlights/HighlightPhrase.cpp
Normal file
103
src/controllers/highlights/HighlightPhrase.cpp
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
#include "controllers/highlights/HighlightPhrase.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
bool HighlightPhrase::operator==(const HighlightPhrase &other) const
|
||||||
|
{
|
||||||
|
return std::tie(this->pattern_, this->hasSound_, this->hasAlert_,
|
||||||
|
this->isRegex_, this->isCaseSensitive_, this->soundUrl_,
|
||||||
|
this->color_) == std::tie(other.pattern_, other.hasSound_,
|
||||||
|
other.hasAlert_, other.isRegex_,
|
||||||
|
other.isCaseSensitive_,
|
||||||
|
other.soundUrl_, other.color_);
|
||||||
|
}
|
||||||
|
|
||||||
|
HighlightPhrase::HighlightPhrase(const QString &pattern, bool hasAlert,
|
||||||
|
bool hasSound, bool isRegex,
|
||||||
|
bool isCaseSensitive, const QString &soundUrl,
|
||||||
|
QColor color)
|
||||||
|
: pattern_(pattern)
|
||||||
|
, hasAlert_(hasAlert)
|
||||||
|
, hasSound_(hasSound)
|
||||||
|
, isRegex_(isRegex)
|
||||||
|
, isCaseSensitive_(isCaseSensitive)
|
||||||
|
, soundUrl_(soundUrl)
|
||||||
|
, regex_(isRegex_ ? pattern
|
||||||
|
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
||||||
|
QRegularExpression::UseUnicodePropertiesOption |
|
||||||
|
(isCaseSensitive_ ? QRegularExpression::NoPatternOption
|
||||||
|
: QRegularExpression::CaseInsensitiveOption))
|
||||||
|
{
|
||||||
|
this->color_ = std::make_shared<QColor>(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
HighlightPhrase::HighlightPhrase(const QString &pattern, bool hasAlert,
|
||||||
|
bool hasSound, bool isRegex,
|
||||||
|
bool isCaseSensitive, const QString &soundUrl,
|
||||||
|
std::shared_ptr<QColor> color)
|
||||||
|
: pattern_(pattern)
|
||||||
|
, hasAlert_(hasAlert)
|
||||||
|
, hasSound_(hasSound)
|
||||||
|
, isRegex_(isRegex)
|
||||||
|
, isCaseSensitive_(isCaseSensitive)
|
||||||
|
, soundUrl_(soundUrl)
|
||||||
|
, color_(color)
|
||||||
|
, regex_(isRegex_ ? pattern
|
||||||
|
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
||||||
|
QRegularExpression::UseUnicodePropertiesOption |
|
||||||
|
(isCaseSensitive_ ? QRegularExpression::NoPatternOption
|
||||||
|
: QRegularExpression::CaseInsensitiveOption))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString &HighlightPhrase::getPattern() const
|
||||||
|
{
|
||||||
|
return this->pattern_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HighlightPhrase::hasAlert() const
|
||||||
|
{
|
||||||
|
return this->hasAlert_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HighlightPhrase::hasSound() const
|
||||||
|
{
|
||||||
|
return this->hasSound_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HighlightPhrase::hasCustomSound() const
|
||||||
|
{
|
||||||
|
return !this->soundUrl_.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HighlightPhrase::isRegex() const
|
||||||
|
{
|
||||||
|
return this->isRegex_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HighlightPhrase::isValid() const
|
||||||
|
{
|
||||||
|
return !this->pattern_.isEmpty() && this->regex_.isValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HighlightPhrase::isMatch(const QString &subject) const
|
||||||
|
{
|
||||||
|
return this->isValid() && this->regex_.match(subject).hasMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HighlightPhrase::isCaseSensitive() const
|
||||||
|
{
|
||||||
|
return this->isCaseSensitive_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QUrl &HighlightPhrase::getSoundUrl() const
|
||||||
|
{
|
||||||
|
return this->soundUrl_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::shared_ptr<QColor> HighlightPhrase::getColor() const
|
||||||
|
{
|
||||||
|
return this->color_;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "providers/colors/ColorProvider.hpp"
|
||||||
#include "util/RapidJsonSerializeQString.hpp"
|
#include "util/RapidJsonSerializeQString.hpp"
|
||||||
#include "util/RapidjsonHelpers.hpp"
|
#include "util/RapidjsonHelpers.hpp"
|
||||||
|
|
||||||
|
@ -12,70 +13,74 @@ namespace chatterino {
|
||||||
class HighlightPhrase
|
class HighlightPhrase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
bool operator==(const HighlightPhrase &other) const
|
bool operator==(const HighlightPhrase &other) const;
|
||||||
{
|
|
||||||
return std::tie(this->pattern_, this->sound_, this->alert_,
|
|
||||||
this->isRegex_, this->caseSensitive_) ==
|
|
||||||
std::tie(other.pattern_, other.sound_, other.alert_,
|
|
||||||
other.isRegex_, other.caseSensitive_);
|
|
||||||
}
|
|
||||||
|
|
||||||
HighlightPhrase(const QString &pattern, bool alert, bool sound,
|
/**
|
||||||
bool isRegex, bool caseSensitive)
|
* @brief Create a new HighlightPhrase.
|
||||||
: pattern_(pattern)
|
*
|
||||||
, alert_(alert)
|
* Use this constructor when creating a new HighlightPhrase.
|
||||||
, sound_(sound)
|
*/
|
||||||
, isRegex_(isRegex)
|
HighlightPhrase(const QString &pattern, bool hasAlert, bool hasSound,
|
||||||
, caseSensitive_(caseSensitive)
|
bool isRegex, bool isCaseSensitive, const QString &soundUrl,
|
||||||
, regex_(
|
QColor color);
|
||||||
isRegex_ ? pattern
|
|
||||||
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
|
||||||
QRegularExpression::UseUnicodePropertiesOption |
|
|
||||||
(caseSensitive_ ? QRegularExpression::NoPatternOption
|
|
||||||
: QRegularExpression::CaseInsensitiveOption))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString &getPattern() const
|
/**
|
||||||
{
|
* @brief Create a new HighlightPhrase.
|
||||||
return this->pattern_;
|
*
|
||||||
}
|
* Use this constructor when updating an existing HighlightPhrase's color.
|
||||||
bool getAlert() const
|
*/
|
||||||
{
|
HighlightPhrase(const QString &pattern, bool hasAlert, bool hasSound,
|
||||||
return this->alert_;
|
bool isRegex, bool isCaseSensitive, const QString &soundUrl,
|
||||||
}
|
std::shared_ptr<QColor> color);
|
||||||
bool getSound() const
|
|
||||||
{
|
|
||||||
return this->sound_;
|
|
||||||
}
|
|
||||||
bool isRegex() const
|
|
||||||
{
|
|
||||||
return this->isRegex_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isValid() const
|
const QString &getPattern() const;
|
||||||
{
|
bool hasAlert() const;
|
||||||
return !this->pattern_.isEmpty() && this->regex_.isValid();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isMatch(const QString &subject) const
|
/**
|
||||||
{
|
* @brief Check if this highlight phrase should play a sound when
|
||||||
return this->isValid() && this->regex_.match(subject).hasMatch();
|
* triggered.
|
||||||
}
|
*
|
||||||
|
* In distinction from `HighlightPhrase::hasCustomSound`, this method only
|
||||||
|
* checks whether or not ANY sound should be played when the phrase is
|
||||||
|
* triggered.
|
||||||
|
*
|
||||||
|
* To check whether a custom sound is set, use
|
||||||
|
* `HighlightPhrase::hasCustomSound` instead.
|
||||||
|
*
|
||||||
|
* @return true, if this highlight phrase should play a sound when
|
||||||
|
* triggered, false otherwise
|
||||||
|
*/
|
||||||
|
bool hasSound() const;
|
||||||
|
|
||||||
bool isCaseSensitive() const
|
/**
|
||||||
{
|
* @brief Check if this highlight phrase has a custom sound set.
|
||||||
return this->caseSensitive_;
|
*
|
||||||
}
|
* Note that this method only checks whether the path to the custom sound
|
||||||
|
* is not empty. It does not check whether the file still exists, is a
|
||||||
|
* sound file, or anything else.
|
||||||
|
*
|
||||||
|
* @return true, if the custom sound file path is not empty, false otherwise
|
||||||
|
*/
|
||||||
|
bool hasCustomSound() const;
|
||||||
|
|
||||||
|
bool isRegex() const;
|
||||||
|
bool isValid() const;
|
||||||
|
bool isMatch(const QString &subject) const;
|
||||||
|
bool isCaseSensitive() const;
|
||||||
|
const QUrl &getSoundUrl() const;
|
||||||
|
const std::shared_ptr<QColor> getColor() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString pattern_;
|
QString pattern_;
|
||||||
bool alert_;
|
bool hasAlert_;
|
||||||
bool sound_;
|
bool hasSound_;
|
||||||
bool isRegex_;
|
bool isRegex_;
|
||||||
bool caseSensitive_;
|
bool isCaseSensitive_;
|
||||||
|
QUrl soundUrl_;
|
||||||
|
std::shared_ptr<QColor> color_;
|
||||||
QRegularExpression regex_;
|
QRegularExpression regex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
||||||
namespace pajlada {
|
namespace pajlada {
|
||||||
|
@ -88,10 +93,13 @@ struct Serialize<chatterino::HighlightPhrase> {
|
||||||
rapidjson::Value ret(rapidjson::kObjectType);
|
rapidjson::Value ret(rapidjson::kObjectType);
|
||||||
|
|
||||||
chatterino::rj::set(ret, "pattern", value.getPattern(), a);
|
chatterino::rj::set(ret, "pattern", value.getPattern(), a);
|
||||||
chatterino::rj::set(ret, "alert", value.getAlert(), a);
|
chatterino::rj::set(ret, "alert", value.hasAlert(), a);
|
||||||
chatterino::rj::set(ret, "sound", value.getSound(), a);
|
chatterino::rj::set(ret, "sound", value.hasSound(), a);
|
||||||
chatterino::rj::set(ret, "regex", value.isRegex(), a);
|
chatterino::rj::set(ret, "regex", value.isRegex(), a);
|
||||||
chatterino::rj::set(ret, "case", value.isCaseSensitive(), a);
|
chatterino::rj::set(ret, "case", value.isCaseSensitive(), a);
|
||||||
|
chatterino::rj::set(ret, "soundUrl", value.getSoundUrl().toString(), a);
|
||||||
|
chatterino::rj::set(ret, "color",
|
||||||
|
value.getColor()->name(QColor::HexArgb), a);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -104,23 +112,30 @@ struct Deserialize<chatterino::HighlightPhrase> {
|
||||||
if (!value.IsObject())
|
if (!value.IsObject())
|
||||||
{
|
{
|
||||||
return chatterino::HighlightPhrase(QString(), true, false, false,
|
return chatterino::HighlightPhrase(QString(), true, false, false,
|
||||||
false);
|
false, "", QColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString _pattern;
|
QString _pattern;
|
||||||
bool _alert = true;
|
bool _hasAlert = true;
|
||||||
bool _sound = false;
|
bool _hasSound = false;
|
||||||
bool _isRegex = false;
|
bool _isRegex = false;
|
||||||
bool _caseSensitive = false;
|
bool _isCaseSensitive = false;
|
||||||
|
QString _soundUrl;
|
||||||
|
QString encodedColor;
|
||||||
|
|
||||||
chatterino::rj::getSafe(value, "pattern", _pattern);
|
chatterino::rj::getSafe(value, "pattern", _pattern);
|
||||||
chatterino::rj::getSafe(value, "alert", _alert);
|
chatterino::rj::getSafe(value, "alert", _hasAlert);
|
||||||
chatterino::rj::getSafe(value, "sound", _sound);
|
chatterino::rj::getSafe(value, "sound", _hasSound);
|
||||||
chatterino::rj::getSafe(value, "regex", _isRegex);
|
chatterino::rj::getSafe(value, "regex", _isRegex);
|
||||||
chatterino::rj::getSafe(value, "case", _caseSensitive);
|
chatterino::rj::getSafe(value, "case", _isCaseSensitive);
|
||||||
|
chatterino::rj::getSafe(value, "soundUrl", _soundUrl);
|
||||||
|
chatterino::rj::getSafe(value, "color", encodedColor);
|
||||||
|
|
||||||
return chatterino::HighlightPhrase(_pattern, _alert, _sound, _isRegex,
|
auto _color = QColor(encodedColor);
|
||||||
_caseSensitive);
|
|
||||||
|
return chatterino::HighlightPhrase(_pattern, _hasAlert, _hasSound,
|
||||||
|
_isRegex, _isCaseSensitive,
|
||||||
|
_soundUrl, _color);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "UserHighlightModel.hpp"
|
#include "UserHighlightModel.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
|
#include "controllers/highlights/HighlightModel.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "util/StandardItemHelper.hpp"
|
#include "util/StandardItemHelper.hpp"
|
||||||
|
|
||||||
|
@ -8,7 +9,7 @@ namespace chatterino {
|
||||||
|
|
||||||
// commandmodel
|
// commandmodel
|
||||||
UserHighlightModel::UserHighlightModel(QObject *parent)
|
UserHighlightModel::UserHighlightModel(QObject *parent)
|
||||||
: SignalVectorModel<HighlightPhrase>(5, parent)
|
: SignalVectorModel<HighlightPhrase>(7, parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,24 +17,37 @@ UserHighlightModel::UserHighlightModel(QObject *parent)
|
||||||
HighlightPhrase UserHighlightModel::getItemFromRow(
|
HighlightPhrase UserHighlightModel::getItemFromRow(
|
||||||
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
|
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
|
||||||
{
|
{
|
||||||
// key, regex
|
using Column = HighlightModel::Column;
|
||||||
|
|
||||||
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
|
// In order for old messages to update their highlight color, we need to
|
||||||
row[1]->data(Qt::CheckStateRole).toBool(),
|
// update the highlight color here.
|
||||||
row[2]->data(Qt::CheckStateRole).toBool(),
|
auto highlightColor = original.getColor();
|
||||||
row[3]->data(Qt::CheckStateRole).toBool(),
|
*highlightColor =
|
||||||
row[4]->data(Qt::CheckStateRole).toBool()};
|
row[Column::Color]->data(Qt::DecorationRole).value<QColor>();
|
||||||
|
|
||||||
|
return HighlightPhrase{
|
||||||
|
row[Column::Pattern]->data(Qt::DisplayRole).toString(),
|
||||||
|
row[Column::FlashTaskbar]->data(Qt::CheckStateRole).toBool(),
|
||||||
|
row[Column::PlaySound]->data(Qt::CheckStateRole).toBool(),
|
||||||
|
row[Column::UseRegex]->data(Qt::CheckStateRole).toBool(),
|
||||||
|
row[Column::CaseSensitive]->data(Qt::CheckStateRole).toBool(),
|
||||||
|
row[Column::SoundPath]->data(Qt::UserRole).toString(),
|
||||||
|
highlightColor};
|
||||||
}
|
}
|
||||||
|
|
||||||
// row into vector item
|
// row into vector item
|
||||||
void UserHighlightModel::getRowFromItem(const HighlightPhrase &item,
|
void UserHighlightModel::getRowFromItem(const HighlightPhrase &item,
|
||||||
std::vector<QStandardItem *> &row)
|
std::vector<QStandardItem *> &row)
|
||||||
{
|
{
|
||||||
setStringItem(row[0], item.getPattern());
|
using Column = HighlightModel::Column;
|
||||||
setBoolItem(row[1], item.getAlert());
|
|
||||||
setBoolItem(row[2], item.getSound());
|
setStringItem(row[Column::Pattern], item.getPattern());
|
||||||
setBoolItem(row[3], item.isRegex());
|
setBoolItem(row[Column::FlashTaskbar], item.hasAlert());
|
||||||
setBoolItem(row[4], item.isCaseSensitive());
|
setBoolItem(row[Column::PlaySound], item.hasSound());
|
||||||
|
setBoolItem(row[Column::UseRegex], item.isRegex());
|
||||||
|
setBoolItem(row[Column::CaseSensitive], item.isCaseSensitive());
|
||||||
|
setFilePathItem(row[Column::SoundPath], item.getSoundUrl());
|
||||||
|
setColorItem(row[Column::Color], *item.getColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
#include "controllers/notifications/NotificationModel.hpp"
|
#include "controllers/notifications/NotificationModel.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "providers/twitch/TwitchApi.hpp"
|
#include "providers/twitch/TwitchApi.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
#include "singletons/Toasts.hpp"
|
#include "singletons/Toasts.hpp"
|
||||||
|
@ -147,12 +146,13 @@ void NotificationController::getFakeTwitchChannelLiveStatus(
|
||||||
TwitchApi::findUserId(channelName, [channelName, this](QString roomID) {
|
TwitchApi::findUserId(channelName, [channelName, this](QString roomID) {
|
||||||
if (roomID.isEmpty())
|
if (roomID.isEmpty())
|
||||||
{
|
{
|
||||||
log("[TwitchChannel:{}] Refreshing live status (Missing ID)",
|
qDebug() << "[TwitchChannel" << channelName
|
||||||
channelName);
|
<< "] Refreshing live status (Missing ID)";
|
||||||
removeFakeChannel(channelName);
|
removeFakeChannel(channelName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log("[TwitchChannel:{}] Refreshing live status", channelName);
|
qDebug() << "[TwitchChannel" << channelName
|
||||||
|
<< "] Refreshing live status";
|
||||||
|
|
||||||
QString url("https://api.twitch.tv/kraken/streams/" + roomID);
|
QString url("https://api.twitch.tv/kraken/streams/" + roomID);
|
||||||
NetworkRequest::twitchRequest(url)
|
NetworkRequest::twitchRequest(url)
|
||||||
|
@ -160,15 +160,15 @@ void NotificationController::getFakeTwitchChannelLiveStatus(
|
||||||
rapidjson::Document document = result.parseRapidJson();
|
rapidjson::Document document = result.parseRapidJson();
|
||||||
if (!document.IsObject())
|
if (!document.IsObject())
|
||||||
{
|
{
|
||||||
log("[TwitchChannel:refreshLiveStatus]root is not an "
|
qDebug() << "[TwitchChannel:refreshLiveStatus] root is not "
|
||||||
"object");
|
"an object";
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!document.HasMember("stream"))
|
if (!document.HasMember("stream"))
|
||||||
{
|
{
|
||||||
log("[TwitchChannel:refreshLiveStatus] Missing stream in "
|
qDebug() << "[TwitchChannel:refreshLiveStatus] Missing "
|
||||||
"root");
|
"stream in root";
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,8 @@ BenchmarkGuard::BenchmarkGuard(const QString &_name)
|
||||||
|
|
||||||
BenchmarkGuard::~BenchmarkGuard()
|
BenchmarkGuard::~BenchmarkGuard()
|
||||||
{
|
{
|
||||||
log("{} {} ms", this->name_, float(timer_.nsecsElapsed()) / 1000000.0f);
|
qDebug() << this->name_ << float(timer_.nsecsElapsed()) / 1000000.0f
|
||||||
|
<< "ms";
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal BenchmarkGuard::getElapsedMs()
|
qreal BenchmarkGuard::getElapsedMs()
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "debug/Log.hpp"
|
#include <QDebug>
|
||||||
|
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "util/Helpers.hpp"
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QTime>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
inline void log(const std::string &formatString, Args &&... args)
|
|
||||||
{
|
|
||||||
qDebug().noquote() << QTime::currentTime().toString("hh:mm:ss.zzz")
|
|
||||||
<< fS(formatString, std::forward<Args>(args)...).c_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
inline void log(const char *formatString, Args &&... args)
|
|
||||||
{
|
|
||||||
log(std::string(formatString), std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
inline void log(const QString &formatString, Args &&... args)
|
|
||||||
{
|
|
||||||
log(formatString.toStdString(), std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
|
@ -14,7 +14,6 @@
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "debug/AssertInGuiThread.hpp"
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
#include "debug/Benchmark.hpp"
|
#include "debug/Benchmark.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
#include "util/DebugCount.hpp"
|
#include "util/DebugCount.hpp"
|
||||||
|
@ -108,8 +107,8 @@ namespace detail {
|
||||||
|
|
||||||
if (reader.imageCount() == 0)
|
if (reader.imageCount() == 0)
|
||||||
{
|
{
|
||||||
log("Error while reading image {}: '{}'", url.string,
|
qDebug() << "Error while reading image" << url.string << ": '"
|
||||||
reader.errorString());
|
<< reader.errorString() << "'";
|
||||||
return frames;
|
return frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,8 +126,8 @@ namespace detail {
|
||||||
|
|
||||||
if (frames.size() == 0)
|
if (frames.size() == 0)
|
||||||
{
|
{
|
||||||
log("Error while reading image {}: '{}'", url.string,
|
qDebug() << "Error while reading image" << url.string << ": '"
|
||||||
reader.errorString());
|
<< reader.errorString() << "'";
|
||||||
}
|
}
|
||||||
|
|
||||||
return frames;
|
return frames;
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
|
|
||||||
|
#include "Application.hpp"
|
||||||
#include "MessageElement.hpp"
|
#include "MessageElement.hpp"
|
||||||
#include "providers/twitch/PubsubActions.hpp"
|
#include "providers/twitch/PubsubActions.hpp"
|
||||||
|
#include "singletons/Theme.hpp"
|
||||||
#include "util/DebugCount.hpp"
|
#include "util/DebugCount.hpp"
|
||||||
#include "util/IrcHelpers.hpp"
|
#include "util/IrcHelpers.hpp"
|
||||||
|
|
||||||
|
@ -24,11 +27,13 @@ SBHighlight Message::getScrollBarHighlight() const
|
||||||
if (this->flags.has(MessageFlag::Highlighted) ||
|
if (this->flags.has(MessageFlag::Highlighted) ||
|
||||||
this->flags.has(MessageFlag::HighlightedWhisper))
|
this->flags.has(MessageFlag::HighlightedWhisper))
|
||||||
{
|
{
|
||||||
return SBHighlight(SBHighlight::Highlight);
|
return SBHighlight(this->highlightColor);
|
||||||
}
|
}
|
||||||
else if (this->flags.has(MessageFlag::Subscription))
|
else if (this->flags.has(MessageFlag::Subscription) &&
|
||||||
|
getSettings()->enableSubHighlight)
|
||||||
{
|
{
|
||||||
return SBHighlight(SBHighlight::Subscription);
|
return SBHighlight(
|
||||||
|
ColorProvider::instance().color(ColorType::Subscription));
|
||||||
}
|
}
|
||||||
return SBHighlight();
|
return SBHighlight();
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ enum class MessageFlag : uint32_t {
|
||||||
Whisper = (1 << 16),
|
Whisper = (1 << 16),
|
||||||
HighlightedWhisper = (1 << 17),
|
HighlightedWhisper = (1 << 17),
|
||||||
Debug = (1 << 18),
|
Debug = (1 << 18),
|
||||||
|
Similar = (1 << 19),
|
||||||
};
|
};
|
||||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ struct Message : boost::noncopyable {
|
||||||
QString displayName;
|
QString displayName;
|
||||||
QString localizedName;
|
QString localizedName;
|
||||||
QString timeoutUser;
|
QString timeoutUser;
|
||||||
|
std::shared_ptr<QColor> highlightColor;
|
||||||
uint32_t count = 1;
|
uint32_t count = 1;
|
||||||
std::vector<std::unique_ptr<MessageElement>> elements;
|
std::vector<std::unique_ptr<MessageElement>> elements;
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,19 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
QColor blendColors(const QColor &base, const QColor &apply)
|
||||||
|
{
|
||||||
|
const qreal &alpha = apply.alphaF();
|
||||||
|
QColor result;
|
||||||
|
result.setRgbF(base.redF() * (1 - alpha) + apply.redF() * alpha,
|
||||||
|
base.greenF() * (1 - alpha) + apply.greenF() * alpha,
|
||||||
|
base.blueF() * (1 - alpha) + apply.blueF() * alpha);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
MessageLayout::MessageLayout(MessagePtr message)
|
MessageLayout::MessageLayout(MessagePtr message)
|
||||||
: message_(message)
|
: message_(message)
|
||||||
, container_(std::make_shared<MessageLayoutContainer>())
|
, container_(std::make_shared<MessageLayoutContainer>())
|
||||||
|
@ -128,6 +141,12 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getSettings()->hideSimilar &&
|
||||||
|
this->message_->flags.has(MessageFlag::Similar))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
element->addToContainer(*this->container_, flags);
|
element->addToContainer(*this->container_, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,21 +268,33 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
|
||||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||||
|
|
||||||
// draw background
|
// draw background
|
||||||
QColor backgroundColor = app->themes->messages.backgrounds.regular;
|
QColor backgroundColor = [this, &app] {
|
||||||
|
if (getSettings()->alternateMessages.getValue() &&
|
||||||
|
this->flags.has(MessageLayoutFlag::AlternateBackground))
|
||||||
|
{
|
||||||
|
return app->themes->messages.backgrounds.alternate;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return app->themes->messages.backgrounds.regular;
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
if ((this->message_->flags.has(MessageFlag::Highlighted) ||
|
if ((this->message_->flags.has(MessageFlag::Highlighted) ||
|
||||||
this->message_->flags.has(MessageFlag::HighlightedWhisper)) &&
|
this->message_->flags.has(MessageFlag::HighlightedWhisper)) &&
|
||||||
!this->flags.has(MessageLayoutFlag::IgnoreHighlights))
|
!this->flags.has(MessageLayoutFlag::IgnoreHighlights))
|
||||||
{
|
{
|
||||||
backgroundColor = app->themes->messages.backgrounds.highlighted;
|
// Blend highlight color with usual background color
|
||||||
|
backgroundColor =
|
||||||
|
blendColors(backgroundColor, *this->message_->highlightColor);
|
||||||
}
|
}
|
||||||
else if (this->message_->flags.has(MessageFlag::Subscription))
|
else if (this->message_->flags.has(MessageFlag::Subscription) &&
|
||||||
|
getSettings()->enableSubHighlight)
|
||||||
{
|
{
|
||||||
backgroundColor = app->themes->messages.backgrounds.subscription;
|
// Blend highlight color with usual background color
|
||||||
}
|
backgroundColor = blendColors(
|
||||||
else if (getSettings()->alternateMessages.getValue() &&
|
backgroundColor,
|
||||||
this->flags.has(MessageLayoutFlag::AlternateBackground))
|
*ColorProvider::instance().color(ColorType::Subscription));
|
||||||
{
|
|
||||||
backgroundColor = app->themes->messages.backgrounds.alternate;
|
|
||||||
}
|
}
|
||||||
else if (this->message_->flags.has(MessageFlag::AutoMod))
|
else if (this->message_->flags.has(MessageFlag::AutoMod))
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/Emote.hpp"
|
#include "messages/Emote.hpp"
|
||||||
#include "messages/Image.hpp"
|
#include "messages/Image.hpp"
|
||||||
#include "messages/ImageSet.hpp"
|
#include "messages/ImageSet.hpp"
|
||||||
|
|
124
src/providers/colors/ColorProvider.cpp
Normal file
124
src/providers/colors/ColorProvider.cpp
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
#include "providers/colors/ColorProvider.hpp"
|
||||||
|
|
||||||
|
#include "controllers/highlights/HighlightController.hpp"
|
||||||
|
#include "singletons/Theme.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
const ColorProvider &ColorProvider::instance()
|
||||||
|
{
|
||||||
|
static ColorProvider instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorProvider::ColorProvider()
|
||||||
|
: typeColorMap_()
|
||||||
|
, defaultColors_()
|
||||||
|
{
|
||||||
|
this->initTypeColorMap();
|
||||||
|
this->initDefaultColors();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::shared_ptr<QColor> ColorProvider::color(ColorType type) const
|
||||||
|
{
|
||||||
|
return this->typeColorMap_.at(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorProvider::updateColor(ColorType type, QColor color)
|
||||||
|
{
|
||||||
|
auto colorPtr = this->typeColorMap_.at(type);
|
||||||
|
*colorPtr = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSet<QColor> ColorProvider::recentColors() const
|
||||||
|
{
|
||||||
|
QSet<QColor> retVal;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Currently, only colors used in highlight phrases are considered. This
|
||||||
|
* may change at any point in the future.
|
||||||
|
*/
|
||||||
|
for (auto phrase : getApp()->highlights->phrases)
|
||||||
|
{
|
||||||
|
retVal.insert(*phrase.getColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto userHl : getApp()->highlights->highlightedUsers)
|
||||||
|
{
|
||||||
|
retVal.insert(*userHl.getColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert preset highlight colors
|
||||||
|
retVal.insert(*this->color(ColorType::SelfHighlight));
|
||||||
|
retVal.insert(*this->color(ColorType::Subscription));
|
||||||
|
retVal.insert(*this->color(ColorType::Whisper));
|
||||||
|
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<QColor> &ColorProvider::defaultColors() const
|
||||||
|
{
|
||||||
|
return this->defaultColors_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorProvider::initTypeColorMap()
|
||||||
|
{
|
||||||
|
// Read settings for custom highlight colors and save them in map.
|
||||||
|
// If no custom values can be found, set up default values instead.
|
||||||
|
auto backgrounds = getApp()->themes->messages.backgrounds;
|
||||||
|
|
||||||
|
QString customColor = getSettings()->selfHighlightColor;
|
||||||
|
if (QColor(customColor).isValid())
|
||||||
|
{
|
||||||
|
this->typeColorMap_.insert(
|
||||||
|
{ColorType::SelfHighlight, std::make_shared<QColor>(customColor)});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->typeColorMap_.insert(
|
||||||
|
{ColorType::SelfHighlight,
|
||||||
|
std::make_shared<QColor>(backgrounds.highlighted)});
|
||||||
|
}
|
||||||
|
|
||||||
|
customColor = getSettings()->subHighlightColor;
|
||||||
|
if (QColor(customColor).isValid())
|
||||||
|
{
|
||||||
|
this->typeColorMap_.insert(
|
||||||
|
{ColorType::Subscription, std::make_shared<QColor>(customColor)});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->typeColorMap_.insert(
|
||||||
|
{ColorType::Subscription,
|
||||||
|
std::make_shared<QColor>(backgrounds.subscription)});
|
||||||
|
}
|
||||||
|
|
||||||
|
customColor = getSettings()->whisperHighlightColor;
|
||||||
|
if (QColor(customColor).isValid())
|
||||||
|
{
|
||||||
|
this->typeColorMap_.insert(
|
||||||
|
{ColorType::Whisper, std::make_shared<QColor>(customColor)});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->typeColorMap_.insert(
|
||||||
|
{ColorType::Whisper,
|
||||||
|
std::make_shared<QColor>(backgrounds.highlighted)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorProvider::initDefaultColors()
|
||||||
|
{
|
||||||
|
// Init default colors
|
||||||
|
this->defaultColors_.emplace_back(31, 141, 43, 127); // Green-ish
|
||||||
|
this->defaultColors_.emplace_back(28, 126, 141, 127); // Blue-ish
|
||||||
|
this->defaultColors_.emplace_back(136, 141, 49, 127); // Golden-ish
|
||||||
|
this->defaultColors_.emplace_back(143, 48, 24, 127); // Red-ish
|
||||||
|
this->defaultColors_.emplace_back(28, 141, 117, 127); // Cyan-ish
|
||||||
|
|
||||||
|
auto backgrounds = getApp()->themes->messages.backgrounds;
|
||||||
|
this->defaultColors_.push_back(backgrounds.highlighted);
|
||||||
|
this->defaultColors_.push_back(backgrounds.subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
53
src/providers/colors/ColorProvider.hpp
Normal file
53
src/providers/colors/ColorProvider.hpp
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
enum class ColorType { SelfHighlight, Subscription, Whisper };
|
||||||
|
|
||||||
|
class ColorProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const ColorProvider &instance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a std::shared_ptr to the color of the requested ColorType.
|
||||||
|
*
|
||||||
|
* If a custom color has been set for the requested ColorType, it is
|
||||||
|
* returned. If no custom color exists for the type, a default color is
|
||||||
|
* returned.
|
||||||
|
*
|
||||||
|
* We need to do this in order to be able to dynamically update the colors
|
||||||
|
* of already parsed predefined (self highlights, subscriptions,
|
||||||
|
* and whispers) highlights.
|
||||||
|
*/
|
||||||
|
const std::shared_ptr<QColor> color(ColorType type) const;
|
||||||
|
|
||||||
|
void updateColor(ColorType type, QColor color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a set of recently used colors used anywhere in Chatterino.
|
||||||
|
*/
|
||||||
|
QSet<QColor> recentColors() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return a vector of colors that are good defaults for use
|
||||||
|
* throughout the program.
|
||||||
|
*/
|
||||||
|
const std::vector<QColor> &defaultColors() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ColorProvider();
|
||||||
|
|
||||||
|
void initTypeColorMap();
|
||||||
|
void initDefaultColors();
|
||||||
|
|
||||||
|
std::unordered_map<ColorType, std::shared_ptr<QColor>> typeColorMap_;
|
||||||
|
std::vector<QColor> defaultColors_;
|
||||||
|
};
|
||||||
|
} // namespace chatterino
|
||||||
|
|
||||||
|
// Adapted from Qt example: https://doc.qt.io/qt-5/qhash.html#qhash
|
||||||
|
inline uint qHash(const QColor &key)
|
||||||
|
{
|
||||||
|
return qHash(key.name(QColor::HexArgb));
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
#include "providers/emoji/Emojis.hpp"
|
#include "providers/emoji/Emojis.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/Emote.hpp"
|
#include "messages/Emote.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
|
||||||
|
@ -132,8 +131,9 @@ void Emojis::loadEmojis()
|
||||||
|
|
||||||
if (result.Code() != rapidjson::kParseErrorNone)
|
if (result.Code() != rapidjson::kParseErrorNone)
|
||||||
{
|
{
|
||||||
log("JSON parse error: {} ({})",
|
qDebug() << "JSON parse error:"
|
||||||
rapidjson::GetParseError_En(result.Code()), result.Offset());
|
<< rapidjson::GetParseError_En(result.Code()) << "("
|
||||||
|
<< result.Offset() << ")";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,8 +165,8 @@ void Emojis::loadEmojis()
|
||||||
auto toneNameIt = toneNames.find(tone);
|
auto toneNameIt = toneNames.find(tone);
|
||||||
if (toneNameIt == toneNames.end())
|
if (toneNameIt == toneNames.end())
|
||||||
{
|
{
|
||||||
log("Tone with key {} does not exist in tone names map",
|
qDebug() << "Tone with key" << tone.c_str()
|
||||||
tone);
|
<< "does not exist in tone names map";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/Emote.hpp"
|
#include "messages/Emote.hpp"
|
||||||
#include "messages/Image.hpp"
|
#include "messages/Image.hpp"
|
||||||
|
|
||||||
|
@ -186,7 +185,8 @@ void FfzEmotes::loadChannel(
|
||||||
const QString &channelId, std::function<void(EmoteMap &&)> emoteCallback,
|
const QString &channelId, std::function<void(EmoteMap &&)> emoteCallback,
|
||||||
std::function<void(boost::optional<EmotePtr>)> modBadgeCallback)
|
std::function<void(boost::optional<EmotePtr>)> modBadgeCallback)
|
||||||
{
|
{
|
||||||
log("[FFZEmotes] Reload FFZ Channel Emotes for channel {}\n", channelId);
|
qDebug() << "[FFZEmotes] Reload FFZ Channel Emotes for channel"
|
||||||
|
<< channelId;
|
||||||
|
|
||||||
NetworkRequest("https://api.frankerfacez.com/v1/room/id/" + channelId)
|
NetworkRequest("https://api.frankerfacez.com/v1/room/id/" + channelId)
|
||||||
|
|
||||||
|
@ -211,13 +211,13 @@ void FfzEmotes::loadChannel(
|
||||||
else if (result.status() == NetworkResult::timedoutStatus)
|
else if (result.status() == NetworkResult::timedoutStatus)
|
||||||
{
|
{
|
||||||
// TODO: Auto retry in case of a timeout, with a delay
|
// TODO: Auto retry in case of a timeout, with a delay
|
||||||
log("Fetching FFZ emotes for channel {} failed due to timeout",
|
qDebug() << "Fetching FFZ emotes for channel" << channelId
|
||||||
channelId);
|
<< "failed due to timeout";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
log("Error fetching FFZ emotes for channel {}, error {}",
|
qDebug() << "Error fetching FFZ emotes for channel" << channelId
|
||||||
channelId, result.status());
|
<< ", error" << result.status();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
#include "common/Channel.hpp"
|
#include "common/Channel.hpp"
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/LimitedQueueSnapshot.hpp"
|
#include "messages/LimitedQueueSnapshot.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
|
@ -66,7 +65,7 @@ AbstractIrcServer::AbstractIrcServer()
|
||||||
|
|
||||||
if (!this->readConnection_->isConnected())
|
if (!this->readConnection_->isConnected())
|
||||||
{
|
{
|
||||||
log("Trying to reconnect... {}", this->falloffCounter_);
|
qDebug() << "Trying to reconnect..." << this->falloffCounter_;
|
||||||
this->connect();
|
this->connect();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -159,23 +158,24 @@ ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName)
|
||||||
}
|
}
|
||||||
|
|
||||||
this->channels.insert(channelName, chan);
|
this->channels.insert(channelName, chan);
|
||||||
this->connections_.emplace_back(chan->destroyed.connect([this,
|
this->connections_.emplace_back(
|
||||||
channelName] {
|
chan->destroyed.connect([this, channelName] {
|
||||||
// fourtf: issues when the server itself is destroyed
|
// fourtf: issues when the server itself is destroyed
|
||||||
|
|
||||||
log("[AbstractIrcServer::addChannel] {} was destroyed", channelName);
|
qDebug() << "[AbstractIrcServer::addChannel]" << channelName
|
||||||
this->channels.remove(channelName);
|
<< "was destroyed";
|
||||||
|
this->channels.remove(channelName);
|
||||||
|
|
||||||
if (this->readConnection_)
|
if (this->readConnection_)
|
||||||
{
|
{
|
||||||
this->readConnection_->sendRaw("PART #" + channelName);
|
this->readConnection_->sendRaw("PART #" + channelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->writeConnection_ && this->hasSeparateWriteConnection())
|
if (this->writeConnection_ && this->hasSeparateWriteConnection())
|
||||||
{
|
{
|
||||||
this->writeConnection_->sendRaw("PART #" + channelName);
|
this->writeConnection_->sendRaw("PART #" + channelName);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// join irc channel
|
// join irc channel
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
#include "IrcConnection2.hpp"
|
#include "IrcConnection2.hpp"
|
||||||
|
|
||||||
|
#include "common/Version.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const auto payload = QString("chatterino/" CHATTERINO_VERSION);
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
IrcConnection::IrcConnection(QObject *parent)
|
IrcConnection::IrcConnection(QObject *parent)
|
||||||
: Communi::IrcConnection(parent)
|
: Communi::IrcConnection(parent)
|
||||||
{
|
{
|
||||||
|
@ -11,10 +19,17 @@ IrcConnection::IrcConnection(QObject *parent)
|
||||||
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
|
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
|
||||||
if (this->isConnected())
|
if (this->isConnected())
|
||||||
{
|
{
|
||||||
|
if (this->waitingForPong_.load())
|
||||||
|
{
|
||||||
|
// Not sending another ping as we haven't received the matching pong yet
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this->recentlyReceivedMessage_.load())
|
if (!this->recentlyReceivedMessage_.load())
|
||||||
{
|
{
|
||||||
this->sendRaw("PING chatterino/ping");
|
this->sendRaw("PING " + payload);
|
||||||
this->reconnectTimer_.start();
|
this->reconnectTimer_.start();
|
||||||
|
this->waitingForPong_ = true;
|
||||||
}
|
}
|
||||||
this->recentlyReceivedMessage_ = false;
|
this->recentlyReceivedMessage_ = false;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +45,17 @@ IrcConnection::IrcConnection(QObject *parent)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QObject::connect(this, &Communi::IrcConnection::connected, this,
|
||||||
|
[this] { this->waitingForPong_ = false; });
|
||||||
|
|
||||||
|
QObject::connect(this, &Communi::IrcConnection::pongMessageReceived,
|
||||||
|
[this](Communi::IrcPongMessage *message) {
|
||||||
|
if (message->argument() == payload)
|
||||||
|
{
|
||||||
|
this->waitingForPong_ = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
QObject::connect(this, &Communi::IrcConnection::messageReceived,
|
QObject::connect(this, &Communi::IrcConnection::messageReceived,
|
||||||
[this](Communi::IrcMessage *) {
|
[this](Communi::IrcMessage *) {
|
||||||
this->recentlyReceivedMessage_ = true;
|
this->recentlyReceivedMessage_ = true;
|
||||||
|
|
|
@ -18,6 +18,9 @@ private:
|
||||||
QTimer pingTimer_;
|
QTimer pingTimer_;
|
||||||
QTimer reconnectTimer_;
|
QTimer reconnectTimer_;
|
||||||
std::atomic<bool> recentlyReceivedMessage_{true};
|
std::atomic<bool> recentlyReceivedMessage_{true};
|
||||||
|
|
||||||
|
// waitingForPong_ is set to true when we send a PING message, and back to false when we receive the matching PONG response
|
||||||
|
std::atomic<bool> waitingForPong_{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/highlights/HighlightController.hpp"
|
#include "controllers/highlights/HighlightController.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/LimitedQueue.hpp"
|
#include "messages/LimitedQueue.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "providers/twitch/TwitchAccountManager.hpp"
|
#include "providers/twitch/TwitchAccountManager.hpp"
|
||||||
|
@ -22,6 +21,97 @@
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
static float relativeSimilarity(const QString &str1, const QString &str2)
|
||||||
|
{
|
||||||
|
// Longest Common Substring Problem
|
||||||
|
std::vector<std::vector<int>> tree(str1.size(),
|
||||||
|
std::vector<int>(str2.size(), 0));
|
||||||
|
int z = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < str1.size(); ++i)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < str2.size(); ++j)
|
||||||
|
{
|
||||||
|
if (str1[i] == str2[j])
|
||||||
|
{
|
||||||
|
if (i == 0 || j == 0)
|
||||||
|
{
|
||||||
|
tree[i][j] = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tree[i][j] = tree[i - 1][j - 1] + 1;
|
||||||
|
}
|
||||||
|
if (tree[i][j] > z)
|
||||||
|
{
|
||||||
|
z = tree[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tree[i][j] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return z == 0 ? 0.f : float(z) / std::max(str1.size(), str2.size());
|
||||||
|
};
|
||||||
|
|
||||||
|
float IrcMessageHandler::similarity(
|
||||||
|
MessagePtr msg, const LimitedQueueSnapshot<MessagePtr> &messages)
|
||||||
|
{
|
||||||
|
float similarityPercent = 0.0f;
|
||||||
|
int bySameUser = 0;
|
||||||
|
for (int i = 1; bySameUser < getSettings()->hideSimilarMaxMessagesToCheck;
|
||||||
|
++i)
|
||||||
|
{
|
||||||
|
if (messages.size() < i)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const auto &prevMsg = messages[messages.size() - i];
|
||||||
|
if (prevMsg->parseTime.secsTo(QTime::currentTime()) >=
|
||||||
|
getSettings()->hideSimilarMaxDelay)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (msg->loginName != prevMsg->loginName)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++bySameUser;
|
||||||
|
similarityPercent = std::max(
|
||||||
|
similarityPercent,
|
||||||
|
relativeSimilarity(msg->messageText, prevMsg->messageText));
|
||||||
|
}
|
||||||
|
return similarityPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrcMessageHandler::setSimilarityFlags(MessagePtr msg, ChannelPtr chan)
|
||||||
|
{
|
||||||
|
if (getSettings()->similarityEnabled)
|
||||||
|
{
|
||||||
|
bool isMyself = msg->loginName ==
|
||||||
|
getApp()->accounts->twitch.getCurrent()->getUserName();
|
||||||
|
bool hideMyself = getSettings()->hideSimilarMyself;
|
||||||
|
|
||||||
|
if (isMyself && !hideMyself)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IrcMessageHandler::similarity(msg, chan->getMessageSnapshot()) >
|
||||||
|
getSettings()->similarityPercentage)
|
||||||
|
{
|
||||||
|
msg->flags.set(MessageFlag::Similar, true);
|
||||||
|
if (getSettings()->colorSimilarDisabled)
|
||||||
|
{
|
||||||
|
msg->flags.set(MessageFlag::Disabled, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static QMap<QString, QString> parseBadges(QString badgesString)
|
static QMap<QString, QString> parseBadges(QString badgesString)
|
||||||
{
|
{
|
||||||
QMap<QString, QString> badges;
|
QMap<QString, QString> badges;
|
||||||
|
@ -134,7 +224,16 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
|
||||||
}
|
}
|
||||||
|
|
||||||
auto msg = builder.build();
|
auto msg = builder.build();
|
||||||
builder.triggerHighlights();
|
|
||||||
|
IrcMessageHandler::setSimilarityFlags(msg, chan);
|
||||||
|
|
||||||
|
if (!msg->flags.has(MessageFlag::Similar) ||
|
||||||
|
(!getSettings()->hideSimilar &&
|
||||||
|
getSettings()->shownSimilarTriggerHighlights))
|
||||||
|
{
|
||||||
|
builder.triggerHighlights();
|
||||||
|
}
|
||||||
|
|
||||||
auto highlighted = msg->flags.has(MessageFlag::Highlighted);
|
auto highlighted = msg->flags.has(MessageFlag::Highlighted);
|
||||||
|
|
||||||
if (!isSub)
|
if (!isSub)
|
||||||
|
@ -235,9 +334,8 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
|
||||||
|
|
||||||
if (chan->isEmpty())
|
if (chan->isEmpty())
|
||||||
{
|
{
|
||||||
log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
|
qDebug() << "[IrcMessageHandler:handleClearChatMessage] Twitch channel"
|
||||||
"found",
|
<< chanName << "not found";
|
||||||
chanName);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,10 +398,9 @@ void IrcMessageHandler::handleClearMessageMessage(Communi::IrcMessage *message)
|
||||||
|
|
||||||
if (chan->isEmpty())
|
if (chan->isEmpty())
|
||||||
{
|
{
|
||||||
log("[IrcMessageHandler:handleClearMessageMessage] Twitch channel {} "
|
qDebug()
|
||||||
"not "
|
<< "[IrcMessageHandler:handleClearMessageMessage] Twitch channel"
|
||||||
"found",
|
<< chanName << "not found";
|
||||||
chanName);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +453,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
|
||||||
void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
|
void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
log("Received whisper!");
|
qDebug() << "Received whisper!";
|
||||||
MessageParseArgs args;
|
MessageParseArgs args;
|
||||||
|
|
||||||
args.isReceivedWhisper = true;
|
args.isReceivedWhisper = true;
|
||||||
|
@ -568,10 +665,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
|
||||||
|
|
||||||
if (channel->isEmpty())
|
if (channel->isEmpty())
|
||||||
{
|
{
|
||||||
log("[IrcManager:handleNoticeMessage] Channel {} not found in "
|
qDebug() << "[IrcManager:handleNoticeMessage] Channel"
|
||||||
"channel "
|
<< channelName << "not found in channel manager";
|
||||||
"manager ",
|
|
||||||
channelName);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <IrcMessage>
|
#include <IrcMessage>
|
||||||
|
#include "common/Channel.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
@ -49,6 +50,10 @@ public:
|
||||||
void handleJoinMessage(Communi::IrcMessage *message);
|
void handleJoinMessage(Communi::IrcMessage *message);
|
||||||
void handlePartMessage(Communi::IrcMessage *message);
|
void handlePartMessage(Communi::IrcMessage *message);
|
||||||
|
|
||||||
|
static float similarity(MessagePtr msg,
|
||||||
|
const LimitedQueueSnapshot<MessagePtr> &messages);
|
||||||
|
static void setSimilarityFlags(MessagePtr message, ChannelPtr channel);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addMessage(Communi::IrcMessage *message, const QString &target,
|
void addMessage(Communi::IrcMessage *message, const QString &target,
|
||||||
const QString &content, TwitchIrcServer &server,
|
const QString &content, TwitchIrcServer &server,
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
|
@ -28,6 +27,13 @@ PartialTwitchUser PartialTwitchUser::byId(const QString &id)
|
||||||
|
|
||||||
void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
|
void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
|
||||||
const QObject *caller)
|
const QObject *caller)
|
||||||
|
{
|
||||||
|
getId(
|
||||||
|
successCallback, [] {}, caller);
|
||||||
|
}
|
||||||
|
void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
|
||||||
|
std::function<void()> failureCallback,
|
||||||
|
const QObject *caller)
|
||||||
{
|
{
|
||||||
assert(!this->username_.isEmpty());
|
assert(!this->username_.isEmpty());
|
||||||
|
|
||||||
|
@ -35,35 +41,38 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
|
||||||
this->username_)
|
this->username_)
|
||||||
.caller(caller)
|
.caller(caller)
|
||||||
.authorizeTwitchV5(getDefaultClientID())
|
.authorizeTwitchV5(getDefaultClientID())
|
||||||
.onSuccess([successCallback](auto result) -> Outcome {
|
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||||
auto root = result.parseJson();
|
auto root = result.parseJson();
|
||||||
if (!root.value("users").isArray())
|
if (!root.value("users").isArray())
|
||||||
{
|
{
|
||||||
log("API Error while getting user id, users is not an array");
|
qDebug()
|
||||||
|
<< "API Error while getting user id, users is not an array";
|
||||||
|
failureCallback();
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto users = root.value("users").toArray();
|
auto users = root.value("users").toArray();
|
||||||
if (users.size() != 1)
|
if (users.size() != 1)
|
||||||
{
|
{
|
||||||
log("API Error while getting user id, users array size is not "
|
qDebug() << "API Error while getting user id, users array size "
|
||||||
"1");
|
"is not 1";
|
||||||
|
failureCallback();
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
if (!users[0].isObject())
|
if (!users[0].isObject())
|
||||||
{
|
{
|
||||||
log("API Error while getting user id, first user is not an "
|
qDebug() << "API Error while getting user id, first user is "
|
||||||
"object");
|
"not an object";
|
||||||
|
failureCallback();
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
auto firstUser = users[0].toObject();
|
auto firstUser = users[0].toObject();
|
||||||
auto id = firstUser.value("_id");
|
auto id = firstUser.value("_id");
|
||||||
if (!id.isString())
|
if (!id.isString())
|
||||||
{
|
{
|
||||||
log("API Error: while getting user id, first user object `_id` "
|
qDebug() << "API Error: while getting user id, first user "
|
||||||
"key "
|
"object `_id` key is not a string";
|
||||||
"is not a "
|
failureCallback();
|
||||||
"string");
|
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
successCallback(id.toString());
|
successCallback(id.toString());
|
||||||
|
|
|
@ -21,6 +21,10 @@ public:
|
||||||
|
|
||||||
void getId(std::function<void(QString)> successCallback,
|
void getId(std::function<void(QString)> successCallback,
|
||||||
const QObject *caller = nullptr);
|
const QObject *caller = nullptr);
|
||||||
|
|
||||||
|
void getId(std::function<void(QString)> successCallback,
|
||||||
|
std::function<void()> failureCallback,
|
||||||
|
const QObject *caller = nullptr);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#include "providers/twitch/PubsubClient.hpp"
|
#include "providers/twitch/PubsubClient.hpp"
|
||||||
|
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "providers/twitch/PubsubActions.hpp"
|
#include "providers/twitch/PubsubActions.hpp"
|
||||||
#include "providers/twitch/PubsubHelpers.hpp"
|
#include "providers/twitch/PubsubHelpers.hpp"
|
||||||
|
#include "util/Helpers.hpp"
|
||||||
#include "util/RapidjsonHelpers.hpp"
|
#include "util/RapidjsonHelpers.hpp"
|
||||||
|
|
||||||
#include <rapidjson/error/en.h>
|
#include <rapidjson/error/en.h>
|
||||||
|
@ -77,14 +77,14 @@ namespace detail {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSubClient::unlistenPrefix(const std::string &prefix)
|
void PubSubClient::unlistenPrefix(const QString &prefix)
|
||||||
{
|
{
|
||||||
std::vector<std::string> topics;
|
std::vector<QString> topics;
|
||||||
|
|
||||||
for (auto it = this->listeners_.begin(); it != this->listeners_.end();)
|
for (auto it = this->listeners_.begin(); it != this->listeners_.end();)
|
||||||
{
|
{
|
||||||
const auto &listener = *it;
|
const auto &listener = *it;
|
||||||
if (listener.topic.find(prefix) == 0)
|
if (listener.topic.startsWith(prefix))
|
||||||
{
|
{
|
||||||
topics.push_back(listener.topic);
|
topics.push_back(listener.topic);
|
||||||
it = this->listeners_.erase(it);
|
it = this->listeners_.erase(it);
|
||||||
|
@ -116,16 +116,14 @@ namespace detail {
|
||||||
{
|
{
|
||||||
assert(this->awaitingPong_);
|
assert(this->awaitingPong_);
|
||||||
|
|
||||||
log("Got pong!");
|
|
||||||
|
|
||||||
this->awaitingPong_ = false;
|
this->awaitingPong_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PubSubClient::isListeningToTopic(const std::string &payload)
|
bool PubSubClient::isListeningToTopic(const QString &topic)
|
||||||
{
|
{
|
||||||
for (const auto &listener : this->listeners_)
|
for (const auto &listener : this->listeners_)
|
||||||
{
|
{
|
||||||
if (listener.topic == payload)
|
if (listener.topic == topic)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -156,9 +154,8 @@ namespace detail {
|
||||||
|
|
||||||
if (self->awaitingPong_)
|
if (self->awaitingPong_)
|
||||||
{
|
{
|
||||||
log("No pong response, disconnect!");
|
qDebug() << "No pong response, disconnect!";
|
||||||
// TODO(pajlada): Label this connection as "disconnect
|
// TODO(pajlada): Label this connection as "disconnect me"
|
||||||
// me"
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -181,7 +178,8 @@ namespace detail {
|
||||||
|
|
||||||
if (ec)
|
if (ec)
|
||||||
{
|
{
|
||||||
log("Error sending message {}: {}", payload, ec.message());
|
qDebug() << "Error sending message" << payload << ":"
|
||||||
|
<< ec.message().c_str();
|
||||||
// TODO(pajlada): Check which error code happened and maybe
|
// TODO(pajlada): Check which error code happened and maybe
|
||||||
// gracefully handle it
|
// gracefully handle it
|
||||||
|
|
||||||
|
@ -223,7 +221,7 @@ PubSub::PubSub()
|
||||||
|
|
||||||
if (!data.HasMember("args"))
|
if (!data.HasMember("args"))
|
||||||
{
|
{
|
||||||
log("Missing required args member");
|
qDebug() << "Missing required args member";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,13 +229,13 @@ PubSub::PubSub()
|
||||||
|
|
||||||
if (!args.IsArray())
|
if (!args.IsArray())
|
||||||
{
|
{
|
||||||
log("args member must be an array");
|
qDebug() << "args member must be an array";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.Size() == 0)
|
if (args.Size() == 0)
|
||||||
{
|
{
|
||||||
log("Missing duration argument in slowmode on");
|
qDebug() << "Missing duration argument in slowmode on";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,7 +243,7 @@ PubSub::PubSub()
|
||||||
|
|
||||||
if (!durationArg.IsString())
|
if (!durationArg.IsString())
|
||||||
{
|
{
|
||||||
log("Duration arg must be a string");
|
qDebug() << "Duration arg must be a string";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,7 +336,7 @@ PubSub::PubSub()
|
||||||
}
|
}
|
||||||
catch (const std::runtime_error &ex)
|
catch (const std::runtime_error &ex)
|
||||||
{
|
{
|
||||||
log("Error parsing moderation action: {}", ex.what());
|
qDebug() << "Error parsing moderation action:" << ex.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
action.modded = false;
|
action.modded = false;
|
||||||
|
@ -368,7 +366,7 @@ PubSub::PubSub()
|
||||||
}
|
}
|
||||||
catch (const std::runtime_error &ex)
|
catch (const std::runtime_error &ex)
|
||||||
{
|
{
|
||||||
log("Error parsing moderation action: {}", ex.what());
|
qDebug() << "Error parsing moderation action:" << ex.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
action.modded = true;
|
action.modded = true;
|
||||||
|
@ -417,7 +415,7 @@ PubSub::PubSub()
|
||||||
}
|
}
|
||||||
catch (const std::runtime_error &ex)
|
catch (const std::runtime_error &ex)
|
||||||
{
|
{
|
||||||
log("Error parsing moderation action: {}", ex.what());
|
qDebug() << "Error parsing moderation action:" << ex.what();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -454,7 +452,7 @@ PubSub::PubSub()
|
||||||
}
|
}
|
||||||
catch (const std::runtime_error &ex)
|
catch (const std::runtime_error &ex)
|
||||||
{
|
{
|
||||||
log("Error parsing moderation action: {}", ex.what());
|
qDebug() << "Error parsing moderation action:" << ex.what();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -485,7 +483,7 @@ PubSub::PubSub()
|
||||||
}
|
}
|
||||||
catch (const std::runtime_error &ex)
|
catch (const std::runtime_error &ex)
|
||||||
{
|
{
|
||||||
log("Error parsing moderation action: {}", ex.what());
|
qDebug() << "Error parsing moderation action:" << ex.what();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -516,7 +514,7 @@ PubSub::PubSub()
|
||||||
}
|
}
|
||||||
catch (const std::runtime_error &ex)
|
catch (const std::runtime_error &ex)
|
||||||
{
|
{
|
||||||
log("Error parsing moderation action: {}", ex.what());
|
qDebug() << "Error parsing moderation action:" << ex.what();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -568,7 +566,7 @@ PubSub::PubSub()
|
||||||
}
|
}
|
||||||
catch (const std::runtime_error &ex)
|
catch (const std::runtime_error &ex)
|
||||||
{
|
{
|
||||||
log("Error parsing moderation action: {}", ex.what());
|
qDebug() << "Error parsing moderation action:" << ex.what();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -597,7 +595,7 @@ PubSub::PubSub()
|
||||||
}
|
}
|
||||||
catch (const std::runtime_error &ex)
|
catch (const std::runtime_error &ex)
|
||||||
{
|
{
|
||||||
log("Error parsing moderation action: {}", ex.what());
|
qDebug() << "Error parsing moderation action:" << ex.what();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -626,7 +624,7 @@ PubSub::PubSub()
|
||||||
}
|
}
|
||||||
catch (const std::runtime_error &ex)
|
catch (const std::runtime_error &ex)
|
||||||
{
|
{
|
||||||
log("Error parsing moderation action: {}", ex.what());
|
qDebug() << "Error parsing moderation action:" << ex.what();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -655,7 +653,7 @@ PubSub::PubSub()
|
||||||
}
|
}
|
||||||
catch (const std::runtime_error &ex)
|
catch (const std::runtime_error &ex)
|
||||||
{
|
{
|
||||||
log("Error parsing moderation action: {}", ex.what());
|
qDebug() << "Error parsing moderation action:" << ex.what();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -685,7 +683,7 @@ PubSub::PubSub()
|
||||||
}
|
}
|
||||||
catch (const std::runtime_error &ex)
|
catch (const std::runtime_error &ex)
|
||||||
{
|
{
|
||||||
log("Error parsing moderation action: {}", ex.what());
|
qDebug() << "Error parsing moderation action:" << ex.what();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -738,7 +736,7 @@ void PubSub::addClient()
|
||||||
|
|
||||||
if (ec)
|
if (ec)
|
||||||
{
|
{
|
||||||
log("Unable to establish connection: {}", ec.message());
|
qDebug() << "Unable to establish connection:" << ec.message().c_str();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -753,20 +751,23 @@ void PubSub::start()
|
||||||
|
|
||||||
void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
|
void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
|
||||||
{
|
{
|
||||||
|
static const QString topicFormat("whispers.%1");
|
||||||
|
|
||||||
assert(account != nullptr);
|
assert(account != nullptr);
|
||||||
|
|
||||||
std::string userID = account->getUserId().toStdString();
|
auto userID = account->getUserId();
|
||||||
|
|
||||||
log("Connection open!");
|
qDebug() << "Connection open!";
|
||||||
websocketpp::lib::error_code ec;
|
websocketpp::lib::error_code ec;
|
||||||
|
|
||||||
std::vector<std::string> topics({"whispers." + userID});
|
std::vector<QString> topics({topicFormat.arg(userID)});
|
||||||
|
|
||||||
this->listen(createListenMessage(topics, account));
|
this->listen(createListenMessage(topics, account));
|
||||||
|
|
||||||
if (ec)
|
if (ec)
|
||||||
{
|
{
|
||||||
log("Unable to send message to websocket server: {}", ec.message());
|
qDebug() << "Unable to send message to websocket server:"
|
||||||
|
<< ec.message().c_str();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -783,26 +784,26 @@ void PubSub::unlistenAllModerationActions()
|
||||||
void PubSub::listenToChannelModerationActions(
|
void PubSub::listenToChannelModerationActions(
|
||||||
const QString &channelID, std::shared_ptr<TwitchAccount> account)
|
const QString &channelID, std::shared_ptr<TwitchAccount> account)
|
||||||
{
|
{
|
||||||
|
static const QString topicFormat("chat_moderator_actions.%1.%2");
|
||||||
assert(!channelID.isEmpty());
|
assert(!channelID.isEmpty());
|
||||||
assert(account != nullptr);
|
assert(account != nullptr);
|
||||||
QString userID = account->getUserId();
|
QString userID = account->getUserId();
|
||||||
if (userID.isEmpty())
|
if (userID.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::string topic(fS("chat_moderator_actions.{}.{}", userID, channelID));
|
auto topic = topicFormat.arg(userID).arg(channelID);
|
||||||
|
|
||||||
if (this->isListeningToTopic(topic))
|
if (this->isListeningToTopic(topic))
|
||||||
{
|
{
|
||||||
log("We are already listening to topic {}", topic);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log("Listen to topic {}", topic);
|
qDebug() << "Listen to topic" << topic;
|
||||||
|
|
||||||
this->listenToTopic(topic, account);
|
this->listenToTopic(topic, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSub::listenToTopic(const std::string &topic,
|
void PubSub::listenToTopic(const QString &topic,
|
||||||
std::shared_ptr<TwitchAccount> account)
|
std::shared_ptr<TwitchAccount> account)
|
||||||
{
|
{
|
||||||
auto message = createListenMessage({topic}, account);
|
auto message = createListenMessage({topic}, account);
|
||||||
|
@ -814,20 +815,19 @@ void PubSub::listen(rapidjson::Document &&msg)
|
||||||
{
|
{
|
||||||
if (this->tryListen(msg))
|
if (this->tryListen(msg))
|
||||||
{
|
{
|
||||||
log("Successfully listened!");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->addClient();
|
this->addClient();
|
||||||
|
|
||||||
log("Added to the back of the queue");
|
qDebug() << "Added to the back of the queue";
|
||||||
this->requests.emplace_back(
|
this->requests.emplace_back(
|
||||||
std::make_unique<rapidjson::Document>(std::move(msg)));
|
std::make_unique<rapidjson::Document>(std::move(msg)));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PubSub::tryListen(rapidjson::Document &msg)
|
bool PubSub::tryListen(rapidjson::Document &msg)
|
||||||
{
|
{
|
||||||
log("tryListen with {} clients", this->clients.size());
|
qDebug() << "tryListen with" << this->clients.size() << "clients";
|
||||||
for (const auto &p : this->clients)
|
for (const auto &p : this->clients)
|
||||||
{
|
{
|
||||||
const auto &client = p.second;
|
const auto &client = p.second;
|
||||||
|
@ -840,7 +840,7 @@ bool PubSub::tryListen(rapidjson::Document &msg)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PubSub::isListeningToTopic(const std::string &topic)
|
bool PubSub::isListeningToTopic(const QString &topic)
|
||||||
{
|
{
|
||||||
for (const auto &p : this->clients)
|
for (const auto &p : this->clients)
|
||||||
{
|
{
|
||||||
|
@ -865,24 +865,24 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
||||||
|
|
||||||
if (!res)
|
if (!res)
|
||||||
{
|
{
|
||||||
log("Error parsing message '{}' from PubSub: {}", payload,
|
qDebug() << "Error parsing message '" << payload.c_str()
|
||||||
rapidjson::GetParseError_En(res.Code()));
|
<< "' from PubSub:" << rapidjson::GetParseError_En(res.Code());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!msg.IsObject())
|
if (!msg.IsObject())
|
||||||
{
|
{
|
||||||
log("Error parsing message '{}' from PubSub. Root object is not an "
|
qDebug() << "Error parsing message '" << payload.c_str()
|
||||||
"object",
|
<< "' from PubSub. Root object is not an "
|
||||||
payload);
|
"object";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string type;
|
QString type;
|
||||||
|
|
||||||
if (!rj::getSafe(msg, "type", type))
|
if (!rj::getSafe(msg, "type", type))
|
||||||
{
|
{
|
||||||
log("Missing required string member `type` in message root");
|
qDebug() << "Missing required string member `type` in message root";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -894,7 +894,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
||||||
{
|
{
|
||||||
if (!msg.HasMember("data"))
|
if (!msg.HasMember("data"))
|
||||||
{
|
{
|
||||||
log("Missing required object member `data` in message root");
|
qDebug() << "Missing required object member `data` in message root";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -902,7 +902,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
||||||
|
|
||||||
if (!data.IsObject())
|
if (!data.IsObject())
|
||||||
{
|
{
|
||||||
log("Member `data` must be an object");
|
qDebug() << "Member `data` must be an object";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -922,7 +922,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
log("Unknown message type: {}", type);
|
qDebug() << "Unknown message type:" << type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -983,7 +983,7 @@ PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl)
|
||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
log("Exception caught in OnTLSInit: {}", e.what());
|
qDebug() << "Exception caught in OnTLSInit:" << e.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
|
@ -991,21 +991,21 @@ PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl)
|
||||||
|
|
||||||
void PubSub::handleListenResponse(const rapidjson::Document &msg)
|
void PubSub::handleListenResponse(const rapidjson::Document &msg)
|
||||||
{
|
{
|
||||||
std::string error;
|
QString error;
|
||||||
|
|
||||||
if (rj::getSafe(msg, "error", error))
|
if (rj::getSafe(msg, "error", error))
|
||||||
{
|
{
|
||||||
std::string nonce;
|
QString nonce;
|
||||||
rj::getSafe(msg, "nonce", nonce);
|
rj::getSafe(msg, "nonce", nonce);
|
||||||
|
|
||||||
if (error.empty())
|
if (error.isEmpty())
|
||||||
{
|
{
|
||||||
log("Successfully listened to nonce {}", nonce);
|
qDebug() << "Successfully listened to nonce" << nonce;
|
||||||
// Nothing went wrong
|
// Nothing went wrong
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log("PubSub error: {} on nonce {}", error, nonce);
|
qDebug() << "PubSub error:" << error << "on nonce" << nonce;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1016,7 +1016,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
||||||
|
|
||||||
if (!rj::getSafe(outerData, "topic", topic))
|
if (!rj::getSafe(outerData, "topic", topic))
|
||||||
{
|
{
|
||||||
log("Missing required string member `topic` in outerData");
|
qDebug() << "Missing required string member `topic` in outerData";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1024,7 +1024,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
||||||
|
|
||||||
if (!rj::getSafe(outerData, "message", payload))
|
if (!rj::getSafe(outerData, "message", payload))
|
||||||
{
|
{
|
||||||
log("Expected string message in outerData");
|
qDebug() << "Expected string message in outerData";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1034,8 +1034,8 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
||||||
|
|
||||||
if (!res)
|
if (!res)
|
||||||
{
|
{
|
||||||
log("Error parsing message '{}' from PubSub: {}", payload,
|
qDebug() << "Error parsing message '" << payload.c_str()
|
||||||
rapidjson::GetParseError_En(res.Code()));
|
<< "' from PubSub:" << rapidjson::GetParseError_En(res.Code());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1045,7 +1045,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
||||||
|
|
||||||
if (!rj::getSafe(msg, "type", whisperType))
|
if (!rj::getSafe(msg, "type", whisperType))
|
||||||
{
|
{
|
||||||
log("Bad whisper data");
|
qDebug() << "Bad whisper data";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1063,7 +1063,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
log("Invalid whisper type: {}", whisperType);
|
qDebug() << "Invalid whisper type:" << whisperType.c_str();
|
||||||
assert(false);
|
assert(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1078,7 +1078,8 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
||||||
|
|
||||||
if (!rj::getSafe(data, "moderation_action", moderationAction))
|
if (!rj::getSafe(data, "moderation_action", moderationAction))
|
||||||
{
|
{
|
||||||
log("Missing moderation action in data: {}", rj::stringify(data));
|
qDebug() << "Missing moderation action in data:"
|
||||||
|
<< rj::stringify(data).c_str();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1086,7 +1087,8 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
||||||
|
|
||||||
if (handlerIt == this->moderationActionHandlers.end())
|
if (handlerIt == this->moderationActionHandlers.end())
|
||||||
{
|
{
|
||||||
log("No handler found for moderation action {}", moderationAction);
|
qDebug() << "No handler found for moderation action"
|
||||||
|
<< moderationAction.c_str();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1095,16 +1097,16 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
log("Unknown topic: {}", topic);
|
qDebug() << "Unknown topic:" << topic;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSub::runThread()
|
void PubSub::runThread()
|
||||||
{
|
{
|
||||||
log("Start pubsub manager thread");
|
qDebug() << "Start pubsub manager thread";
|
||||||
this->websocketClient.run();
|
this->websocketClient.run();
|
||||||
log("Done with pubsub manager thread");
|
qDebug() << "Done with pubsub manager thread";
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -33,7 +32,7 @@ using WebsocketErrorCode = websocketpp::lib::error_code;
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
struct Listener {
|
struct Listener {
|
||||||
std::string topic;
|
QString topic;
|
||||||
bool authed;
|
bool authed;
|
||||||
bool persistent;
|
bool persistent;
|
||||||
bool confirmed = false;
|
bool confirmed = false;
|
||||||
|
@ -49,11 +48,11 @@ namespace detail {
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
bool listen(rapidjson::Document &message);
|
bool listen(rapidjson::Document &message);
|
||||||
void unlistenPrefix(const std::string &prefix);
|
void unlistenPrefix(const QString &prefix);
|
||||||
|
|
||||||
void handlePong();
|
void handlePong();
|
||||||
|
|
||||||
bool isListeningToTopic(const std::string &topic);
|
bool isListeningToTopic(const QString &topic);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ping();
|
void ping();
|
||||||
|
@ -135,13 +134,13 @@ public:
|
||||||
std::vector<std::unique_ptr<rapidjson::Document>> requests;
|
std::vector<std::unique_ptr<rapidjson::Document>> requests;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void listenToTopic(const std::string &topic,
|
void listenToTopic(const QString &topic,
|
||||||
std::shared_ptr<TwitchAccount> account);
|
std::shared_ptr<TwitchAccount> account);
|
||||||
|
|
||||||
void listen(rapidjson::Document &&msg);
|
void listen(rapidjson::Document &&msg);
|
||||||
bool tryListen(rapidjson::Document &msg);
|
bool tryListen(rapidjson::Document &msg);
|
||||||
|
|
||||||
bool isListeningToTopic(const std::string &topic);
|
bool isListeningToTopic(const QString &topic);
|
||||||
|
|
||||||
void addClient();
|
void addClient();
|
||||||
|
|
||||||
|
|
|
@ -46,9 +46,8 @@ bool getTargetUser(const rapidjson::Value &data, ActionUser &user)
|
||||||
return rj::getSafe(data, "target_user_id", user.id);
|
return rj::getSafe(data, "target_user_id", user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
rapidjson::Document createListenMessage(
|
rapidjson::Document createListenMessage(const std::vector<QString> &topicsVec,
|
||||||
const std::vector<std::string> &topicsVec,
|
std::shared_ptr<TwitchAccount> account)
|
||||||
std::shared_ptr<TwitchAccount> account)
|
|
||||||
{
|
{
|
||||||
rapidjson::Document msg(rapidjson::kObjectType);
|
rapidjson::Document msg(rapidjson::kObjectType);
|
||||||
auto &a = msg.GetAllocator();
|
auto &a = msg.GetAllocator();
|
||||||
|
@ -75,8 +74,7 @@ rapidjson::Document createListenMessage(
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
rapidjson::Document createUnlistenMessage(
|
rapidjson::Document createUnlistenMessage(const std::vector<QString> &topicsVec)
|
||||||
const std::vector<std::string> &topicsVec)
|
|
||||||
{
|
{
|
||||||
rapidjson::Document msg(rapidjson::kObjectType);
|
rapidjson::Document msg(rapidjson::kObjectType);
|
||||||
auto &a = msg.GetAllocator();
|
auto &a = msg.GetAllocator();
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <boost/asio/steady_timer.hpp>
|
#include <boost/asio/steady_timer.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "util/RapidjsonHelpers.hpp"
|
#include "util/RapidjsonHelpers.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
@ -18,11 +17,10 @@ bool getCreatedByUser(const rapidjson::Value &data, ActionUser &user);
|
||||||
|
|
||||||
bool getTargetUser(const rapidjson::Value &data, ActionUser &user);
|
bool getTargetUser(const rapidjson::Value &data, ActionUser &user);
|
||||||
|
|
||||||
rapidjson::Document createListenMessage(
|
rapidjson::Document createListenMessage(const std::vector<QString> &topicsVec,
|
||||||
const std::vector<std::string> &topicsVec,
|
std::shared_ptr<TwitchAccount> account);
|
||||||
std::shared_ptr<TwitchAccount> account);
|
|
||||||
rapidjson::Document createUnlistenMessage(
|
rapidjson::Document createUnlistenMessage(
|
||||||
const std::vector<std::string> &topicsVec);
|
const std::vector<QString> &topicsVec);
|
||||||
|
|
||||||
// Create timer using given ioService
|
// Create timer using given ioService
|
||||||
template <typename Duration, typename Callback>
|
template <typename Duration, typename Callback>
|
||||||
|
@ -35,7 +33,7 @@ void runAfter(boost::asio::io_service &ioService, Duration duration,
|
||||||
timer->async_wait([timer, cb](const boost::system::error_code &ec) {
|
timer->async_wait([timer, cb](const boost::system::error_code &ec) {
|
||||||
if (ec)
|
if (ec)
|
||||||
{
|
{
|
||||||
log("Error in runAfter: {}", ec.message());
|
qDebug() << "Error in runAfter:" << ec.message().c_str();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +51,7 @@ void runAfter(std::shared_ptr<boost::asio::steady_timer> timer,
|
||||||
timer->async_wait([timer, cb](const boost::system::error_code &ec) {
|
timer->async_wait([timer, cb](const boost::system::error_code &ec) {
|
||||||
if (ec)
|
if (ec)
|
||||||
{
|
{
|
||||||
log("Error in runAfter: {}", ec.message());
|
qDebug() << "Error in runAfter:" << ec.message().c_str();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
#include "common/Env.hpp"
|
#include "common/Env.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "providers/twitch/PartialTwitchUser.hpp"
|
#include "providers/twitch/PartialTwitchUser.hpp"
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
|
@ -134,8 +133,8 @@ void TwitchAccount::loadIgnores()
|
||||||
TwitchUser ignoredUser;
|
TwitchUser ignoredUser;
|
||||||
if (!rj::getSafe(userIt->value, ignoredUser))
|
if (!rj::getSafe(userIt->value, ignoredUser))
|
||||||
{
|
{
|
||||||
log("Error parsing twitch user JSON {}",
|
qDebug() << "Error parsing twitch user JSON"
|
||||||
rj::stringify(userIt->value));
|
<< rj::stringify(userIt->value).c_str();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,14 +344,14 @@ std::set<TwitchUser> TwitchAccount::getIgnores() const
|
||||||
|
|
||||||
void TwitchAccount::loadEmotes()
|
void TwitchAccount::loadEmotes()
|
||||||
{
|
{
|
||||||
log("Loading Twitch emotes for user {}", this->getUserName());
|
qDebug() << "Loading Twitch emotes for user" << this->getUserName();
|
||||||
|
|
||||||
const auto &clientID = this->getOAuthClient();
|
const auto &clientID = this->getOAuthClient();
|
||||||
const auto &oauthToken = this->getOAuthToken();
|
const auto &oauthToken = this->getOAuthToken();
|
||||||
|
|
||||||
if (clientID.isEmpty() || oauthToken.isEmpty())
|
if (clientID.isEmpty() || oauthToken.isEmpty())
|
||||||
{
|
{
|
||||||
log("Missing Client ID or OAuth token");
|
qDebug() << "Missing Client ID or OAuth token";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,7 +362,7 @@ void TwitchAccount::loadEmotes()
|
||||||
|
|
||||||
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
|
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
|
||||||
.onError([=](NetworkResult result) {
|
.onError([=](NetworkResult result) {
|
||||||
log("[TwitchAccount::loadEmotes] Error {}", result.status());
|
qDebug() << "[TwitchAccount::loadEmotes] Error" << result.status();
|
||||||
if (result.status() == 203)
|
if (result.status() == 203)
|
||||||
{
|
{
|
||||||
// onFinished(FollowResult_NotFollowing);
|
// onFinished(FollowResult_NotFollowing);
|
||||||
|
@ -402,7 +401,8 @@ void TwitchAccount::autoModAllow(const QString msgID)
|
||||||
|
|
||||||
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
|
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
|
||||||
.onError([=](NetworkResult result) {
|
.onError([=](NetworkResult result) {
|
||||||
log("[TwitchAccounts::autoModAllow] Error {}", result.status());
|
qDebug() << "[TwitchAccounts::autoModAllow] Error"
|
||||||
|
<< result.status();
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
@ -421,7 +421,8 @@ void TwitchAccount::autoModDeny(const QString msgID)
|
||||||
|
|
||||||
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
|
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
|
||||||
.onError([=](NetworkResult result) {
|
.onError([=](NetworkResult result) {
|
||||||
log("[TwitchAccounts::autoModDeny] Error {}", result.status());
|
qDebug() << "[TwitchAccounts::autoModDeny] Error"
|
||||||
|
<< result.status();
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
@ -436,7 +437,7 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
|
||||||
auto emoticonSets = root.FindMember("emoticon_sets");
|
auto emoticonSets = root.FindMember("emoticon_sets");
|
||||||
if (emoticonSets == root.MemberEnd() || !emoticonSets->value.IsObject())
|
if (emoticonSets == root.MemberEnd() || !emoticonSets->value.IsObject())
|
||||||
{
|
{
|
||||||
log("No emoticon_sets in load emotes response");
|
qDebug() << "No emoticon_sets in load emotes response";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -452,21 +453,21 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
|
||||||
{
|
{
|
||||||
if (!emoteJSON.IsObject())
|
if (!emoteJSON.IsObject())
|
||||||
{
|
{
|
||||||
log("Emote value was invalid");
|
qDebug() << "Emote value was invalid";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t idNumber;
|
uint64_t idNumber;
|
||||||
if (!rj::getSafe(emoteJSON, "id", idNumber))
|
if (!rj::getSafe(emoteJSON, "id", idNumber))
|
||||||
{
|
{
|
||||||
log("No ID key found in Emote value");
|
qDebug() << "No ID key found in Emote value";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString _code;
|
QString _code;
|
||||||
if (!rj::getSafe(emoteJSON, "code", _code))
|
if (!rj::getSafe(emoteJSON, "code", _code))
|
||||||
{
|
{
|
||||||
log("No code key found in Emote value");
|
qDebug() << "No code key found in Emote value";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,6 +482,10 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
|
||||||
emoteData->emotes.emplace(code, emote);
|
emoteData->emotes.emplace(code, emote);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::sort(emoteSet->emotes.begin(), emoteSet->emotes.end(),
|
||||||
|
[](const TwitchEmote &l, const TwitchEmote &r) {
|
||||||
|
return l.name.string < r.name.string;
|
||||||
|
});
|
||||||
emoteData->emoteSets.emplace_back(emoteSet);
|
emoteData->emoteSets.emplace_back(emoteSet);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -489,7 +494,7 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
|
||||||
{
|
{
|
||||||
if (!emoteSet)
|
if (!emoteSet)
|
||||||
{
|
{
|
||||||
log("null emote set sent");
|
qDebug() << "null emote set sent";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -505,7 +510,8 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
|
||||||
NetworkRequest(Env::get().twitchEmoteSetResolverUrl.arg(emoteSet->key))
|
NetworkRequest(Env::get().twitchEmoteSetResolverUrl.arg(emoteSet->key))
|
||||||
.cache()
|
.cache()
|
||||||
.onError([](NetworkResult result) {
|
.onError([](NetworkResult result) {
|
||||||
log("Error code {} while loading emote set data", result.status());
|
qDebug() << "Error code" << result.status()
|
||||||
|
<< "while loading emote set data";
|
||||||
})
|
})
|
||||||
.onSuccess([emoteSet](auto result) -> Outcome {
|
.onSuccess([emoteSet](auto result) -> Outcome {
|
||||||
auto root = result.parseRapidJson();
|
auto root = result.parseRapidJson();
|
||||||
|
@ -527,7 +533,7 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
log("Loaded twitch emote set data for {}!", emoteSet->key);
|
qDebug() << "Loaded twitch emote set data for" << emoteSet->key;
|
||||||
|
|
||||||
auto name = channelName;
|
auto name = channelName;
|
||||||
name.detach();
|
name.detach();
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "providers/twitch/TwitchAccountManager.hpp"
|
#include "providers/twitch/TwitchAccountManager.hpp"
|
||||||
|
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
#include "providers/twitch/TwitchAccount.hpp"
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
|
|
||||||
|
@ -103,23 +102,23 @@ void TwitchAccountManager::reloadUsers()
|
||||||
switch (this->addUser(userData))
|
switch (this->addUser(userData))
|
||||||
{
|
{
|
||||||
case AddUserResponse::UserAlreadyExists: {
|
case AddUserResponse::UserAlreadyExists: {
|
||||||
log("User {} already exists", userData.username);
|
qDebug() << "User" << userData.username << "already exists";
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AddUserResponse::UserValuesUpdated: {
|
case AddUserResponse::UserValuesUpdated: {
|
||||||
log("User {} already exists, and values updated!",
|
qDebug() << "User" << userData.username
|
||||||
userData.username);
|
<< "already exists, and values updated!";
|
||||||
if (userData.username == this->getCurrent()->getUserName())
|
if (userData.username == this->getCurrent()->getUserName())
|
||||||
{
|
{
|
||||||
log("It was the current user, so we need to reconnect "
|
qDebug() << "It was the current user, so we need to "
|
||||||
"stuff!");
|
"reconnect stuff!";
|
||||||
this->currentUserChanged.invoke();
|
this->currentUserChanged.invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case AddUserResponse::UserAdded: {
|
case AddUserResponse::UserAdded: {
|
||||||
log("Added user {}", userData.username);
|
qDebug() << "Added user" << userData.username;
|
||||||
listUpdated = true;
|
listUpdated = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -140,15 +139,12 @@ void TwitchAccountManager::load()
|
||||||
auto user = this->findUserByUsername(newUsername);
|
auto user = this->findUserByUsername(newUsername);
|
||||||
if (user)
|
if (user)
|
||||||
{
|
{
|
||||||
log("[AccountManager:currentUsernameChanged] User successfully "
|
qDebug() << "Twitch user updated to" << newUsername;
|
||||||
"updated to {}",
|
|
||||||
newUsername);
|
|
||||||
this->currentUser_ = user;
|
this->currentUser_ = user;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
log("[AccountManager:currentUsernameChanged] User successfully "
|
qDebug() << "Twitch user updated to anonymous";
|
||||||
"updated to anonymous");
|
|
||||||
this->currentUser_ = this->anonymousUser_;
|
this->currentUser_ = this->anonymousUser_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,11 +166,13 @@ bool TwitchAccountManager::isLoggedIn() const
|
||||||
|
|
||||||
bool TwitchAccountManager::removeUser(TwitchAccount *account)
|
bool TwitchAccountManager::removeUser(TwitchAccount *account)
|
||||||
{
|
{
|
||||||
|
static const QString accountFormat("/accounts/uid%1");
|
||||||
|
|
||||||
auto userID(account->getUserId());
|
auto userID(account->getUserId());
|
||||||
if (!userID.isEmpty())
|
if (!userID.isEmpty())
|
||||||
{
|
{
|
||||||
pajlada::Settings::SettingManager::removeSetting(
|
pajlada::Settings::SettingManager::removeSetting(
|
||||||
fS("/accounts/uid{}", userID));
|
accountFormat.arg(userID).toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (account->getUserName() == this->currentUsername)
|
if (account->getUserName() == this->currentUsername)
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "providers/twitch/TwitchCommon.hpp"
|
#include "providers/twitch/TwitchCommon.hpp"
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
@ -23,22 +22,23 @@ void TwitchApi::findUserId(const QString user,
|
||||||
auto root = result.parseJson();
|
auto root = result.parseJson();
|
||||||
if (!root.value("users").isArray())
|
if (!root.value("users").isArray())
|
||||||
{
|
{
|
||||||
log("API Error while getting user id, users is not an array");
|
qDebug()
|
||||||
|
<< "API Error while getting user id, users is not an array";
|
||||||
successCallback("");
|
successCallback("");
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
auto users = root.value("users").toArray();
|
auto users = root.value("users").toArray();
|
||||||
if (users.size() != 1)
|
if (users.size() != 1)
|
||||||
{
|
{
|
||||||
log("API Error while getting user id, users array size is not "
|
qDebug() << "API Error while getting user id, users array size "
|
||||||
"1");
|
"is not 1";
|
||||||
successCallback("");
|
successCallback("");
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
if (!users[0].isObject())
|
if (!users[0].isObject())
|
||||||
{
|
{
|
||||||
log("API Error while getting user id, first user is not an "
|
qDebug() << "API Error while getting user id, first user is "
|
||||||
"object");
|
"not an object";
|
||||||
successCallback("");
|
successCallback("");
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
@ -46,10 +46,8 @@ void TwitchApi::findUserId(const QString user,
|
||||||
auto id = firstUser.value("_id");
|
auto id = firstUser.value("_id");
|
||||||
if (!id.isString())
|
if (!id.isString())
|
||||||
{
|
{
|
||||||
log("API Error: while getting user id, first user object `_id` "
|
qDebug() << "API Error: while getting user id, first user "
|
||||||
"key "
|
"object `_id` key is not a string";
|
||||||
"is not a "
|
|
||||||
"string");
|
|
||||||
successCallback("");
|
successCallback("");
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
@ -73,8 +71,8 @@ void TwitchApi::findUserName(const QString userid,
|
||||||
auto name = root.value("name");
|
auto name = root.value("name");
|
||||||
if (!name.isString())
|
if (!name.isString())
|
||||||
{
|
{
|
||||||
log("API Error: while getting user name, `name` is not a "
|
qDebug() << "API Error: while getting user name, `name` is not "
|
||||||
"string");
|
"a string";
|
||||||
successCallback("");
|
successCallback("");
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "common/Outcome.hpp"
|
#include "common/Outcome.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/Emote.hpp"
|
#include "messages/Emote.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "controllers/notifications/NotificationController.hpp"
|
#include "controllers/notifications/NotificationController.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "providers/bttv/BttvEmotes.hpp"
|
#include "providers/bttv/BttvEmotes.hpp"
|
||||||
#include "providers/bttv/LoadBttvChannelEmote.hpp"
|
#include "providers/bttv/LoadBttvChannelEmote.hpp"
|
||||||
|
@ -92,7 +91,7 @@ TwitchChannel::TwitchChannel(const QString &name,
|
||||||
, mod_(false)
|
, mod_(false)
|
||||||
, titleRefreshedTime_(QTime::currentTime().addSecs(-TITLE_REFRESH_PERIOD))
|
, titleRefreshedTime_(QTime::currentTime().addSecs(-TITLE_REFRESH_PERIOD))
|
||||||
{
|
{
|
||||||
log("[TwitchChannel:{}] Opened", name);
|
qDebug() << "[TwitchChannel" << name << "] Opened";
|
||||||
|
|
||||||
this->liveStatusChanged.connect([this]() {
|
this->liveStatusChanged.connect([this]() {
|
||||||
if (this->isLive() == 1)
|
if (this->isLive() == 1)
|
||||||
|
@ -194,7 +193,8 @@ void TwitchChannel::sendMessage(const QString &message)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log("[TwitchChannel:{}] Send message: {}", this->getName(), message);
|
qDebug() << "[TwitchChannel" << this->getName()
|
||||||
|
<< "] Send message:" << message;
|
||||||
|
|
||||||
// Do last message processing
|
// Do last message processing
|
||||||
QString parsedMessage = app->emotes->emojis.replaceShortCodes(message);
|
QString parsedMessage = app->emotes->emojis.replaceShortCodes(message);
|
||||||
|
@ -491,8 +491,8 @@ void TwitchChannel::refreshLiveStatus()
|
||||||
|
|
||||||
if (roomID.isEmpty())
|
if (roomID.isEmpty())
|
||||||
{
|
{
|
||||||
log("[TwitchChannel:{}] Refreshing live status (Missing ID)",
|
qDebug() << "[TwitchChannel" << this->getName()
|
||||||
this->getName());
|
<< "] Refreshing live status (Missing ID)";
|
||||||
this->setLive(false);
|
this->setLive(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -517,13 +517,13 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
||||||
{
|
{
|
||||||
if (!document.IsObject())
|
if (!document.IsObject())
|
||||||
{
|
{
|
||||||
log("[TwitchChannel:refreshLiveStatus] root is not an object");
|
qDebug() << "[TwitchChannel:refreshLiveStatus] root is not an object";
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!document.HasMember("stream"))
|
if (!document.HasMember("stream"))
|
||||||
{
|
{
|
||||||
log("[TwitchChannel:refreshLiveStatus] Missing stream in root");
|
qDebug() << "[TwitchChannel:refreshLiveStatus] Missing stream in root";
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,7 +539,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
||||||
if (!stream.HasMember("viewers") || !stream.HasMember("game") ||
|
if (!stream.HasMember("viewers") || !stream.HasMember("game") ||
|
||||||
!stream.HasMember("channel") || !stream.HasMember("created_at"))
|
!stream.HasMember("channel") || !stream.HasMember("created_at"))
|
||||||
{
|
{
|
||||||
log("[TwitchChannel:refreshLiveStatus] Missing members in stream");
|
qDebug()
|
||||||
|
<< "[TwitchChannel:refreshLiveStatus] Missing members in stream";
|
||||||
this->setLive(false);
|
this->setLive(false);
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
@ -548,8 +549,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
||||||
|
|
||||||
if (!streamChannel.IsObject() || !streamChannel.HasMember("status"))
|
if (!streamChannel.IsObject() || !streamChannel.HasMember("status"))
|
||||||
{
|
{
|
||||||
log("[TwitchChannel:refreshLiveStatus] Missing member \"status\" in "
|
qDebug() << "[TwitchChannel:refreshLiveStatus] Missing member "
|
||||||
"channel");
|
"\"status\" in channel";
|
||||||
return Failure;
|
return Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -743,6 +744,12 @@ void TwitchChannel::refreshCheerEmotes()
|
||||||
NetworkRequest::twitchRequest(url)
|
NetworkRequest::twitchRequest(url)
|
||||||
.onSuccess([this,
|
.onSuccess([this,
|
||||||
weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||||
|
auto shared = weak.lock();
|
||||||
|
if (!shared)
|
||||||
|
{
|
||||||
|
return Failure;
|
||||||
|
}
|
||||||
|
|
||||||
auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson());
|
auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson());
|
||||||
std::vector<CheerEmoteSet> emoteSets;
|
std::vector<CheerEmoteSet> emoteSets;
|
||||||
|
|
||||||
|
@ -836,7 +843,7 @@ boost::optional<CheerEmote> TwitchChannel::cheerEmote(const QString &string)
|
||||||
int bitAmount = amount.toInt(&ok);
|
int bitAmount = amount.toInt(&ok);
|
||||||
if (!ok)
|
if (!ok)
|
||||||
{
|
{
|
||||||
log("Error parsing bit amount in cheerEmote");
|
qDebug() << "Error parsing bit amount in cheerEmote";
|
||||||
}
|
}
|
||||||
for (const auto &emote : set.cheerEmotes)
|
for (const auto &emote : set.cheerEmotes)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "debug/Benchmark.hpp"
|
#include "debug/Benchmark.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/Emote.hpp"
|
#include "messages/Emote.hpp"
|
||||||
#include "messages/Image.hpp"
|
#include "messages/Image.hpp"
|
||||||
#include "util/RapidjsonHelpers.hpp"
|
#include "util/RapidjsonHelpers.hpp"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#include "providers/twitch/TwitchHelpers.hpp"
|
#include "providers/twitch/TwitchHelpers.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
@ -7,7 +6,7 @@ bool trimChannelName(const QString &channelName, QString &outChannelName)
|
||||||
{
|
{
|
||||||
if (channelName.length() < 2)
|
if (channelName.length() < 2)
|
||||||
{
|
{
|
||||||
log("channel name length below 2");
|
qDebug() << "channel name length below 2";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#include "controllers/highlights/HighlightController.hpp"
|
#include "controllers/highlights/HighlightController.hpp"
|
||||||
#include "controllers/ignores/IgnoreController.hpp"
|
#include "controllers/ignores/IgnoreController.hpp"
|
||||||
#include "controllers/pings/PingController.hpp"
|
#include "controllers/pings/PingController.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "providers/chatterino/ChatterinoBadges.hpp"
|
#include "providers/chatterino/ChatterinoBadges.hpp"
|
||||||
#include "providers/twitch/TwitchBadges.hpp"
|
#include "providers/twitch/TwitchBadges.hpp"
|
||||||
|
@ -65,6 +64,25 @@ QColor getRandomColor(const QVariant &userId)
|
||||||
return twitchUsernameColors[colorIndex];
|
return twitchUsernameColors[colorIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUrl getFallbackHighlightSound()
|
||||||
|
{
|
||||||
|
using namespace chatterino;
|
||||||
|
|
||||||
|
QString path = getSettings()->pathHighlightSound;
|
||||||
|
bool fileExists = QFileInfo::exists(path) && QFileInfo(path).isFile();
|
||||||
|
|
||||||
|
// Use fallback sound when checkbox is not checked
|
||||||
|
// or custom file doesn't exist
|
||||||
|
if (getSettings()->customHighlightSound && fileExists)
|
||||||
|
{
|
||||||
|
return QUrl::fromLocalFile(path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return QUrl("qrc:/sounds/ping2.wav");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
@ -90,8 +108,6 @@ namespace {
|
||||||
QStringList parts = badgeInfo.split('/');
|
QStringList parts = badgeInfo.split('/');
|
||||||
if (parts.size() != 2)
|
if (parts.size() != 2)
|
||||||
{
|
{
|
||||||
log("Skipping badge-info because it split weird: {}",
|
|
||||||
badgeInfo);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +126,6 @@ namespace {
|
||||||
QStringList parts = badge.split('/');
|
QStringList parts = badge.split('/');
|
||||||
if (parts.size() != 2)
|
if (parts.size() != 2)
|
||||||
{
|
{
|
||||||
log("Skipping badge because it split weird: {}", badge);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,8 +174,8 @@ bool TwitchMessageBuilder::isIgnored() const
|
||||||
{
|
{
|
||||||
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_))
|
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_))
|
||||||
{
|
{
|
||||||
log("Blocking message because it contains ignored phrase {}",
|
qDebug() << "Blocking message because it contains ignored phrase"
|
||||||
phrase.getPattern());
|
<< phrase.getPattern();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,8 +205,8 @@ bool TwitchMessageBuilder::isIgnored() const
|
||||||
case ShowIgnoredUsersMessages::Never:
|
case ShowIgnoredUsersMessages::Never:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
log("Blocking message because it's from blocked user {}",
|
qDebug() << "Blocking message because it's from blocked user"
|
||||||
user.name);
|
<< user.name;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,17 +252,11 @@ void TwitchMessageBuilder::triggerHighlights()
|
||||||
if (auto player = getPlayer())
|
if (auto player = getPlayer())
|
||||||
{
|
{
|
||||||
// update the media player url if necessary
|
// update the media player url if necessary
|
||||||
QUrl highlightSoundUrl =
|
if (currentPlayerUrl != this->highlightSoundUrl_)
|
||||||
getSettings()->customHighlightSound
|
|
||||||
? QUrl::fromLocalFile(
|
|
||||||
getSettings()->pathHighlightSound.getValue())
|
|
||||||
: QUrl("qrc:/sounds/ping2.wav");
|
|
||||||
|
|
||||||
if (currentPlayerUrl != highlightSoundUrl)
|
|
||||||
{
|
{
|
||||||
player->setMedia(highlightSoundUrl);
|
player->setMedia(this->highlightSoundUrl_);
|
||||||
|
|
||||||
currentPlayerUrl = highlightSoundUrl;
|
currentPlayerUrl = this->highlightSoundUrl_;
|
||||||
}
|
}
|
||||||
|
|
||||||
player->play();
|
player->play();
|
||||||
|
@ -427,8 +436,8 @@ void TwitchMessageBuilder::addWords(
|
||||||
auto emoteImage = std::get<1>(*currentTwitchEmote);
|
auto emoteImage = std::get<1>(*currentTwitchEmote);
|
||||||
if (emoteImage == nullptr)
|
if (emoteImage == nullptr)
|
||||||
{
|
{
|
||||||
log("emoteImage nullptr {}",
|
qDebug() << "emoteImage nullptr"
|
||||||
std::get<2>(*currentTwitchEmote).string);
|
<< std::get<2>(*currentTwitchEmote).string;
|
||||||
}
|
}
|
||||||
this->emplace<EmoteElement>(emoteImage,
|
this->emplace<EmoteElement>(emoteImage,
|
||||||
MessageElementFlag::TwitchEmote);
|
MessageElementFlag::TwitchEmote);
|
||||||
|
@ -771,7 +780,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
|
||||||
{
|
{
|
||||||
if (std::get<1>(*copy) == nullptr)
|
if (std::get<1>(*copy) == nullptr)
|
||||||
{
|
{
|
||||||
log("remem nullptr {}", std::get<2>(*copy).string);
|
qDebug() << "remem nullptr" << std::get<2>(*copy).string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::vector<std::tuple<int, EmotePtr, EmoteName>> v(
|
std::vector<std::tuple<int, EmotePtr, EmoteName>> v(
|
||||||
|
@ -809,7 +818,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
|
||||||
{
|
{
|
||||||
if (emote.second == nullptr)
|
if (emote.second == nullptr)
|
||||||
{
|
{
|
||||||
log("emote null {}", emote.first.string);
|
qDebug() << "emote null" << emote.first.string;
|
||||||
}
|
}
|
||||||
twitchEmotes.push_back(std::tuple<int, EmotePtr, EmoteName>{
|
twitchEmotes.push_back(std::tuple<int, EmotePtr, EmoteName>{
|
||||||
startIndex + pos, emote.second, emote.first});
|
startIndex + pos, emote.second, emote.first});
|
||||||
|
@ -872,7 +881,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
|
||||||
{
|
{
|
||||||
if (std::get<1>(tup) == nullptr)
|
if (std::get<1>(tup) == nullptr)
|
||||||
{
|
{
|
||||||
log("v nullptr {}", std::get<2>(tup).string);
|
qDebug() << "v nullptr" << std::get<2>(tup).string;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
QRegularExpression emoteregex(
|
QRegularExpression emoteregex(
|
||||||
|
@ -941,7 +950,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
|
||||||
{
|
{
|
||||||
if (std::get<1>(tup) == nullptr)
|
if (std::get<1>(tup) == nullptr)
|
||||||
{
|
{
|
||||||
log("v nullptr {}", std::get<2>(tup).string);
|
qDebug() << "v nullptr" << std::get<2>(tup).string;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
QRegularExpression emoteregex(
|
QRegularExpression emoteregex(
|
||||||
|
@ -971,6 +980,39 @@ void TwitchMessageBuilder::parseHighlights()
|
||||||
{
|
{
|
||||||
auto app = getApp();
|
auto app = getApp();
|
||||||
|
|
||||||
|
if (this->message().flags.has(MessageFlag::Subscription) &&
|
||||||
|
getSettings()->enableSubHighlight)
|
||||||
|
{
|
||||||
|
if (getSettings()->enableSubHighlightTaskbar)
|
||||||
|
{
|
||||||
|
this->highlightAlert_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getSettings()->enableSubHighlightSound)
|
||||||
|
{
|
||||||
|
this->highlightSound_ = true;
|
||||||
|
|
||||||
|
// Use custom sound if set, otherwise use fallback
|
||||||
|
if (!getSettings()->subHighlightSoundUrl.getValue().isEmpty())
|
||||||
|
{
|
||||||
|
this->highlightSoundUrl_ =
|
||||||
|
QUrl(getSettings()->subHighlightSoundUrl.getValue());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->highlightSoundUrl_ = getFallbackHighlightSound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->message().flags.set(MessageFlag::Highlighted);
|
||||||
|
this->message().highlightColor =
|
||||||
|
ColorProvider::instance().color(ColorType::Subscription);
|
||||||
|
|
||||||
|
// This message was a subscription.
|
||||||
|
// Don't check for any other highlight phrases.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto currentUser = app->accounts->twitch.getCurrent();
|
auto currentUser = app->accounts->twitch.getCurrent();
|
||||||
|
|
||||||
QString currentUsername = currentUser->getUserName();
|
QString currentUsername = currentUser->getUserName();
|
||||||
|
@ -981,6 +1023,39 @@ void TwitchMessageBuilder::parseHighlights()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Highlight because it's a whisper
|
||||||
|
if (this->args.isReceivedWhisper && getSettings()->enableWhisperHighlight)
|
||||||
|
{
|
||||||
|
if (getSettings()->enableWhisperHighlightTaskbar)
|
||||||
|
{
|
||||||
|
this->highlightAlert_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getSettings()->enableWhisperHighlightSound)
|
||||||
|
{
|
||||||
|
this->highlightSound_ = true;
|
||||||
|
|
||||||
|
// Use custom sound if set, otherwise use fallback
|
||||||
|
if (!getSettings()->whisperHighlightSoundUrl.getValue().isEmpty())
|
||||||
|
{
|
||||||
|
this->highlightSoundUrl_ =
|
||||||
|
QUrl(getSettings()->whisperHighlightSoundUrl.getValue());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->highlightSoundUrl_ = getFallbackHighlightSound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->message().highlightColor =
|
||||||
|
ColorProvider::instance().color(ColorType::Whisper);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Do _NOT_ return yet, we might want to apply phrase/user name
|
||||||
|
* highlights (which override whisper color/sound).
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<HighlightPhrase> userHighlights =
|
std::vector<HighlightPhrase> userHighlights =
|
||||||
app->highlights->highlightedUsers.cloneVector();
|
app->highlights->highlightedUsers.cloneVector();
|
||||||
|
|
||||||
|
@ -991,29 +1066,39 @@ void TwitchMessageBuilder::parseHighlights()
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
log("Highlight because user {} sent a message",
|
qDebug() << "Highlight because user" << this->ircMessage->nick()
|
||||||
this->ircMessage->nick());
|
<< "sent a message";
|
||||||
if (!this->highlightVisual_)
|
|
||||||
{
|
|
||||||
this->highlightVisual_ = true;
|
|
||||||
this->message().flags.set(MessageFlag::Highlighted);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userHighlight.getAlert())
|
this->message().flags.set(MessageFlag::Highlighted);
|
||||||
|
this->message().highlightColor = userHighlight.getColor();
|
||||||
|
|
||||||
|
if (userHighlight.hasAlert())
|
||||||
{
|
{
|
||||||
this->highlightAlert_ = true;
|
this->highlightAlert_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userHighlight.getSound())
|
if (userHighlight.hasSound())
|
||||||
{
|
{
|
||||||
this->highlightSound_ = true;
|
this->highlightSound_ = true;
|
||||||
|
// Use custom sound if set, otherwise use the fallback sound
|
||||||
|
if (userHighlight.hasCustomSound())
|
||||||
|
{
|
||||||
|
this->highlightSoundUrl_ = userHighlight.getSoundUrl();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->highlightSoundUrl_ = getFallbackHighlightSound();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->highlightAlert_ && this->highlightSound_)
|
if (this->highlightAlert_ && this->highlightSound_)
|
||||||
{
|
{
|
||||||
// Break if no further action can be taken from other
|
/*
|
||||||
// usernames Mostly used for regex stuff
|
* User name highlights "beat" highlight phrases: If a message has
|
||||||
break;
|
* all attributes (color, taskbar flashing, sound) set, highlight
|
||||||
|
* phrases will not be checked.
|
||||||
|
*/
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1032,7 +1117,9 @@ void TwitchMessageBuilder::parseHighlights()
|
||||||
{
|
{
|
||||||
HighlightPhrase selfHighlight(
|
HighlightPhrase selfHighlight(
|
||||||
currentUsername, getSettings()->enableSelfHighlightTaskbar,
|
currentUsername, getSettings()->enableSelfHighlightTaskbar,
|
||||||
getSettings()->enableSelfHighlightSound, false, false);
|
getSettings()->enableSelfHighlightSound, false, false,
|
||||||
|
getSettings()->selfHighlightSoundUrl.getValue(),
|
||||||
|
ColorProvider::instance().color(ColorType::SelfHighlight));
|
||||||
activeHighlights.emplace_back(std::move(selfHighlight));
|
activeHighlights.emplace_back(std::move(selfHighlight));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1044,45 +1131,43 @@ void TwitchMessageBuilder::parseHighlights()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
log("Highlight because {} matches {}", this->originalMessage_,
|
qDebug() << "Highlight because" << this->originalMessage_ << "matches"
|
||||||
highlight.getPattern());
|
<< highlight.getPattern();
|
||||||
if (!this->highlightVisual_)
|
|
||||||
{
|
|
||||||
this->highlightVisual_ = true;
|
|
||||||
this->message().flags.set(MessageFlag::Highlighted);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (highlight.getAlert())
|
this->message().flags.set(MessageFlag::Highlighted);
|
||||||
|
this->message().highlightColor = highlight.getColor();
|
||||||
|
|
||||||
|
if (highlight.hasAlert())
|
||||||
{
|
{
|
||||||
this->highlightAlert_ = true;
|
this->highlightAlert_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (highlight.getSound())
|
// Only set highlightSound_ if it hasn't been set by username
|
||||||
|
// highlights already.
|
||||||
|
if (highlight.hasSound() && !this->highlightSound_)
|
||||||
{
|
{
|
||||||
this->highlightSound_ = true;
|
this->highlightSound_ = true;
|
||||||
|
|
||||||
|
// Use custom sound if set, otherwise use fallback sound
|
||||||
|
if (highlight.hasCustomSound())
|
||||||
|
{
|
||||||
|
this->highlightSoundUrl_ = highlight.getSoundUrl();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->highlightSoundUrl_ = getFallbackHighlightSound();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->highlightAlert_ && this->highlightSound_)
|
if (this->highlightAlert_ && this->highlightSound_)
|
||||||
{
|
{
|
||||||
// Break if no further action can be taken from other
|
/*
|
||||||
// highlights This might change if highlights can have
|
* Break once no further attributes (taskbar, sound) can be
|
||||||
// custom colors/sounds/actions
|
* applied.
|
||||||
|
*/
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Highlight because it's a whisper
|
|
||||||
if (this->args.isReceivedWhisper && getSettings()->enableWhisperHighlight)
|
|
||||||
{
|
|
||||||
if (getSettings()->enableWhisperHighlightTaskbar)
|
|
||||||
{
|
|
||||||
this->highlightAlert_ = true;
|
|
||||||
}
|
|
||||||
if (getSettings()->enableWhisperHighlightSound)
|
|
||||||
{
|
|
||||||
this->highlightSound_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TwitchMessageBuilder::appendTwitchEmote(
|
void TwitchMessageBuilder::appendTwitchEmote(
|
||||||
|
@ -1130,7 +1215,7 @@ void TwitchMessageBuilder::appendTwitchEmote(
|
||||||
start, app->emotes->twitch.getOrCreateEmote(id, name), name};
|
start, app->emotes->twitch.getOrCreateEmote(id, name), name};
|
||||||
if (std::get<1>(tup) == nullptr)
|
if (std::get<1>(tup) == nullptr)
|
||||||
{
|
{
|
||||||
log("nullptr {}", std::get<2>(tup).string);
|
qDebug() << "nullptr" << std::get<2>(tup).string;
|
||||||
}
|
}
|
||||||
vec.push_back(std::move(tup));
|
vec.push_back(std::move(tup));
|
||||||
}
|
}
|
||||||
|
@ -1216,7 +1301,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
||||||
auto badgeEmote = this->getTwitchBadge(badge);
|
auto badgeEmote = this->getTwitchBadge(badge);
|
||||||
if (!badgeEmote)
|
if (!badgeEmote)
|
||||||
{
|
{
|
||||||
log("No channel/global variant found {}", badge.key_);
|
qDebug() << "No channel/global variant found" << badge.key_;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto tooltip = (*badgeEmote)->tooltip.string;
|
auto tooltip = (*badgeEmote)->tooltip.string;
|
||||||
|
|
|
@ -92,9 +92,10 @@ private:
|
||||||
|
|
||||||
const bool action_ = false;
|
const bool action_ = false;
|
||||||
|
|
||||||
bool highlightVisual_ = false;
|
|
||||||
bool highlightAlert_ = false;
|
bool highlightAlert_ = false;
|
||||||
bool highlightSound_ = false;
|
bool highlightSound_ = false;
|
||||||
|
|
||||||
|
QUrl highlightSoundUrl_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "singletons/Logging.hpp"
|
#include "singletons/Logging.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Resources.hpp"
|
#include "singletons/Resources.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
|
|
|
@ -146,18 +146,39 @@ public:
|
||||||
/// Highlighting
|
/// Highlighting
|
||||||
// BoolSetting enableHighlights = {"/highlighting/enabled", true};
|
// BoolSetting enableHighlights = {"/highlighting/enabled", true};
|
||||||
BoolSetting customHighlightSound = {"/highlighting/useCustomSound", false};
|
BoolSetting customHighlightSound = {"/highlighting/useCustomSound", false};
|
||||||
|
|
||||||
BoolSetting enableSelfHighlight = {
|
BoolSetting enableSelfHighlight = {
|
||||||
"/highlighting/selfHighlight/nameIsHighlightKeyword", true};
|
"/highlighting/selfHighlight/nameIsHighlightKeyword", true};
|
||||||
BoolSetting enableSelfHighlightSound = {
|
BoolSetting enableSelfHighlightSound = {
|
||||||
"/highlighting/selfHighlight/enableSound", true};
|
"/highlighting/selfHighlight/enableSound", true};
|
||||||
BoolSetting enableSelfHighlightTaskbar = {
|
BoolSetting enableSelfHighlightTaskbar = {
|
||||||
"/highlighting/selfHighlight/enableTaskbarFlashing", true};
|
"/highlighting/selfHighlight/enableTaskbarFlashing", true};
|
||||||
|
QStringSetting selfHighlightSoundUrl = {
|
||||||
|
"/highlighting/selfHighlightSoundUrl", ""};
|
||||||
|
QStringSetting selfHighlightColor = {"/highlighting/selfHighlightColor",
|
||||||
|
""};
|
||||||
|
|
||||||
BoolSetting enableWhisperHighlight = {
|
BoolSetting enableWhisperHighlight = {
|
||||||
"/highlighting/whisperHighlight/whispersHighlighted", true};
|
"/highlighting/whisperHighlight/whispersHighlighted", true};
|
||||||
BoolSetting enableWhisperHighlightSound = {
|
BoolSetting enableWhisperHighlightSound = {
|
||||||
"/highlighting/whisperHighlight/enableSound", false};
|
"/highlighting/whisperHighlight/enableSound", false};
|
||||||
BoolSetting enableWhisperHighlightTaskbar = {
|
BoolSetting enableWhisperHighlightTaskbar = {
|
||||||
"/highlighting/whisperHighlight/enableTaskbarFlashing", false};
|
"/highlighting/whisperHighlight/enableTaskbarFlashing", false};
|
||||||
|
QStringSetting whisperHighlightSoundUrl = {
|
||||||
|
"/highlighting/whisperHighlightSoundUrl", ""};
|
||||||
|
QStringSetting whisperHighlightColor = {
|
||||||
|
"/highlighting/whisperHighlightColor", ""};
|
||||||
|
|
||||||
|
BoolSetting enableSubHighlight = {
|
||||||
|
"/highlighting/subHighlight/subsHighlighted", true};
|
||||||
|
BoolSetting enableSubHighlightSound = {
|
||||||
|
"/highlighting/subHighlight/enableSound", false};
|
||||||
|
BoolSetting enableSubHighlightTaskbar = {
|
||||||
|
"/highlighting/subHighlight/enableTaskbarFlashing", false};
|
||||||
|
QStringSetting subHighlightSoundUrl = {"/highlighting/subHighlightSoundUrl",
|
||||||
|
""};
|
||||||
|
QStringSetting subHighlightColor = {"/highlighting/subHighlightColor", ""};
|
||||||
|
|
||||||
QStringSetting highlightColor = {"/highlighting/color", ""};
|
QStringSetting highlightColor = {"/highlighting/color", ""};
|
||||||
|
|
||||||
BoolSetting longAlerts = {"/highlighting/alerts", false};
|
BoolSetting longAlerts = {"/highlighting/alerts", false};
|
||||||
|
@ -168,7 +189,7 @@ public:
|
||||||
QStringSetting logPath = {"/logging/path", ""};
|
QStringSetting logPath = {"/logging/path", ""};
|
||||||
|
|
||||||
QStringSetting pathHighlightSound = {"/highlighting/highlightSoundPath",
|
QStringSetting pathHighlightSound = {"/highlighting/highlightSoundPath",
|
||||||
"qrc:/sounds/ping2.wav"};
|
""};
|
||||||
|
|
||||||
BoolSetting highlightAlwaysPlaySound = {"/highlighting/alwaysPlaySound",
|
BoolSetting highlightAlwaysPlaySound = {"/highlighting/alwaysPlaySound",
|
||||||
false};
|
false};
|
||||||
|
@ -228,6 +249,20 @@ public:
|
||||||
IntSetting lastSelectChannelTab = {"/ui/lastSelectChannelTab", 0};
|
IntSetting lastSelectChannelTab = {"/ui/lastSelectChannelTab", 0};
|
||||||
IntSetting lastSelectIrcConn = {"/ui/lastSelectIrcConn", 0};
|
IntSetting lastSelectIrcConn = {"/ui/lastSelectIrcConn", 0};
|
||||||
|
|
||||||
|
// Similarity
|
||||||
|
BoolSetting similarityEnabled = {"/similarity/similarityEnabled", false};
|
||||||
|
BoolSetting colorSimilarDisabled = {"/similarity/colorSimilarDisabled",
|
||||||
|
true};
|
||||||
|
BoolSetting hideSimilar = {"/similarity/hideSimilar", false};
|
||||||
|
BoolSetting hideSimilarMyself = {"/similarity/hideSimilarMyself", false};
|
||||||
|
BoolSetting shownSimilarTriggerHighlights = {
|
||||||
|
"/similarity/shownSimilarTriggerHighlights", false};
|
||||||
|
FloatSetting similarityPercentage = {"/similarity/similarityPercentage",
|
||||||
|
0.9f};
|
||||||
|
IntSetting hideSimilarMaxDelay = {"/similarity/hideSimilarMaxDelay", 5};
|
||||||
|
IntSetting hideSimilarMaxMessagesToCheck = {
|
||||||
|
"/similarity/hideSimilarMaxMessagesToCheck", 3};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateModerationActions();
|
void updateModerationActions();
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,9 +38,6 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
||||||
|
|
||||||
this->splits.resizeHandle = QColor(0, 148, 255, 0xff);
|
this->splits.resizeHandle = QColor(0, 148, 255, 0xff);
|
||||||
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x50);
|
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x50);
|
||||||
|
|
||||||
// Highlighted Messages: theme support quick-fix
|
|
||||||
this->messages.backgrounds.highlighted = QColor("#BD8489");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -49,11 +46,10 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
||||||
|
|
||||||
this->splits.resizeHandle = QColor(0, 148, 255, 0x70);
|
this->splits.resizeHandle = QColor(0, 148, 255, 0x70);
|
||||||
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x20);
|
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x20);
|
||||||
|
|
||||||
// Highlighted Messages: theme support quick-fix
|
|
||||||
this->messages.backgrounds.highlighted = QColor("#4B282C");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->messages.backgrounds.highlighted = QColor(140, 84, 89, 127);
|
||||||
|
|
||||||
this->splits.header.background = getColor(0, sat, flat ? 1 : 0.9);
|
this->splits.header.background = getColor(0, sat, flat ? 1 : 0.9);
|
||||||
this->splits.header.border = getColor(0, sat, flat ? 1 : 0.85);
|
this->splits.header.border = getColor(0, sat, flat ? 1 : 0.85);
|
||||||
this->splits.header.text = this->messages.textColors.regular;
|
this->splits.header.text = this->messages.textColors.regular;
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "debug/AssertInGuiThread.hpp"
|
#include "debug/AssertInGuiThread.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
#include "providers/irc/Irc2.hpp"
|
#include "providers/irc/Irc2.hpp"
|
||||||
#include "providers/irc/IrcChannel2.hpp"
|
#include "providers/irc/IrcChannel2.hpp"
|
||||||
|
@ -264,7 +263,7 @@ Window *WindowManager::windowAt(int index)
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
log("getting window at bad index {}", index);
|
qDebug() << "getting window at bad index" << index;
|
||||||
|
|
||||||
return this->windows_.at(index);
|
return this->windows_.at(index);
|
||||||
}
|
}
|
||||||
|
@ -432,7 +431,7 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
|
||||||
|
|
||||||
void WindowManager::save()
|
void WindowManager::save()
|
||||||
{
|
{
|
||||||
log("[WindowManager] Saving");
|
qDebug() << "[WindowManager] Saving";
|
||||||
assertInGuiThread();
|
assertInGuiThread();
|
||||||
QJsonDocument document;
|
QJsonDocument document;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "LoggingChannel.hpp"
|
#include "LoggingChannel.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "singletons/Paths.hpp"
|
#include "singletons/Paths.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
|
||||||
|
@ -64,13 +63,13 @@ void LoggingChannel::openLogFile()
|
||||||
|
|
||||||
if (!QDir().mkpath(directory))
|
if (!QDir().mkpath(directory))
|
||||||
{
|
{
|
||||||
log("Unable to create logging path");
|
qDebug() << "Unable to create logging path";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open file handle to log file of current date
|
// Open file handle to log file of current date
|
||||||
QString fileName = directory + QDir::separator() + baseFileName;
|
QString fileName = directory + QDir::separator() + baseFileName;
|
||||||
log("Logging to {}", fileName);
|
qDebug() << "Logging to" << fileName;
|
||||||
this->fileHandle.setFileName(fileName);
|
this->fileHandle.setFileName(fileName);
|
||||||
|
|
||||||
this->fileHandle.open(QIODevice::Append);
|
this->fileHandle.open(QIODevice::Append);
|
||||||
|
|
16
src/util/Clipboard.cpp
Normal file
16
src/util/Clipboard.cpp
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#include "util/Clipboard.hpp"
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
void crossPlatformCopy(const QString &text)
|
||||||
|
{
|
||||||
|
auto clipboard = QApplication::clipboard();
|
||||||
|
clipboard->setText(text);
|
||||||
|
if (clipboard->supportsSelection())
|
||||||
|
{
|
||||||
|
clipboard->setText(text, QClipboard::Selection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
9
src/util/Clipboard.hpp
Normal file
9
src/util/Clipboard.hpp
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
void crossPlatformCopy(const QString &text);
|
||||||
|
|
||||||
|
} // namespace chatterino
|
|
@ -1,16 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <fmt/format.h>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
auto fS(Args &&... args)
|
|
||||||
{
|
|
||||||
return fmt::format(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString generateUuid();
|
QString generateUuid();
|
||||||
|
|
||||||
QString formatRichLink(const QString &url, bool file = false);
|
QString formatRichLink(const QString &url, bool file = false);
|
||||||
|
@ -21,13 +14,3 @@ QString formatRichNamedLink(const QString &url, const QString &name,
|
||||||
QString shortenString(const QString &str, unsigned maxWidth = 50);
|
QString shortenString(const QString &str, unsigned maxWidth = 50);
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
||||||
namespace fmt {
|
|
||||||
|
|
||||||
// format_arg for QString
|
|
||||||
inline void format_arg(BasicFormatter<char> &f, const char *&, const QString &v)
|
|
||||||
{
|
|
||||||
f.writer().write("{}", v.toStdString());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace fmt
|
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
|
||||||
#include "debug/Log.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
namespace {
|
namespace {
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
@ -63,7 +61,7 @@ namespace {
|
||||||
if (command.isNull())
|
if (command.isNull())
|
||||||
return QString();
|
return QString();
|
||||||
|
|
||||||
log(command);
|
qDebug() << command;
|
||||||
|
|
||||||
// inject switch to enable private browsing
|
// inject switch to enable private browsing
|
||||||
command = injectPrivateSwitch(command);
|
command = injectPrivateSwitch(command);
|
||||||
|
|
|
@ -22,6 +22,19 @@ static void setStringItem(QStandardItem *item, const QString &value,
|
||||||
(editable ? (Qt::ItemIsEditable) : 0)));
|
(editable ? (Qt::ItemIsEditable) : 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void setFilePathItem(QStandardItem *item, const QUrl &value)
|
||||||
|
{
|
||||||
|
item->setData(value, Qt::UserRole);
|
||||||
|
item->setData(value.fileName(), Qt::DisplayRole);
|
||||||
|
item->setFlags(Qt::ItemFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setColorItem(QStandardItem *item, const QColor &value)
|
||||||
|
{
|
||||||
|
item->setData(value, Qt::DecorationRole);
|
||||||
|
item->setFlags(Qt::ItemFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable));
|
||||||
|
}
|
||||||
|
|
||||||
static QStandardItem *emptyItem()
|
static QStandardItem *emptyItem()
|
||||||
{
|
{
|
||||||
auto *item = new QStandardItem();
|
auto *item = new QStandardItem();
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "util/StreamLink.hpp"
|
#include "util/StreamLink.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "util/Helpers.hpp"
|
#include "util/Helpers.hpp"
|
||||||
#include "widgets/dialogs/QualityPopup.hpp"
|
#include "widgets/dialogs/QualityPopup.hpp"
|
||||||
|
@ -92,7 +91,7 @@ namespace {
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
log("Error occured {}", err);
|
qDebug() << "Error occured" << err;
|
||||||
}
|
}
|
||||||
|
|
||||||
p->deleteLater();
|
p->deleteLater();
|
||||||
|
@ -119,7 +118,7 @@ void getStreamQualities(const QString &channelURL,
|
||||||
[=](int res) {
|
[=](int res) {
|
||||||
if (res != 0)
|
if (res != 0)
|
||||||
{
|
{
|
||||||
log("Got error code {}", res);
|
qDebug() << "Got error code" << res;
|
||||||
// return;
|
// return;
|
||||||
}
|
}
|
||||||
QString lastLine = QString(p->readAllStandardOutput());
|
QString lastLine = QString(p->readAllStandardOutput());
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#include "widgets/AccountSwitchPopup.hpp"
|
#include "widgets/AccountSwitchPopup.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "widgets/dialogs/SettingsDialog.hpp"
|
#include "widgets/dialogs/SettingsDialog.hpp"
|
||||||
|
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
#include "BaseSettings.hpp"
|
#include "BaseSettings.hpp"
|
||||||
#include "BaseTheme.hpp"
|
#include "BaseTheme.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "widgets/BaseWindow.hpp"
|
#include "widgets/BaseWindow.hpp"
|
||||||
|
|
||||||
#include <QChildEvent>
|
#include <QChildEvent>
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
#include "BaseSettings.hpp"
|
#include "BaseSettings.hpp"
|
||||||
#include "BaseTheme.hpp"
|
#include "BaseTheme.hpp"
|
||||||
#include "boost/algorithm/algorithm.hpp"
|
#include "boost/algorithm/algorithm.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "util/PostToThread.hpp"
|
#include "util/PostToThread.hpp"
|
||||||
#include "util/Shortcut.hpp"
|
#include "util/Shortcut.hpp"
|
||||||
#include "util/WindowsHelper.hpp"
|
#include "util/WindowsHelper.hpp"
|
||||||
|
@ -385,7 +384,7 @@ void BaseWindow::mousePressEvent(QMouseEvent *event)
|
||||||
|
|
||||||
if (!recursiveCheckMouseTracking(widget))
|
if (!recursiveCheckMouseTracking(widget))
|
||||||
{
|
{
|
||||||
log("Start moving");
|
qDebug() << "Start moving";
|
||||||
this->moving = true;
|
this->moving = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -402,7 +401,7 @@ void BaseWindow::mouseReleaseEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
if (this->moving)
|
if (this->moving)
|
||||||
{
|
{
|
||||||
log("Stop moving");
|
qDebug() << "Stop moving";
|
||||||
this->moving = false;
|
this->moving = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "widgets/Notebook.hpp"
|
#include "widgets/Notebook.hpp"
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
|
|
|
@ -282,18 +282,7 @@ void Scrollbar::paintEvent(QPaintEvent *)
|
||||||
|
|
||||||
if (!highlight.isNull())
|
if (!highlight.isNull())
|
||||||
{
|
{
|
||||||
QColor color = [&] {
|
QColor color = highlight.getColor();
|
||||||
switch (highlight.getColor())
|
|
||||||
{
|
|
||||||
case ScrollbarHighlight::Highlight:
|
|
||||||
return getApp()
|
|
||||||
->themes->scrollbars.highlights.highlight;
|
|
||||||
case ScrollbarHighlight::Subscription:
|
|
||||||
return getApp()
|
|
||||||
->themes->scrollbars.highlights.subscription;
|
|
||||||
}
|
|
||||||
return QColor();
|
|
||||||
}();
|
|
||||||
|
|
||||||
switch (highlight.getStyle())
|
switch (highlight.getStyle())
|
||||||
{
|
{
|
||||||
|
|
|
@ -351,6 +351,11 @@ void Window::addShortcuts()
|
||||||
getApp()->twitch.server->getOrAddChannel(si.channelName));
|
getApp()->twitch.server->getOrAddChannel(si.channelName));
|
||||||
splitContainer->appendSplit(split);
|
splitContainer->appendSplit(split);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
createWindowShortcut(this, "CTRL+H", [this] {
|
||||||
|
getSettings()->hideSimilar.setValue(!getSettings()->hideSimilar);
|
||||||
|
getApp()->windows->forceLayoutChannelViews();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::addMenuBar()
|
void Window::addMenuBar()
|
||||||
|
|
367
src/widgets/dialogs/ColorPickerDialog.cpp
Normal file
367
src/widgets/dialogs/ColorPickerDialog.cpp
Normal file
|
@ -0,0 +1,367 @@
|
||||||
|
#include "widgets/dialogs/ColorPickerDialog.hpp"
|
||||||
|
|
||||||
|
#include "providers/colors/ColorProvider.hpp"
|
||||||
|
#include "singletons/Theme.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
ColorPickerDialog::ColorPickerDialog(const QColor &initial, QWidget *parent)
|
||||||
|
: BasePopup(BaseWindow::EnableCustomFrame, parent)
|
||||||
|
, color_()
|
||||||
|
, dialogConfirmed_(false)
|
||||||
|
{
|
||||||
|
// This hosts the "business logic" and the dialog button box
|
||||||
|
LayoutCreator<QWidget> layoutWidget(this->getLayoutContainer());
|
||||||
|
auto layout = layoutWidget.setLayoutType<QVBoxLayout>().withoutMargin();
|
||||||
|
|
||||||
|
// This hosts the business logic: color picker and predefined colors
|
||||||
|
LayoutCreator<QWidget> contentCreator(new QWidget());
|
||||||
|
auto contents = contentCreator.setLayoutType<QHBoxLayout>();
|
||||||
|
|
||||||
|
// This hosts the predefined colors (and also the currently selected color)
|
||||||
|
LayoutCreator<QWidget> predefCreator(new QWidget());
|
||||||
|
auto predef = predefCreator.setLayoutType<QVBoxLayout>();
|
||||||
|
|
||||||
|
// Recently used colors
|
||||||
|
{
|
||||||
|
LayoutCreator<QWidget> gridCreator(new QWidget());
|
||||||
|
this->initRecentColors(gridCreator);
|
||||||
|
|
||||||
|
predef.append(gridCreator.getElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default colors
|
||||||
|
{
|
||||||
|
LayoutCreator<QWidget> gridCreator(new QWidget());
|
||||||
|
this->initDefaultColors(gridCreator);
|
||||||
|
|
||||||
|
predef.append(gridCreator.getElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently selected color
|
||||||
|
{
|
||||||
|
LayoutCreator<QWidget> curColorCreator(new QWidget());
|
||||||
|
auto curColor = curColorCreator.setLayoutType<QHBoxLayout>();
|
||||||
|
curColor.emplace<QLabel>("Selected:").assign(&this->ui_.selected.label);
|
||||||
|
curColor.emplace<ColorButton>(initial).assign(
|
||||||
|
&this->ui_.selected.color);
|
||||||
|
|
||||||
|
predef.append(curColor.getElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
contents.append(predef.getElement());
|
||||||
|
|
||||||
|
// Color picker
|
||||||
|
{
|
||||||
|
LayoutCreator<QWidget> obj(new QWidget());
|
||||||
|
auto vbox = obj.setLayoutType<QVBoxLayout>();
|
||||||
|
|
||||||
|
// The actual color picker
|
||||||
|
{
|
||||||
|
LayoutCreator<QWidget> cpCreator(new QWidget());
|
||||||
|
this->initColorPicker(cpCreator);
|
||||||
|
|
||||||
|
vbox.append(cpCreator.getElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spin boxes
|
||||||
|
{
|
||||||
|
LayoutCreator<QWidget> sbCreator(new QWidget());
|
||||||
|
this->initSpinBoxes(sbCreator);
|
||||||
|
|
||||||
|
vbox.append(sbCreator.getElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML color
|
||||||
|
{
|
||||||
|
LayoutCreator<QWidget> htmlCreator(new QWidget());
|
||||||
|
this->initHtmlColor(htmlCreator);
|
||||||
|
|
||||||
|
vbox.append(htmlCreator.getElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
contents.append(obj.getElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
layout.append(contents.getElement());
|
||||||
|
|
||||||
|
// Dialog buttons
|
||||||
|
auto buttons =
|
||||||
|
layout.emplace<QHBoxLayout>().emplace<QDialogButtonBox>(this);
|
||||||
|
{
|
||||||
|
auto *button_ok = buttons->addButton(QDialogButtonBox::Ok);
|
||||||
|
QObject::connect(button_ok, &QPushButton::clicked,
|
||||||
|
[=](bool) { this->ok(); });
|
||||||
|
auto *button_cancel = buttons->addButton(QDialogButtonBox::Cancel);
|
||||||
|
QObject::connect(button_cancel, &QAbstractButton::clicked,
|
||||||
|
[=](bool) { this->close(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
this->themeChangedEvent();
|
||||||
|
this->selectColor(initial, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorPickerDialog::~ColorPickerDialog()
|
||||||
|
{
|
||||||
|
if (this->htmlColorValidator_)
|
||||||
|
{
|
||||||
|
this->htmlColorValidator_->deleteLater();
|
||||||
|
this->htmlColorValidator_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor ColorPickerDialog::selectedColor() const
|
||||||
|
{
|
||||||
|
if (!this->dialogConfirmed_)
|
||||||
|
{
|
||||||
|
// If the Cancel button was clicked, return the invalid color
|
||||||
|
return QColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this->color_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerDialog::closeEvent(QCloseEvent *)
|
||||||
|
{
|
||||||
|
this->closed.invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerDialog::themeChangedEvent()
|
||||||
|
{
|
||||||
|
BaseWindow::themeChangedEvent();
|
||||||
|
|
||||||
|
QString textCol = this->theme->splits.input.text.name(QColor::HexRgb);
|
||||||
|
QString bgCol = this->theme->splits.input.background.name(QColor::HexRgb);
|
||||||
|
|
||||||
|
// Labels
|
||||||
|
|
||||||
|
QString labelStyle = QString("color: %1;").arg(textCol);
|
||||||
|
|
||||||
|
this->ui_.recent.label->setStyleSheet(labelStyle);
|
||||||
|
this->ui_.def.label->setStyleSheet(labelStyle);
|
||||||
|
this->ui_.selected.label->setStyleSheet(labelStyle);
|
||||||
|
this->ui_.picker.htmlLabel->setStyleSheet(labelStyle);
|
||||||
|
|
||||||
|
for (auto spinBoxLabel : this->ui_.picker.spinBoxLabels)
|
||||||
|
{
|
||||||
|
spinBoxLabel->setStyleSheet(labelStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->ui_.picker.htmlEdit->setStyleSheet(
|
||||||
|
this->theme->splits.input.styleSheet);
|
||||||
|
|
||||||
|
// Styling spin boxes is too much effort
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerDialog::selectColor(const QColor &color, bool fromColorPicker)
|
||||||
|
{
|
||||||
|
if (color == this->color_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->color_ = color;
|
||||||
|
|
||||||
|
// Update UI elements
|
||||||
|
this->ui_.selected.color->setColor(this->color_);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Somewhat "ugly" hack to prevent feedback loop between widgets. Since
|
||||||
|
* this method is private, I'm okay with this being ugly.
|
||||||
|
*/
|
||||||
|
if (!fromColorPicker)
|
||||||
|
{
|
||||||
|
this->ui_.picker.colorPicker->setCol(this->color_.hslHue(),
|
||||||
|
this->color_.hslSaturation());
|
||||||
|
this->ui_.picker.luminancePicker->setCol(this->color_.hsvHue(),
|
||||||
|
this->color_.hsvSaturation(),
|
||||||
|
this->color_.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
this->ui_.picker.spinBoxes[SpinBox::RED]->setValue(this->color_.red());
|
||||||
|
this->ui_.picker.spinBoxes[SpinBox::GREEN]->setValue(this->color_.green());
|
||||||
|
this->ui_.picker.spinBoxes[SpinBox::BLUE]->setValue(this->color_.blue());
|
||||||
|
this->ui_.picker.spinBoxes[SpinBox::ALPHA]->setValue(this->color_.alpha());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Here, we are intentionally using HexRgb instead of HexArgb. Most online
|
||||||
|
* sites (or other applications) will likely not include the alpha channel
|
||||||
|
* in their output.
|
||||||
|
*/
|
||||||
|
this->ui_.picker.htmlEdit->setText(this->color_.name(QColor::HexRgb));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerDialog::ok()
|
||||||
|
{
|
||||||
|
this->dialogConfirmed_ = true;
|
||||||
|
this->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerDialog::initRecentColors(LayoutCreator<QWidget> &creator)
|
||||||
|
{
|
||||||
|
auto grid = creator.setLayoutType<QGridLayout>();
|
||||||
|
|
||||||
|
auto label = this->ui_.recent.label = new QLabel("Recently used:");
|
||||||
|
grid->addWidget(label, 0, 0, 1, -1);
|
||||||
|
|
||||||
|
const auto recentColors = ColorProvider::instance().recentColors();
|
||||||
|
auto it = recentColors.begin();
|
||||||
|
size_t ind = 0;
|
||||||
|
while (it != recentColors.end() && ind < MAX_RECENT_COLORS)
|
||||||
|
{
|
||||||
|
this->ui_.recent.colors.push_back(new ColorButton(*it, this));
|
||||||
|
auto *button = this->ui_.recent.colors[ind];
|
||||||
|
|
||||||
|
const int rowInd = (ind / RECENT_COLORS_PER_ROW) + 1;
|
||||||
|
const int columnInd = ind % RECENT_COLORS_PER_ROW;
|
||||||
|
|
||||||
|
grid->addWidget(button, rowInd, columnInd);
|
||||||
|
|
||||||
|
QObject::connect(button, &QPushButton::clicked,
|
||||||
|
[=] { this->selectColor(button->color(), false); });
|
||||||
|
|
||||||
|
++it;
|
||||||
|
++ind;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto spacer =
|
||||||
|
new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
|
||||||
|
grid->addItem(spacer, (ind / RECENT_COLORS_PER_ROW) + 2, 0, 1, 1,
|
||||||
|
Qt::AlignTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerDialog::initDefaultColors(LayoutCreator<QWidget> &creator)
|
||||||
|
{
|
||||||
|
auto grid = creator.setLayoutType<QGridLayout>();
|
||||||
|
|
||||||
|
auto label = this->ui_.def.label = new QLabel("Default colors:");
|
||||||
|
grid->addWidget(label, 0, 0, 1, -1);
|
||||||
|
|
||||||
|
const auto defaultColors = ColorProvider::instance().defaultColors();
|
||||||
|
auto it = defaultColors.begin();
|
||||||
|
size_t ind = 0;
|
||||||
|
while (it != defaultColors.end())
|
||||||
|
{
|
||||||
|
this->ui_.def.colors.push_back(new ColorButton(*it, this));
|
||||||
|
auto *button = this->ui_.def.colors[ind];
|
||||||
|
|
||||||
|
const int rowInd = (ind / DEFAULT_COLORS_PER_ROW) + 1;
|
||||||
|
const int columnInd = ind % DEFAULT_COLORS_PER_ROW;
|
||||||
|
|
||||||
|
grid->addWidget(button, rowInd, columnInd);
|
||||||
|
|
||||||
|
QObject::connect(button, &QPushButton::clicked,
|
||||||
|
[=] { this->selectColor(button->color(), false); });
|
||||||
|
|
||||||
|
++it;
|
||||||
|
++ind;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto spacer =
|
||||||
|
new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
|
||||||
|
grid->addItem(spacer, (ind / DEFAULT_COLORS_PER_ROW) + 2, 0, 1, 1,
|
||||||
|
Qt::AlignTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerDialog::initColorPicker(LayoutCreator<QWidget> &creator)
|
||||||
|
{
|
||||||
|
auto cpPanel = creator.setLayoutType<QHBoxLayout>();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For some reason, LayoutCreator::emplace didn't work for these.
|
||||||
|
* (Or maybe I was too dense to make it work.)
|
||||||
|
* After trying to debug for 4 hours or so, I gave up and settled
|
||||||
|
* for this solution.
|
||||||
|
*/
|
||||||
|
auto *colorPicker = new QColorPicker(this);
|
||||||
|
this->ui_.picker.colorPicker = colorPicker;
|
||||||
|
|
||||||
|
auto *luminancePicker = new QColorLuminancePicker(this);
|
||||||
|
this->ui_.picker.luminancePicker = luminancePicker;
|
||||||
|
|
||||||
|
cpPanel.append(colorPicker);
|
||||||
|
cpPanel.append(luminancePicker);
|
||||||
|
|
||||||
|
QObject::connect(colorPicker, SIGNAL(newCol(int, int)), luminancePicker,
|
||||||
|
SLOT(setCol(int, int)));
|
||||||
|
|
||||||
|
QObject::connect(
|
||||||
|
luminancePicker, &QColorLuminancePicker::newHsv,
|
||||||
|
[=](int h, int s, int v) {
|
||||||
|
int alpha = this->ui_.picker.spinBoxes[SpinBox::ALPHA]->value();
|
||||||
|
this->selectColor(QColor::fromHsv(h, s, v, alpha), true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerDialog::initSpinBoxes(LayoutCreator<QWidget> &creator)
|
||||||
|
{
|
||||||
|
auto spinBoxes = creator.setLayoutType<QGridLayout>();
|
||||||
|
|
||||||
|
auto *red = this->ui_.picker.spinBoxes[SpinBox::RED] =
|
||||||
|
new QColSpinBox(this);
|
||||||
|
auto *green = this->ui_.picker.spinBoxes[SpinBox::GREEN] =
|
||||||
|
new QColSpinBox(this);
|
||||||
|
auto *blue = this->ui_.picker.spinBoxes[SpinBox::BLUE] =
|
||||||
|
new QColSpinBox(this);
|
||||||
|
auto *alpha = this->ui_.picker.spinBoxes[SpinBox::ALPHA] =
|
||||||
|
new QColSpinBox(this);
|
||||||
|
|
||||||
|
// We need pointers to these for theme changes
|
||||||
|
auto *redLbl = this->ui_.picker.spinBoxLabels[SpinBox::RED] =
|
||||||
|
new QLabel("Red:");
|
||||||
|
auto *greenLbl = this->ui_.picker.spinBoxLabels[SpinBox::GREEN] =
|
||||||
|
new QLabel("Green:");
|
||||||
|
auto *blueLbl = this->ui_.picker.spinBoxLabels[SpinBox::BLUE] =
|
||||||
|
new QLabel("Blue:");
|
||||||
|
auto *alphaLbl = this->ui_.picker.spinBoxLabels[SpinBox::ALPHA] =
|
||||||
|
new QLabel("Alpha:");
|
||||||
|
|
||||||
|
spinBoxes->addWidget(redLbl, 0, 0);
|
||||||
|
spinBoxes->addWidget(red, 0, 1);
|
||||||
|
|
||||||
|
spinBoxes->addWidget(greenLbl, 1, 0);
|
||||||
|
spinBoxes->addWidget(green, 1, 1);
|
||||||
|
|
||||||
|
spinBoxes->addWidget(blueLbl, 2, 0);
|
||||||
|
spinBoxes->addWidget(blue, 2, 1);
|
||||||
|
|
||||||
|
spinBoxes->addWidget(alphaLbl, 3, 0);
|
||||||
|
spinBoxes->addWidget(alpha, 3, 1);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < SpinBox::END; ++i)
|
||||||
|
{
|
||||||
|
QObject::connect(
|
||||||
|
this->ui_.picker.spinBoxes[i],
|
||||||
|
QOverload<int>::of(&QSpinBox::valueChanged), [=](int value) {
|
||||||
|
this->selectColor(QColor(red->value(), green->value(),
|
||||||
|
blue->value(), alpha->value()),
|
||||||
|
false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorPickerDialog::initHtmlColor(LayoutCreator<QWidget> &creator)
|
||||||
|
{
|
||||||
|
auto html = creator.setLayoutType<QGridLayout>();
|
||||||
|
|
||||||
|
// Copied from Qt source for QColorShower
|
||||||
|
static QRegularExpression regExp(
|
||||||
|
QStringLiteral("#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})"));
|
||||||
|
auto *validator = this->htmlColorValidator_ =
|
||||||
|
new QRegularExpressionValidator(regExp, this);
|
||||||
|
|
||||||
|
auto *htmlLabel = this->ui_.picker.htmlLabel = new QLabel("HTML:");
|
||||||
|
auto *htmlEdit = this->ui_.picker.htmlEdit = new QLineEdit(this);
|
||||||
|
|
||||||
|
htmlEdit->setValidator(validator);
|
||||||
|
|
||||||
|
html->addWidget(htmlLabel, 0, 0);
|
||||||
|
html->addWidget(htmlEdit, 0, 1);
|
||||||
|
|
||||||
|
QObject::connect(htmlEdit, &QLineEdit::textEdited,
|
||||||
|
[=](const QString &text) {
|
||||||
|
QColor col(text);
|
||||||
|
if (col.isValid())
|
||||||
|
this->selectColor(col, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
110
src/widgets/dialogs/ColorPickerDialog.hpp
Normal file
110
src/widgets/dialogs/ColorPickerDialog.hpp
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "util/LayoutCreator.hpp"
|
||||||
|
#include "widgets/BasePopup.hpp"
|
||||||
|
#include "widgets/helper/ColorButton.hpp"
|
||||||
|
#include "widgets/helper/QColorPicker.hpp"
|
||||||
|
|
||||||
|
#include <pajlada/signals/signal.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A custom color picker dialog.
|
||||||
|
*
|
||||||
|
* This class exists because QColorPickerDialog did not suit our use case.
|
||||||
|
* This dialog provides buttons for recently used and default colors, as well
|
||||||
|
* as a color picker widget identical to the one used in QColorPickerDialog.
|
||||||
|
*/
|
||||||
|
class ColorPickerDialog : public BasePopup
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Create a new color picker dialog that selects the initial color.
|
||||||
|
*
|
||||||
|
* You can connect to the ::closed signal of this instance to get notified
|
||||||
|
* when the dialog is closed.
|
||||||
|
*/
|
||||||
|
ColorPickerDialog(const QColor &initial, QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
~ColorPickerDialog();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the final color selected by the user.
|
||||||
|
*
|
||||||
|
* Note that this method will always return the invalid color if the dialog
|
||||||
|
* is still open, or if the dialog has not been confirmed.
|
||||||
|
*
|
||||||
|
* @return The color selected by the user, if the dialog was confirmed.
|
||||||
|
* The invalid color, if the dialog has not been confirmed.
|
||||||
|
*/
|
||||||
|
QColor selectedColor() const;
|
||||||
|
|
||||||
|
pajlada::Signals::NoArgSignal closed;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void closeEvent(QCloseEvent *);
|
||||||
|
void themeChangedEvent();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct {
|
||||||
|
struct {
|
||||||
|
QLabel *label;
|
||||||
|
std::vector<ColorButton *> colors;
|
||||||
|
} recent;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
QLabel *label;
|
||||||
|
std::vector<ColorButton *> colors;
|
||||||
|
} def;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
QLabel *label;
|
||||||
|
ColorButton *color;
|
||||||
|
} selected;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
QColorPicker *colorPicker;
|
||||||
|
QColorLuminancePicker *luminancePicker;
|
||||||
|
|
||||||
|
std::array<QLabel *, 4> spinBoxLabels;
|
||||||
|
std::array<QColSpinBox *, 4> spinBoxes;
|
||||||
|
|
||||||
|
QLabel *htmlLabel;
|
||||||
|
QLineEdit *htmlEdit;
|
||||||
|
} picker;
|
||||||
|
} ui_;
|
||||||
|
|
||||||
|
enum SpinBox : size_t { RED = 0, GREEN = 1, BLUE = 2, ALPHA = 3, END };
|
||||||
|
|
||||||
|
static const size_t MAX_RECENT_COLORS = 10;
|
||||||
|
static const size_t RECENT_COLORS_PER_ROW = 5;
|
||||||
|
static const size_t DEFAULT_COLORS_PER_ROW = 5;
|
||||||
|
|
||||||
|
QColor color_;
|
||||||
|
bool dialogConfirmed_;
|
||||||
|
QRegularExpressionValidator *htmlColorValidator_{};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update the currently selected color.
|
||||||
|
*
|
||||||
|
* @param color Color to update to.
|
||||||
|
* @param fromColorPicker Whether the color update has been triggered by
|
||||||
|
* one of the color picker widgets. This is needed
|
||||||
|
* to prevent weird widget behavior.
|
||||||
|
*/
|
||||||
|
void selectColor(const QColor &color, bool fromColorPicker);
|
||||||
|
|
||||||
|
/// Called when the dialog is confirmed.
|
||||||
|
void ok();
|
||||||
|
|
||||||
|
// Helper methods for initializing UI elements
|
||||||
|
void initRecentColors(LayoutCreator<QWidget> &creator);
|
||||||
|
void initDefaultColors(LayoutCreator<QWidget> &creator);
|
||||||
|
void initColorPicker(LayoutCreator<QWidget> &creator);
|
||||||
|
void initSpinBoxes(LayoutCreator<QWidget> &creator);
|
||||||
|
void initHtmlColor(LayoutCreator<QWidget> &creator);
|
||||||
|
};
|
||||||
|
} // namespace chatterino
|
|
@ -7,6 +7,7 @@
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
|
#include "util/Shortcut.hpp"
|
||||||
#include "widgets/Notebook.hpp"
|
#include "widgets/Notebook.hpp"
|
||||||
#include "widgets/helper/ChannelView.hpp"
|
#include "widgets/helper/ChannelView.hpp"
|
||||||
|
|
||||||
|
@ -31,7 +32,14 @@ namespace {
|
||||||
|
|
||||||
if (!map.empty())
|
if (!map.empty())
|
||||||
{
|
{
|
||||||
for (const auto &emote : map)
|
std::vector<std::pair<EmoteName, EmotePtr>> vec(map.begin(),
|
||||||
|
map.end());
|
||||||
|
std::sort(vec.begin(), vec.end(),
|
||||||
|
[](const std::pair<EmoteName, EmotePtr> &l,
|
||||||
|
const std::pair<EmoteName, EmotePtr> &r) {
|
||||||
|
return l.first.string < r.first.string;
|
||||||
|
});
|
||||||
|
for (const auto &emote : vec)
|
||||||
{
|
{
|
||||||
builder
|
builder
|
||||||
.emplace<EmoteElement>(emote.second,
|
.emplace<EmoteElement>(emote.second,
|
||||||
|
@ -131,6 +139,10 @@ EmotePopup::EmotePopup(QWidget *parent)
|
||||||
this->viewEmojis_ = makeView("Emojis");
|
this->viewEmojis_ = makeView("Emojis");
|
||||||
|
|
||||||
this->loadEmojis();
|
this->loadEmojis();
|
||||||
|
|
||||||
|
createWindowShortcut(this, "CTRL+Tab", [=] { notebook->selectNextTab(); });
|
||||||
|
createWindowShortcut(this, "CTRL+Shift+Tab",
|
||||||
|
[=] { notebook->selectPreviousTab(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmotePopup::loadChannel(ChannelPtr _channel)
|
void EmotePopup::loadChannel(ChannelPtr _channel)
|
||||||
|
|
|
@ -65,7 +65,7 @@ namespace {
|
||||||
// messageBox.setIcon(QMessageBox::Information);
|
// messageBox.setIcon(QMessageBox::Information);
|
||||||
// messageBox.setText("Successfully logged in with user <b>" +
|
// messageBox.setText("Successfully logged in with user <b>" +
|
||||||
// qS(username) + "</b>!");
|
// qS(username) + "</b>!");
|
||||||
auto basePath = fS("/accounts/uid{}", userID);
|
std::string basePath = "/accounts/uid" + userID.toStdString();
|
||||||
pajlada::Settings::Setting<QString>::set(basePath + "/username",
|
pajlada::Settings::Setting<QString>::set(basePath + "/username",
|
||||||
username);
|
username);
|
||||||
pajlada::Settings::Setting<QString>::set(basePath + "/userID", userID);
|
pajlada::Settings::Setting<QString>::set(basePath + "/userID", userID);
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
#include "IrcMessage"
|
#include "IrcMessage"
|
||||||
#include "common/Channel.hpp"
|
#include "common/Channel.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "providers/twitch/PartialTwitchUser.hpp"
|
#include "providers/twitch/PartialTwitchUser.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
|
@ -55,7 +54,7 @@ void LogsPopup::getLogs()
|
||||||
dynamic_cast<TwitchChannel *>(this->channel_.get()))
|
dynamic_cast<TwitchChannel *>(this->channel_.get()))
|
||||||
{
|
{
|
||||||
this->channelName_ = twitchChannel->getName();
|
this->channelName_ = twitchChannel->getName();
|
||||||
this->getLogviewerLogs(twitchChannel->roomId());
|
this->getOverrustleLogs();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -63,14 +62,11 @@ void LogsPopup::getLogs()
|
||||||
|
|
||||||
if (!this->channelName_.isEmpty())
|
if (!this->channelName_.isEmpty())
|
||||||
{
|
{
|
||||||
PartialTwitchUser::byName(this->channelName_)
|
this->getOverrustleLogs();
|
||||||
.getId(
|
|
||||||
[=](const QString &roomID) { this->getLogviewerLogs(roomID); },
|
|
||||||
this);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log("Unable to get logs, no channel name or something specified");
|
qDebug() << "Unable to get logs, no channel name or something specified";
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogsPopup::setMessages(std::vector<MessagePtr> &messages)
|
void LogsPopup::setMessages(std::vector<MessagePtr> &messages)
|
||||||
|
@ -81,57 +77,9 @@ void LogsPopup::setMessages(std::vector<MessagePtr> &messages)
|
||||||
SearchPopup::setChannel(logsChannel);
|
SearchPopup::setChannel(logsChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogsPopup::getLogviewerLogs(const QString &roomID)
|
|
||||||
{
|
|
||||||
auto url = QString("https://cbenni.com/api/logs/%1/?nick=%2&before=500")
|
|
||||||
.arg(this->channelName_, this->userName_);
|
|
||||||
|
|
||||||
NetworkRequest(url)
|
|
||||||
.caller(this)
|
|
||||||
.onError([this](NetworkResult) { this->getOverrustleLogs(); })
|
|
||||||
.onSuccess([this, roomID](auto result) -> Outcome {
|
|
||||||
auto data = result.parseJson();
|
|
||||||
std::vector<MessagePtr> messages;
|
|
||||||
|
|
||||||
QJsonValue before = data.value("before");
|
|
||||||
|
|
||||||
for (auto i : before.toArray())
|
|
||||||
{
|
|
||||||
auto messageObject = i.toObject();
|
|
||||||
QString message = messageObject.value("text").toString();
|
|
||||||
|
|
||||||
// Hacky way to fix the timestamp
|
|
||||||
message.insert(1, "historical=1;");
|
|
||||||
message.insert(1, QString("tmi-sent-ts=%10000;")
|
|
||||||
.arg(messageObject["time"].toInt()));
|
|
||||||
message.insert(1, QString("room-id=%1;").arg(roomID));
|
|
||||||
|
|
||||||
MessageParseArgs args;
|
|
||||||
auto ircMessage =
|
|
||||||
Communi::IrcMessage::fromData(message.toUtf8(), nullptr);
|
|
||||||
auto privMsg =
|
|
||||||
static_cast<Communi::IrcPrivateMessage *>(ircMessage);
|
|
||||||
TwitchMessageBuilder builder(this->channel_.get(), privMsg,
|
|
||||||
args);
|
|
||||||
builder.message().searchText = message;
|
|
||||||
|
|
||||||
messages.push_back(builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
messages.push_back(
|
|
||||||
MessageBuilder(systemMessage,
|
|
||||||
"Logs provided by https://cbenni.com")
|
|
||||||
.release());
|
|
||||||
this->setMessages(messages);
|
|
||||||
|
|
||||||
return Success;
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LogsPopup::getOverrustleLogs()
|
void LogsPopup::getOverrustleLogs()
|
||||||
{
|
{
|
||||||
QString url =
|
auto url =
|
||||||
QString("https://overrustlelogs.net/api/v1/stalk/%1/%2.json?limit=500")
|
QString("https://overrustlelogs.net/api/v1/stalk/%1/%2.json?limit=500")
|
||||||
.arg(this->channelName_, this->userName_);
|
.arg(this->channelName_, this->userName_);
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ private:
|
||||||
|
|
||||||
void setMessages(std::vector<MessagePtr> &messages);
|
void setMessages(std::vector<MessagePtr> &messages);
|
||||||
void getOverrustleLogs();
|
void getOverrustleLogs();
|
||||||
void getLogviewerLogs(const QString &roomID);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#include "QualityPopup.hpp"
|
#include "QualityPopup.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "util/StreamLink.hpp"
|
#include "util/StreamLink.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
@ -50,7 +49,7 @@ void QualityPopup::okButtonClicked()
|
||||||
}
|
}
|
||||||
catch (const Exception &ex)
|
catch (const Exception &ex)
|
||||||
{
|
{
|
||||||
log("Exception caught trying to open streamlink: {}", ex.what());
|
qDebug() << "Exception caught trying to open streamlink:" << ex.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
this->close();
|
this->close();
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#define TEXT_VIEWS "Views: "
|
#define TEXT_VIEWS "Views: "
|
||||||
#define TEXT_CREATED "Created: "
|
#define TEXT_CREATED "Created: "
|
||||||
#define TEXT_USER_ID "ID: "
|
#define TEXT_USER_ID "ID: "
|
||||||
|
#define TEXT_UNAVAILABLE "(not available)"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -382,6 +383,22 @@ void UserInfoPopup::updateUserData()
|
||||||
{
|
{
|
||||||
std::weak_ptr<bool> hack = this->hack_;
|
std::weak_ptr<bool> hack = this->hack_;
|
||||||
|
|
||||||
|
const auto onIdFetchFailed = [this]() {
|
||||||
|
// this can occur when the account doesn't exist.
|
||||||
|
this->ui_.followerCountLabel->setText(TEXT_FOLLOWERS +
|
||||||
|
QString(TEXT_UNAVAILABLE));
|
||||||
|
this->ui_.viewCountLabel->setText(TEXT_VIEWS +
|
||||||
|
QString(TEXT_UNAVAILABLE));
|
||||||
|
this->ui_.createdDateLabel->setText(TEXT_CREATED +
|
||||||
|
QString(TEXT_UNAVAILABLE));
|
||||||
|
|
||||||
|
this->ui_.nameLabel->setText(this->userName_);
|
||||||
|
|
||||||
|
this->ui_.userIDLabel->setText(QString("ID") +
|
||||||
|
QString(TEXT_UNAVAILABLE));
|
||||||
|
this->ui_.userIDLabel->setProperty("copy-text",
|
||||||
|
QString(TEXT_UNAVAILABLE));
|
||||||
|
};
|
||||||
const auto onIdFetched = [this, hack](QString id) {
|
const auto onIdFetched = [this, hack](QString id) {
|
||||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||||
|
|
||||||
|
@ -462,7 +479,8 @@ void UserInfoPopup::updateUserData()
|
||||||
this->ui_.ignoreHighlights->setChecked(isIgnoringHighlights);
|
this->ui_.ignoreHighlights->setChecked(isIgnoringHighlights);
|
||||||
};
|
};
|
||||||
|
|
||||||
PartialTwitchUser::byName(this->userName_).getId(onIdFetched, this);
|
PartialTwitchUser::byName(this->userName_)
|
||||||
|
.getId(onIdFetched, onIdFetchFailed, this);
|
||||||
|
|
||||||
this->ui_.follow->setEnabled(false);
|
this->ui_.follow->setEnabled(false);
|
||||||
this->ui_.ignore->setEnabled(false);
|
this->ui_.ignore->setEnabled(false);
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "debug/Benchmark.hpp"
|
#include "debug/Benchmark.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "messages/Emote.hpp"
|
#include "messages/Emote.hpp"
|
||||||
#include "messages/LimitedQueueSnapshot.hpp"
|
#include "messages/LimitedQueueSnapshot.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
|
@ -29,6 +28,7 @@
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "singletons/TooltipPreviewImage.hpp"
|
#include "singletons/TooltipPreviewImage.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
|
#include "util/Clipboard.hpp"
|
||||||
#include "util/DistanceBetweenPoints.hpp"
|
#include "util/DistanceBetweenPoints.hpp"
|
||||||
#include "util/IncognitoBrowser.hpp"
|
#include "util/IncognitoBrowser.hpp"
|
||||||
#include "widgets/Scrollbar.hpp"
|
#include "widgets/Scrollbar.hpp"
|
||||||
|
@ -65,9 +65,8 @@ namespace {
|
||||||
if (!image->isEmpty())
|
if (!image->isEmpty())
|
||||||
{
|
{
|
||||||
copyMenu->addAction(
|
copyMenu->addAction(
|
||||||
QString(scale) + "x link", [url = image->url()] {
|
QString(scale) + "x link",
|
||||||
QApplication::clipboard()->setText(url.string);
|
[url = image->url()] { crossPlatformCopy(url.string); });
|
||||||
});
|
|
||||||
openMenu->addAction(
|
openMenu->addAction(
|
||||||
QString(scale) + "x link", [url = image->url()] {
|
QString(scale) + "x link", [url = image->url()] {
|
||||||
QDesktopServices::openUrl(QUrl(url.string));
|
QDesktopServices::openUrl(QUrl(url.string));
|
||||||
|
@ -85,9 +84,8 @@ namespace {
|
||||||
openMenu->addSeparator();
|
openMenu->addSeparator();
|
||||||
|
|
||||||
copyMenu->addAction(
|
copyMenu->addAction(
|
||||||
"Copy " + name + " emote link", [url = emote.homePage] {
|
"Copy " + name + " emote link",
|
||||||
QApplication::clipboard()->setText(url.string); //
|
[url = emote.homePage] { crossPlatformCopy(url.string); });
|
||||||
});
|
|
||||||
openMenu->addAction(
|
openMenu->addAction(
|
||||||
"Open " + name + " emote link", [url = emote.homePage] {
|
"Open " + name + " emote link", [url = emote.homePage] {
|
||||||
QDesktopServices::openUrl(QUrl(url.string)); //
|
QDesktopServices::openUrl(QUrl(url.string)); //
|
||||||
|
@ -125,9 +123,8 @@ ChannelView::ChannelView(BaseWidget *parent)
|
||||||
});
|
});
|
||||||
|
|
||||||
auto shortcut = new QShortcut(QKeySequence("Ctrl+C"), this);
|
auto shortcut = new QShortcut(QKeySequence("Ctrl+C"), this);
|
||||||
QObject::connect(shortcut, &QShortcut::activated, [this] {
|
QObject::connect(shortcut, &QShortcut::activated,
|
||||||
QGuiApplication::clipboard()->setText(this->getSelectedText());
|
[this] { crossPlatformCopy(this->getSelectedText()); });
|
||||||
});
|
|
||||||
|
|
||||||
this->clickTimer_ = new QTimer(this);
|
this->clickTimer_ = new QTimer(this);
|
||||||
this->clickTimer_->setSingleShot(true);
|
this->clickTimer_->setSingleShot(true);
|
||||||
|
@ -651,7 +648,8 @@ void ChannelView::messageAppended(MessagePtr &message,
|
||||||
|
|
||||||
if (!messageFlags->has(MessageFlag::DoNotTriggerNotification))
|
if (!messageFlags->has(MessageFlag::DoNotTriggerNotification))
|
||||||
{
|
{
|
||||||
if (messageFlags->has(MessageFlag::Highlighted))
|
if (messageFlags->has(MessageFlag::Highlighted) &&
|
||||||
|
!messageFlags->has(MessageFlag::Subscription))
|
||||||
{
|
{
|
||||||
this->tabHighlightRequested.invoke(HighlightState::Highlighted);
|
this->tabHighlightRequested.invoke(HighlightState::Highlighted);
|
||||||
}
|
}
|
||||||
|
@ -740,9 +738,8 @@ void ChannelView::messageReplaced(size_t index, MessagePtr &replacement)
|
||||||
auto snapshot = this->messages_.getSnapshot();
|
auto snapshot = this->messages_.getSnapshot();
|
||||||
if (index >= snapshot.size())
|
if (index >= snapshot.size())
|
||||||
{
|
{
|
||||||
log("Tried to replace out of bounds message. Index: {}. "
|
qDebug() << "Tried to replace out of bounds message. Index:" << index
|
||||||
"Length: {}",
|
<< ". Length:" << snapshot.size();
|
||||||
index, snapshot.size());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1554,8 +1551,7 @@ void ChannelView::addContextMenuItems(
|
||||||
menu->addAction("Open link incognito",
|
menu->addAction("Open link incognito",
|
||||||
[url] { openLinkIncognito(url); });
|
[url] { openLinkIncognito(url); });
|
||||||
}
|
}
|
||||||
menu->addAction("Copy link",
|
menu->addAction("Copy link", [url] { crossPlatformCopy(url); });
|
||||||
[url] { QApplication::clipboard()->setText(url); });
|
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
}
|
}
|
||||||
|
@ -1563,9 +1559,8 @@ void ChannelView::addContextMenuItems(
|
||||||
// Copy actions
|
// Copy actions
|
||||||
if (!this->selection_.isEmpty())
|
if (!this->selection_.isEmpty())
|
||||||
{
|
{
|
||||||
menu->addAction("Copy selection", [this] {
|
menu->addAction("Copy selection",
|
||||||
QGuiApplication::clipboard()->setText(this->getSelectedText());
|
[this] { crossPlatformCopy(this->getSelectedText()); });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
menu->addAction("Copy message", [layout] {
|
menu->addAction("Copy message", [layout] {
|
||||||
|
@ -1573,14 +1568,14 @@ void ChannelView::addContextMenuItems(
|
||||||
layout->addSelectionText(copyString, 0, INT_MAX,
|
layout->addSelectionText(copyString, 0, INT_MAX,
|
||||||
CopyMode::OnlyTextAndEmotes);
|
CopyMode::OnlyTextAndEmotes);
|
||||||
|
|
||||||
QGuiApplication::clipboard()->setText(copyString);
|
crossPlatformCopy(copyString);
|
||||||
});
|
});
|
||||||
|
|
||||||
menu->addAction("Copy full message", [layout] {
|
menu->addAction("Copy full message", [layout] {
|
||||||
QString copyString;
|
QString copyString;
|
||||||
layout->addSelectionText(copyString);
|
layout->addSelectionText(copyString);
|
||||||
|
|
||||||
QGuiApplication::clipboard()->setText(copyString);
|
crossPlatformCopy(copyString);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Open in new split.
|
// Open in new split.
|
||||||
|
|
23
src/widgets/helper/ColorButton.cpp
Normal file
23
src/widgets/helper/ColorButton.cpp
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#include "widgets/helper/ColorButton.hpp"
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
ColorButton::ColorButton(const QColor &color, QWidget *parent)
|
||||||
|
: QPushButton(parent)
|
||||||
|
, color_(color)
|
||||||
|
{
|
||||||
|
this->setColor(color_);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QColor &ColorButton::color() const
|
||||||
|
{
|
||||||
|
return this->color_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorButton::setColor(QColor color)
|
||||||
|
{
|
||||||
|
this->color_ = color;
|
||||||
|
this->setStyleSheet("background-color: " + color.name(QColor::HexArgb));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
18
src/widgets/helper/ColorButton.hpp
Normal file
18
src/widgets/helper/ColorButton.hpp
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
class ColorButton : public QPushButton
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ColorButton(const QColor &color, QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
const QColor &color() const;
|
||||||
|
|
||||||
|
void setColor(QColor color);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QColor color_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chatterino
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/Common.hpp"
|
#include "common/Common.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "singletons/Fonts.hpp"
|
#include "singletons/Fonts.hpp"
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
|
|
290
src/widgets/helper/QColorPicker.cpp
Normal file
290
src/widgets/helper/QColorPicker.cpp
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2016 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the QtWidgets module of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:LGPL$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU Lesser General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||||
|
** packaging of this file. Please review the following information to
|
||||||
|
** ensure the GNU Lesser General Public License version 3 requirements
|
||||||
|
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 2.0 or (at your option) the GNU General
|
||||||
|
** Public license version 3 or any later version approved by the KDE Free
|
||||||
|
** Qt Foundation. The licenses are as published by the Free Software
|
||||||
|
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||||
|
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
#include "widgets/helper/QColorPicker.hpp"
|
||||||
|
|
||||||
|
#include <qdrawutil.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These classes are literally copied from the Qt source.
|
||||||
|
* Unfortunately, they are private to the QColorDialog class so we cannot use
|
||||||
|
* them directly.
|
||||||
|
* If they become public at any point in the future, it should be possible to
|
||||||
|
* replace every include of this header with the respective includes for the
|
||||||
|
* QColorPicker, QColorLuminancePicker, and QColSpinBox classes.
|
||||||
|
*/
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
int QColorLuminancePicker::y2val(int y)
|
||||||
|
{
|
||||||
|
int d = height() - 2 * coff - 1;
|
||||||
|
return 255 - (y - coff) * 255 / d;
|
||||||
|
}
|
||||||
|
|
||||||
|
int QColorLuminancePicker::val2y(int v)
|
||||||
|
{
|
||||||
|
int d = height() - 2 * coff - 1;
|
||||||
|
return coff + (255 - v) * d / 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
QColorLuminancePicker::QColorLuminancePicker(QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
{
|
||||||
|
hue = 100;
|
||||||
|
val = 100;
|
||||||
|
sat = 100;
|
||||||
|
pix = 0;
|
||||||
|
setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
|
||||||
|
}
|
||||||
|
|
||||||
|
QColorLuminancePicker::~QColorLuminancePicker()
|
||||||
|
{
|
||||||
|
delete pix;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QColorLuminancePicker::mouseMoveEvent(QMouseEvent *m)
|
||||||
|
{
|
||||||
|
setVal(y2val(m->y()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QColorLuminancePicker::mousePressEvent(QMouseEvent *m)
|
||||||
|
{
|
||||||
|
setVal(y2val(m->y()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QColorLuminancePicker::setVal(int v)
|
||||||
|
{
|
||||||
|
if (val == v)
|
||||||
|
return;
|
||||||
|
val = qMax(0, qMin(v, 255));
|
||||||
|
delete pix;
|
||||||
|
pix = 0;
|
||||||
|
repaint();
|
||||||
|
emit newHsv(hue, sat, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
//receives from a hue,sat chooser and relays.
|
||||||
|
void QColorLuminancePicker::setCol(int h, int s)
|
||||||
|
{
|
||||||
|
setCol(h, s, val);
|
||||||
|
emit newHsv(h, s, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize QColorLuminancePicker::sizeHint() const
|
||||||
|
{
|
||||||
|
return QSize(LUMINANCE_PICKER_WIDTH, LUMINANCE_PICKER_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QColorLuminancePicker::paintEvent(QPaintEvent *)
|
||||||
|
{
|
||||||
|
int w = width() - 5;
|
||||||
|
QRect r(0, foff, w, height() - 2 * foff);
|
||||||
|
int wi = r.width() - 2;
|
||||||
|
int hi = r.height() - 2;
|
||||||
|
if (!pix || pix->height() != hi || pix->width() != wi)
|
||||||
|
{
|
||||||
|
delete pix;
|
||||||
|
QImage img(wi, hi, QImage::Format_RGB32);
|
||||||
|
int y;
|
||||||
|
uint *pixel = (uint *)img.scanLine(0);
|
||||||
|
for (y = 0; y < hi; y++)
|
||||||
|
{
|
||||||
|
uint *end = pixel + wi;
|
||||||
|
std::fill(pixel, end,
|
||||||
|
QColor::fromHsv(hue, sat, y2val(y + coff)).rgb());
|
||||||
|
pixel = end;
|
||||||
|
}
|
||||||
|
pix = new QPixmap(QPixmap::fromImage(img));
|
||||||
|
}
|
||||||
|
QPainter p(this);
|
||||||
|
p.drawPixmap(1, coff, *pix);
|
||||||
|
const QPalette &g = palette();
|
||||||
|
qDrawShadePanel(&p, r, g, true);
|
||||||
|
p.setPen(g.windowText().color());
|
||||||
|
p.setBrush(g.windowText());
|
||||||
|
QPolygon a;
|
||||||
|
int y = val2y(val);
|
||||||
|
a.setPoints(3, w, y, w + 5, y + 5, w + 5, y - 5);
|
||||||
|
p.eraseRect(w, 0, 5, height());
|
||||||
|
p.drawPolygon(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QColorLuminancePicker::setCol(int h, int s, int v)
|
||||||
|
{
|
||||||
|
val = v;
|
||||||
|
hue = h;
|
||||||
|
sat = s;
|
||||||
|
delete pix;
|
||||||
|
pix = 0;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint QColorPicker::colPt()
|
||||||
|
{
|
||||||
|
QRect r = contentsRect();
|
||||||
|
return QPoint((360 - hue) * (r.width() - 1) / 360,
|
||||||
|
(255 - sat) * (r.height() - 1) / 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
int QColorPicker::huePt(const QPoint &pt)
|
||||||
|
{
|
||||||
|
QRect r = contentsRect();
|
||||||
|
return 360 - pt.x() * 360 / (r.width() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int QColorPicker::satPt(const QPoint &pt)
|
||||||
|
{
|
||||||
|
QRect r = contentsRect();
|
||||||
|
return 255 - pt.y() * 255 / (r.height() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QColorPicker::setCol(const QPoint &pt)
|
||||||
|
{
|
||||||
|
setCol(huePt(pt), satPt(pt));
|
||||||
|
}
|
||||||
|
|
||||||
|
QColorPicker::QColorPicker(QWidget *parent)
|
||||||
|
: QFrame(parent)
|
||||||
|
, crossVisible(true)
|
||||||
|
{
|
||||||
|
hue = 0;
|
||||||
|
sat = 0;
|
||||||
|
setCol(150, 255);
|
||||||
|
setAttribute(Qt::WA_NoSystemBackground);
|
||||||
|
setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
|
||||||
|
}
|
||||||
|
|
||||||
|
QColorPicker::~QColorPicker()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void QColorPicker::setCrossVisible(bool visible)
|
||||||
|
{
|
||||||
|
if (crossVisible != visible)
|
||||||
|
{
|
||||||
|
crossVisible = visible;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize QColorPicker::sizeHint() const
|
||||||
|
{
|
||||||
|
return QSize(COLOR_PICKER_WIDTH, COLOR_PICKER_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QColorPicker::setCol(int h, int s)
|
||||||
|
{
|
||||||
|
int nhue = qMin(qMax(0, h), 359);
|
||||||
|
int nsat = qMin(qMax(0, s), 255);
|
||||||
|
if (nhue == hue && nsat == sat)
|
||||||
|
return;
|
||||||
|
QRect r(colPt(), QSize(20, 20));
|
||||||
|
hue = nhue;
|
||||||
|
sat = nsat;
|
||||||
|
r = r.united(QRect(colPt(), QSize(20, 20)));
|
||||||
|
r.translate(contentsRect().x() - 9, contentsRect().y() - 9);
|
||||||
|
repaint(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QColorPicker::mouseMoveEvent(QMouseEvent *m)
|
||||||
|
{
|
||||||
|
QPoint p = m->pos() - contentsRect().topLeft();
|
||||||
|
setCol(p);
|
||||||
|
emit newCol(hue, sat);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QColorPicker::mousePressEvent(QMouseEvent *m)
|
||||||
|
{
|
||||||
|
QPoint p = m->pos() - contentsRect().topLeft();
|
||||||
|
setCol(p);
|
||||||
|
emit newCol(hue, sat);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QColorPicker::paintEvent(QPaintEvent *)
|
||||||
|
{
|
||||||
|
QPainter p(this);
|
||||||
|
drawFrame(&p);
|
||||||
|
QRect r = contentsRect();
|
||||||
|
p.drawPixmap(r.topLeft(), pix);
|
||||||
|
if (crossVisible)
|
||||||
|
{
|
||||||
|
QPoint pt = colPt() + r.topLeft();
|
||||||
|
p.setPen(Qt::black);
|
||||||
|
p.fillRect(pt.x() - 9, pt.y(), 20, 2, Qt::black);
|
||||||
|
p.fillRect(pt.x(), pt.y() - 9, 2, 20, Qt::black);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QColorPicker::resizeEvent(QResizeEvent *ev)
|
||||||
|
{
|
||||||
|
QFrame::resizeEvent(ev);
|
||||||
|
int w = width() - frameWidth() * 2;
|
||||||
|
int h = height() - frameWidth() * 2;
|
||||||
|
QImage img(w, h, QImage::Format_RGB32);
|
||||||
|
int x, y;
|
||||||
|
uint *pixel = (uint *)img.scanLine(0);
|
||||||
|
for (y = 0; y < h; y++)
|
||||||
|
{
|
||||||
|
const uint *end = pixel + w;
|
||||||
|
x = 0;
|
||||||
|
while (pixel < end)
|
||||||
|
{
|
||||||
|
QPoint p(x, y);
|
||||||
|
QColor c;
|
||||||
|
c.setHsv(huePt(p), satPt(p), 200);
|
||||||
|
*pixel = c.rgb();
|
||||||
|
++pixel;
|
||||||
|
++x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pix = QPixmap::fromImage(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
QColSpinBox::QColSpinBox(QWidget *parent)
|
||||||
|
: QSpinBox(parent)
|
||||||
|
{
|
||||||
|
this->setRange(0, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QColSpinBox::setValue(int i)
|
||||||
|
{
|
||||||
|
const QSignalBlocker blocker(this);
|
||||||
|
QSpinBox::setValue(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace chatterino
|
130
src/widgets/helper/QColorPicker.hpp
Normal file
130
src/widgets/helper/QColorPicker.hpp
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2016 The Qt Company Ltd.
|
||||||
|
** Contact: https://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the QtWidgets module of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:LGPL$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at https://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU Lesser General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||||
|
** General Public License version 3 as published by the Free Software
|
||||||
|
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
||||||
|
** packaging of this file. Please review the following information to
|
||||||
|
** ensure the GNU Lesser General Public License version 3 requirements
|
||||||
|
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
||||||
|
**
|
||||||
|
** GNU General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU
|
||||||
|
** General Public License version 2.0 or (at your option) the GNU General
|
||||||
|
** Public license version 3 or any later version approved by the KDE Free
|
||||||
|
** Qt Foundation. The licenses are as published by the Free Software
|
||||||
|
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
||||||
|
** included in the packaging of this file. Please review the following
|
||||||
|
** information to ensure the GNU General Public License requirements will
|
||||||
|
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
||||||
|
** https://www.gnu.org/licenses/gpl-3.0.html.
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QSpinBox>
|
||||||
|
|
||||||
|
namespace chatterino {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These classes are literally copied from the Qt source.
|
||||||
|
* Unfortunately, they are private to the QColorDialog class so we cannot use
|
||||||
|
* them directly.
|
||||||
|
* If they become public at any point in the future, it should be possible to
|
||||||
|
* replace every include of this header with the respective includes for the
|
||||||
|
* QColorPicker, QColorLuminancePicker, and QColSpinBox classes.
|
||||||
|
*/
|
||||||
|
class QColorPicker : public QFrame
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
QColorPicker(QWidget *parent);
|
||||||
|
~QColorPicker();
|
||||||
|
void setCrossVisible(bool visible);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setCol(int h, int s);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void newCol(int h, int s);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QSize sizeHint() const override;
|
||||||
|
void paintEvent(QPaintEvent *) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *) override;
|
||||||
|
void mousePressEvent(QMouseEvent *) override;
|
||||||
|
void resizeEvent(QResizeEvent *) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int hue;
|
||||||
|
int sat;
|
||||||
|
QPoint colPt();
|
||||||
|
int huePt(const QPoint &pt);
|
||||||
|
int satPt(const QPoint &pt);
|
||||||
|
void setCol(const QPoint &pt);
|
||||||
|
QPixmap pix;
|
||||||
|
bool crossVisible;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int COLOR_PICKER_WIDTH = 220;
|
||||||
|
static const int COLOR_PICKER_HEIGHT = 200;
|
||||||
|
|
||||||
|
class QColorLuminancePicker : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
QColorLuminancePicker(QWidget *parent = 0);
|
||||||
|
~QColorLuminancePicker();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void setCol(int h, int s, int v);
|
||||||
|
void setCol(int h, int s);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void newHsv(int h, int s, int v);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QSize sizeHint() const override;
|
||||||
|
void paintEvent(QPaintEvent *) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent *) override;
|
||||||
|
void mousePressEvent(QMouseEvent *) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum { foff = 3, coff = 4 }; //frame and contents offset
|
||||||
|
int val;
|
||||||
|
int hue;
|
||||||
|
int sat;
|
||||||
|
int y2val(int y);
|
||||||
|
int val2y(int val);
|
||||||
|
void setVal(int v);
|
||||||
|
QPixmap *pix;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int LUMINANCE_PICKER_WIDTH = 25;
|
||||||
|
static const int LUMINANCE_PICKER_HEIGHT = COLOR_PICKER_HEIGHT;
|
||||||
|
|
||||||
|
class QColSpinBox : public QSpinBox
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QColSpinBox(QWidget *parent);
|
||||||
|
|
||||||
|
void setValue(int i);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chatterino
|
|
@ -1,24 +1,27 @@
|
||||||
#include "widgets/helper/ScrollbarHighlight.hpp"
|
#include "widgets/helper/ScrollbarHighlight.hpp"
|
||||||
|
|
||||||
|
#include "Application.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "widgets/Scrollbar.hpp"
|
#include "widgets/Scrollbar.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
ScrollbarHighlight::ScrollbarHighlight()
|
ScrollbarHighlight::ScrollbarHighlight()
|
||||||
: color_(Color::Highlight)
|
: color_(std::make_shared<QColor>())
|
||||||
, style_(Style::None)
|
, style_(Style::None)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollbarHighlight::ScrollbarHighlight(Color color, Style style)
|
ScrollbarHighlight::ScrollbarHighlight(const std::shared_ptr<QColor> color,
|
||||||
|
Style style)
|
||||||
: color_(color)
|
: color_(color)
|
||||||
, style_(style)
|
, style_(style)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollbarHighlight::Color ScrollbarHighlight::getColor() const
|
QColor ScrollbarHighlight::getColor() const
|
||||||
{
|
{
|
||||||
return this->color_;
|
return *this->color_;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollbarHighlight::Style ScrollbarHighlight::getStyle() const
|
ScrollbarHighlight::Style ScrollbarHighlight::getStyle() const
|
||||||
|
|
|
@ -6,17 +6,25 @@ class ScrollbarHighlight
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum Style : char { None, Default, Line };
|
enum Style : char { None, Default, Line };
|
||||||
enum Color : char { Highlight, Subscription };
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructs an invalid ScrollbarHighlight.
|
||||||
|
*
|
||||||
|
* A highlight constructed this way will not show on the scrollbar.
|
||||||
|
* For these, use the static ScrollbarHighlight::newSubscription and
|
||||||
|
* ScrollbarHighlight::newHighlight methods.
|
||||||
|
*/
|
||||||
ScrollbarHighlight();
|
ScrollbarHighlight();
|
||||||
ScrollbarHighlight(Color color, Style style = Default);
|
|
||||||
|
|
||||||
Color getColor() const;
|
ScrollbarHighlight(const std::shared_ptr<QColor> color,
|
||||||
|
Style style = Default);
|
||||||
|
|
||||||
|
QColor getColor() const;
|
||||||
Style getStyle() const;
|
Style getStyle() const;
|
||||||
bool isNull() const;
|
bool isNull() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Color color_;
|
std::shared_ptr<QColor> color_;
|
||||||
Style style_;
|
Style style_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
#include "common/Modes.hpp"
|
#include "common/Modes.hpp"
|
||||||
#include "common/Version.hpp"
|
#include "common/Version.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "util/LayoutCreator.hpp"
|
#include "util/LayoutCreator.hpp"
|
||||||
#include "util/RemoveScrollAreaBackground.hpp"
|
#include "util/RemoveScrollAreaBackground.hpp"
|
||||||
#include "widgets/helper/SignalLabel.hpp"
|
#include "widgets/helper/SignalLabel.hpp"
|
||||||
|
@ -102,8 +101,6 @@ AboutPage::AboutPage()
|
||||||
":/licenses/qt_lgpl-3.0.txt");
|
":/licenses/qt_lgpl-3.0.txt");
|
||||||
addLicense(form.getElement(), "Boost", "https://www.boost.org/",
|
addLicense(form.getElement(), "Boost", "https://www.boost.org/",
|
||||||
":/licenses/boost_boost.txt");
|
":/licenses/boost_boost.txt");
|
||||||
addLicense(form.getElement(), "Fmt", "http://fmtlib.net/",
|
|
||||||
":/licenses/fmt_bsd2.txt");
|
|
||||||
addLicense(form.getElement(), "LibCommuni",
|
addLicense(form.getElement(), "LibCommuni",
|
||||||
"https://github.com/communi/libcommuni",
|
"https://github.com/communi/libcommuni",
|
||||||
":/licenses/libcommuni_BSD3.txt");
|
":/licenses/libcommuni_BSD3.txt");
|
||||||
|
@ -166,7 +163,7 @@ AboutPage::AboutPage()
|
||||||
|
|
||||||
if (contributorParts.size() != 4)
|
if (contributorParts.size() != 4)
|
||||||
{
|
{
|
||||||
log("Missing parts in line '{}'", line);
|
qDebug() << "Missing parts in line" << line;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -547,6 +547,33 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
||||||
QDesktopServices::openUrl(getPaths()->rootAppDataDirectory);
|
QDesktopServices::openUrl(getPaths()->rootAppDataDirectory);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
layout.addTitle("Similarity");
|
||||||
|
layout.addDescription(
|
||||||
|
"Hides or grays out similar messages from the same user");
|
||||||
|
layout.addCheckbox("Similarity enabled", s.similarityEnabled);
|
||||||
|
layout.addCheckbox("Gray out similar messages", s.colorSimilarDisabled);
|
||||||
|
layout.addCheckbox("Hide similar messages (Ctrl + H)", s.hideSimilar);
|
||||||
|
layout.addCheckbox("Hide or gray out my own similar messages",
|
||||||
|
s.hideSimilarMyself);
|
||||||
|
layout.addCheckbox("Shown similar messages trigger highlights",
|
||||||
|
s.shownSimilarTriggerHighlights);
|
||||||
|
layout.addDropdown<float>(
|
||||||
|
"Similarity percentage", {"0.5", "0.75", "0.9"}, s.similarityPercentage,
|
||||||
|
[](auto val) { return QString::number(val); },
|
||||||
|
[](auto args) { return fuzzyToFloat(args.value, 0.9f); });
|
||||||
|
s.hideSimilar.connect(
|
||||||
|
[]() { getApp()->windows->forceLayoutChannelViews(); }, false);
|
||||||
|
layout.addDropdown<int>(
|
||||||
|
"Similar messages max delay in seconds",
|
||||||
|
{"5", "10", "15", "30", "60", "120"}, s.hideSimilarMaxDelay,
|
||||||
|
[](auto val) { return QString::number(val); },
|
||||||
|
[](auto args) { return fuzzyToInt(args.value, 5); });
|
||||||
|
layout.addDropdown<int>(
|
||||||
|
"Similar messages max previous messages to check",
|
||||||
|
{"1", "2", "3", "4", "5"}, s.hideSimilarMaxMessagesToCheck,
|
||||||
|
[](auto val) { return QString::number(val); },
|
||||||
|
[](auto args) { return fuzzyToInt(args.value, 3); });
|
||||||
|
|
||||||
// invisible element for width
|
// invisible element for width
|
||||||
auto inv = new BaseWidget(this);
|
auto inv = new BaseWidget(this);
|
||||||
inv->setScaleIndependantWidth(500);
|
inv->setScaleIndependantWidth(500);
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
#include "controllers/highlights/HighlightController.hpp"
|
#include "controllers/highlights/HighlightController.hpp"
|
||||||
#include "controllers/highlights/HighlightModel.hpp"
|
#include "controllers/highlights/HighlightModel.hpp"
|
||||||
#include "controllers/highlights/UserHighlightModel.hpp"
|
#include "controllers/highlights/UserHighlightModel.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
|
#include "singletons/Theme.hpp"
|
||||||
#include "util/LayoutCreator.hpp"
|
#include "util/LayoutCreator.hpp"
|
||||||
#include "util/StandardItemHelper.hpp"
|
#include "util/StandardItemHelper.hpp"
|
||||||
#include "widgets/helper/EditableModelView.hpp"
|
#include "widgets/dialogs/ColorPickerDialog.hpp"
|
||||||
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
|
@ -46,8 +46,10 @@ HighlightingPage::HighlightingPage()
|
||||||
// HIGHLIGHTS
|
// HIGHLIGHTS
|
||||||
auto highlights = tabs.appendTab(new QVBoxLayout, "Messages");
|
auto highlights = tabs.appendTab(new QVBoxLayout, "Messages");
|
||||||
{
|
{
|
||||||
highlights.emplace<QLabel>("Messages can be highlighted if "
|
highlights.emplace<QLabel>(
|
||||||
"they match a certain pattern.");
|
"Messages can be highlighted if they match a certain "
|
||||||
|
"pattern.\n"
|
||||||
|
"NOTE: User highlights will override phrase highlights.");
|
||||||
|
|
||||||
EditableModelView *view =
|
EditableModelView *view =
|
||||||
highlights
|
highlights
|
||||||
|
@ -57,7 +59,8 @@ HighlightingPage::HighlightingPage()
|
||||||
|
|
||||||
view->addRegexHelpLink();
|
view->addRegexHelpLink();
|
||||||
view->setTitles({"Pattern", "Flash\ntaskbar", "Play\nsound",
|
view->setTitles({"Pattern", "Flash\ntaskbar", "Play\nsound",
|
||||||
"Enable\nregex", "Case-\nsensitive"});
|
"Enable\nregex", "Case-\nsensitive",
|
||||||
|
"Custom\nsound", "Color"});
|
||||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||||
QHeaderView::Fixed);
|
QHeaderView::Fixed);
|
||||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||||
|
@ -72,14 +75,22 @@ HighlightingPage::HighlightingPage()
|
||||||
|
|
||||||
view->addButtonPressed.connect([] {
|
view->addButtonPressed.connect([] {
|
||||||
getApp()->highlights->phrases.appendItem(HighlightPhrase{
|
getApp()->highlights->phrases.appendItem(HighlightPhrase{
|
||||||
"my phrase", true, false, false, false});
|
"my phrase", true, false, false, false, "",
|
||||||
|
*ColorProvider::instance().color(
|
||||||
|
ColorType::SelfHighlight)});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QObject::connect(view->getTableView(), &QTableView::clicked,
|
||||||
|
[this, view](const QModelIndex &clicked) {
|
||||||
|
this->tableCellClicked(clicked, view);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto pingUsers = tabs.appendTab(new QVBoxLayout, "Users");
|
auto pingUsers = tabs.appendTab(new QVBoxLayout, "Users");
|
||||||
{
|
{
|
||||||
pingUsers.emplace<QLabel>(
|
pingUsers.emplace<QLabel>(
|
||||||
"Messages from a certain user can be highlighted.");
|
"Messages from a certain user can be highlighted.\n"
|
||||||
|
"NOTE: User highlights will override phrase highlights.");
|
||||||
EditableModelView *view =
|
EditableModelView *view =
|
||||||
pingUsers
|
pingUsers
|
||||||
.emplace<EditableModelView>(
|
.emplace<EditableModelView>(
|
||||||
|
@ -90,9 +101,10 @@ HighlightingPage::HighlightingPage()
|
||||||
view->getTableView()->horizontalHeader()->hideSection(4);
|
view->getTableView()->horizontalHeader()->hideSection(4);
|
||||||
|
|
||||||
// Case-sensitivity doesn't make sense for user names so it is
|
// Case-sensitivity doesn't make sense for user names so it is
|
||||||
// set to "false" by default & no checkbox is shown
|
// set to "false" by default & the column is hidden
|
||||||
view->setTitles({"Username", "Flash\ntaskbar", "Play\nsound",
|
view->setTitles({"Username", "Flash\ntaskbar", "Play\nsound",
|
||||||
"Enable\nregex"});
|
"Enable\nregex", "Case-\nsensitive",
|
||||||
|
"Custom\nsound", "Color"});
|
||||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||||
QHeaderView::Fixed);
|
QHeaderView::Fixed);
|
||||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||||
|
@ -108,8 +120,15 @@ HighlightingPage::HighlightingPage()
|
||||||
view->addButtonPressed.connect([] {
|
view->addButtonPressed.connect([] {
|
||||||
getApp()->highlights->highlightedUsers.appendItem(
|
getApp()->highlights->highlightedUsers.appendItem(
|
||||||
HighlightPhrase{"highlighted user", true, false, false,
|
HighlightPhrase{"highlighted user", true, false, false,
|
||||||
false});
|
false, "",
|
||||||
|
*ColorProvider::instance().color(
|
||||||
|
ColorType::SelfHighlight)});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QObject::connect(view->getTableView(), &QTableView::clicked,
|
||||||
|
[this, view](const QModelIndex &clicked) {
|
||||||
|
this->tableCellClicked(clicked, view);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto disabledUsers =
|
auto disabledUsers =
|
||||||
|
@ -148,17 +167,33 @@ HighlightingPage::HighlightingPage()
|
||||||
// MISC
|
// MISC
|
||||||
auto customSound = layout.emplace<QHBoxLayout>().withoutMargin();
|
auto customSound = layout.emplace<QHBoxLayout>().withoutMargin();
|
||||||
{
|
{
|
||||||
customSound.append(this->createCheckBox(
|
auto fallbackSound = customSound.append(this->createCheckBox(
|
||||||
"Custom sound", getSettings()->customHighlightSound));
|
"Fallback sound (played when no other sound is set)",
|
||||||
|
getSettings()->customHighlightSound));
|
||||||
|
|
||||||
|
auto getSelectFileText = [] {
|
||||||
|
const QString value = getSettings()->pathHighlightSound;
|
||||||
|
return value.isEmpty() ? "Select custom fallback sound"
|
||||||
|
: QUrl::fromLocalFile(value).fileName();
|
||||||
|
};
|
||||||
|
|
||||||
auto selectFile =
|
auto selectFile =
|
||||||
customSound.emplace<QPushButton>("Select custom sound file");
|
customSound.emplace<QPushButton>(getSelectFileText());
|
||||||
QObject::connect(selectFile.getElement(), &QPushButton::clicked,
|
|
||||||
this, [this] {
|
QObject::connect(
|
||||||
auto fileName = QFileDialog::getOpenFileName(
|
selectFile.getElement(), &QPushButton::clicked, this,
|
||||||
this, tr("Open Sound"), "",
|
[=]() mutable {
|
||||||
tr("Audio Files (*.mp3 *.wav)"));
|
auto fileName = QFileDialog::getOpenFileName(
|
||||||
getSettings()->pathHighlightSound = fileName;
|
this, tr("Open Sound"), "",
|
||||||
});
|
tr("Audio Files (*.mp3 *.wav)"));
|
||||||
|
|
||||||
|
getSettings()->pathHighlightSound = fileName;
|
||||||
|
selectFile.getElement()->setText(getSelectFileText());
|
||||||
|
|
||||||
|
// Set check box according to updated value
|
||||||
|
fallbackSound->setCheckState(
|
||||||
|
fileName.isEmpty() ? Qt::Unchecked : Qt::Checked);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
layout.append(createCheckBox(ALWAYS_PLAY,
|
layout.append(createCheckBox(ALWAYS_PLAY,
|
||||||
|
@ -172,4 +207,71 @@ HighlightingPage::HighlightingPage()
|
||||||
this->disabledUsersChangedTimer_.setSingleShot(true);
|
this->disabledUsersChangedTimer_.setSingleShot(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HighlightingPage::tableCellClicked(const QModelIndex &clicked,
|
||||||
|
EditableModelView *view)
|
||||||
|
{
|
||||||
|
using Column = HighlightModel::Column;
|
||||||
|
|
||||||
|
if (clicked.column() == Column::SoundPath)
|
||||||
|
{
|
||||||
|
auto fileUrl = QFileDialog::getOpenFileUrl(
|
||||||
|
this, tr("Open Sound"), QUrl(), tr("Audio Files (*.mp3 *.wav)"));
|
||||||
|
view->getModel()->setData(clicked, fileUrl, Qt::UserRole);
|
||||||
|
view->getModel()->setData(clicked, fileUrl.fileName(), Qt::DisplayRole);
|
||||||
|
|
||||||
|
// Enable custom sound check box if user set a sound
|
||||||
|
if (!fileUrl.isEmpty())
|
||||||
|
{
|
||||||
|
QModelIndex checkBox = clicked.siblingAtColumn(Column::PlaySound);
|
||||||
|
view->getModel()->setData(checkBox, Qt::Checked,
|
||||||
|
Qt::CheckStateRole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (clicked.column() == Column::Color)
|
||||||
|
{
|
||||||
|
auto initial =
|
||||||
|
view->getModel()->data(clicked, Qt::DecorationRole).value<QColor>();
|
||||||
|
|
||||||
|
auto dialog = new ColorPickerDialog(initial, this);
|
||||||
|
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
dialog->show();
|
||||||
|
dialog->closed.connect([=] {
|
||||||
|
QColor selected = dialog->selectedColor();
|
||||||
|
|
||||||
|
if (selected.isValid())
|
||||||
|
{
|
||||||
|
view->getModel()->setData(clicked, selected,
|
||||||
|
Qt::DecorationRole);
|
||||||
|
|
||||||
|
// Hacky (?) way to figure out what tab the cell was clicked in
|
||||||
|
const bool fromMessages = (dynamic_cast<HighlightModel *>(
|
||||||
|
view->getModel()) != nullptr);
|
||||||
|
|
||||||
|
if (fromMessages)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* For preset highlights in the "Messages" tab, we need to
|
||||||
|
* manually update the color map.
|
||||||
|
*/
|
||||||
|
auto instance = ColorProvider::instance();
|
||||||
|
switch (clicked.row())
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
instance.updateColor(ColorType::SelfHighlight,
|
||||||
|
selected);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
instance.updateColor(ColorType::Whisper, selected);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
instance.updateColor(ColorType::Subscription,
|
||||||
|
selected);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "widgets/helper/EditableModelView.hpp"
|
||||||
#include "widgets/settingspages/SettingsPage.hpp"
|
#include "widgets/settingspages/SettingsPage.hpp"
|
||||||
|
|
||||||
#include <QAbstractTableModel>
|
#include <QAbstractTableModel>
|
||||||
|
@ -17,6 +18,8 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTimer disabledUsersChangedTimer_;
|
QTimer disabledUsersChangedTimer_;
|
||||||
|
|
||||||
|
void tableCellClicked(const QModelIndex &clicked, EditableModelView *view);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -26,6 +26,10 @@ KeyboardSettingsPage::KeyboardSettingsPage()
|
||||||
form->addRow(new QLabel("Ctrl + Shift + T"), new QLabel("Create new tab"));
|
form->addRow(new QLabel("Ctrl + Shift + T"), new QLabel("Create new tab"));
|
||||||
form->addRow(new QLabel("Ctrl + Shift + W"),
|
form->addRow(new QLabel("Ctrl + Shift + W"),
|
||||||
new QLabel("Close current tab"));
|
new QLabel("Close current tab"));
|
||||||
|
form->addRow(
|
||||||
|
new QLabel("Ctrl + H"),
|
||||||
|
new QLabel(
|
||||||
|
"Hide/Show similar messages (Enable in General under Similarity)"));
|
||||||
|
|
||||||
form->addItem(new QSpacerItem(16, 16));
|
form->addItem(new QSpacerItem(16, 16));
|
||||||
form->addRow(new QLabel("Ctrl + 1/2/3/..."),
|
form->addRow(new QLabel("Ctrl + 1/2/3/..."),
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#include "common/Env.hpp"
|
#include "common/Env.hpp"
|
||||||
#include "common/NetworkRequest.hpp"
|
#include "common/NetworkRequest.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
#include "debug/Log.hpp"
|
|
||||||
#include "providers/twitch/EmoteValue.hpp"
|
#include "providers/twitch/EmoteValue.hpp"
|
||||||
#include "providers/twitch/TwitchChannel.hpp"
|
#include "providers/twitch/TwitchChannel.hpp"
|
||||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||||
|
@ -13,6 +12,7 @@
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
#include "util/NuulsUploader.hpp"
|
#include "util/NuulsUploader.hpp"
|
||||||
|
#include "util/Clipboard.hpp"
|
||||||
#include "util/Shortcut.hpp"
|
#include "util/Shortcut.hpp"
|
||||||
#include "util/StreamLink.hpp"
|
#include "util/StreamLink.hpp"
|
||||||
#include "widgets/Notebook.hpp"
|
#include "widgets/Notebook.hpp"
|
||||||
|
@ -537,7 +537,7 @@ void Split::openInStreamlink()
|
||||||
}
|
}
|
||||||
catch (const Exception &ex)
|
catch (const Exception &ex)
|
||||||
{
|
{
|
||||||
log("Error in doOpenStreamlink: {}", ex.what());
|
qDebug() << "Error in doOpenStreamlink:" << ex.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -667,7 +667,7 @@ void Split::openSubPage()
|
||||||
|
|
||||||
void Split::copyToClipboard()
|
void Split::copyToClipboard()
|
||||||
{
|
{
|
||||||
QApplication::clipboard()->setText(this->view_->getSelectedText());
|
crossPlatformCopy(this->view_->getSelectedText());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Split::showSearch()
|
void Split::showSearch()
|
||||||
|
|
|
@ -577,10 +577,15 @@ void SplitHeader::updateModerationModeIcon()
|
||||||
auto channel = this->split_->getChannel();
|
auto channel = this->split_->getChannel();
|
||||||
auto twitchChannel = dynamic_cast<TwitchChannel *>(channel.get());
|
auto twitchChannel = dynamic_cast<TwitchChannel *>(channel.get());
|
||||||
|
|
||||||
if (twitchChannel != nullptr && twitchChannel->hasModRights())
|
if (twitchChannel != nullptr &&
|
||||||
|
(twitchChannel->hasModRights() || moderationMode))
|
||||||
|
{
|
||||||
this->moderationButton_->show();
|
this->moderationButton_->show();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
this->moderationButton_->hide();
|
this->moderationButton_->hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SplitHeader::paintEvent(QPaintEvent *)
|
void SplitHeader::paintEvent(QPaintEvent *)
|
||||||
|
|
Loading…
Reference in a new issue