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
|
||||
|
||||
# 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)
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
run: |
|
||||
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
|
||||
choco install conan -y -s="https://api.bintray.com/nuget/anotherfoxguy/choco-pkg"
|
||||
|
||||
refreshenv
|
||||
shell: cmd
|
||||
|
|
|
@ -32,6 +32,7 @@ matrix:
|
|||
- git config --global user.email "builds@travis-ci.com"
|
||||
- git config --global user.name "Travis CI"
|
||||
- export GIT_TAG=nightly-build
|
||||
- export TRAVIS_TAG=nightly-build
|
||||
- git tag $GIT_TAG -f
|
||||
- sh ./.CI/CreateAppImage.sh
|
||||
|
||||
|
@ -83,6 +84,7 @@ matrix:
|
|||
- git config --global user.email "builds@travis-ci.com"
|
||||
- git config --global user.name "Travis CI"
|
||||
- export GIT_TAG=nightly-build
|
||||
- export TRAVIS_TAG=nightly-build
|
||||
- git tag $GIT_TAG -f
|
||||
|
||||
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.
|
||||
|
||||
### 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
|
||||
3. In QT Creator, select `Help` > `About Plugins` > `C++` > `Beautifier` to enable the plugin
|
||||
4. Restart QT Creator
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
# 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
|
||||
# - WEBSOCKETPP_PREFIX ($$PWD by default)
|
||||
# - WEBSOCKETPP_SYSTEM (1 = true) (unix only)
|
||||
|
@ -76,7 +73,6 @@ CONFIG(debug, debug|release) {
|
|||
|
||||
# Submodules
|
||||
include(lib/warnings.pri)
|
||||
include(lib/fmt.pri)
|
||||
include(lib/humanize.pri)
|
||||
include(lib/libcommuni.pri)
|
||||
include(lib/websocketpp.pri)
|
||||
|
@ -137,6 +133,7 @@ SOURCES += \
|
|||
src/controllers/highlights/HighlightBlacklistModel.cpp \
|
||||
src/controllers/highlights/HighlightController.cpp \
|
||||
src/controllers/highlights/HighlightModel.cpp \
|
||||
src/controllers/highlights/HighlightPhrase.cpp \
|
||||
src/controllers/highlights/UserHighlightModel.cpp \
|
||||
src/controllers/ignores/IgnoreController.cpp \
|
||||
src/controllers/ignores/IgnoreModel.cpp \
|
||||
|
@ -170,6 +167,7 @@ SOURCES += \
|
|||
src/providers/bttv/BttvEmotes.cpp \
|
||||
src/providers/bttv/LoadBttvChannelEmote.cpp \
|
||||
src/providers/chatterino/ChatterinoBadges.cpp \
|
||||
src/providers/colors/ColorProvider.cpp \
|
||||
src/providers/emoji/Emojis.cpp \
|
||||
src/providers/ffz/FfzEmotes.cpp \
|
||||
src/providers/irc/AbstractIrcServer.cpp \
|
||||
|
@ -214,6 +212,7 @@ SOURCES += \
|
|||
src/singletons/TooltipPreviewImage.cpp \
|
||||
src/singletons/Updates.cpp \
|
||||
src/singletons/WindowManager.cpp \
|
||||
src/util/Clipboard.cpp \
|
||||
src/util/DebugCount.cpp \
|
||||
src/util/FormatTime.cpp \
|
||||
src/util/FunctionEventFilter.cpp \
|
||||
|
@ -232,6 +231,7 @@ SOURCES += \
|
|||
src/widgets/BasePopup.cpp \
|
||||
src/widgets/BaseWidget.cpp \
|
||||
src/widgets/BaseWindow.cpp \
|
||||
src/widgets/dialogs/ColorPickerDialog.cpp \
|
||||
src/widgets/dialogs/EmotePopup.cpp \
|
||||
src/widgets/dialogs/IrcConnectionEditor.cpp \
|
||||
src/widgets/dialogs/LastRunCrashDialog.cpp \
|
||||
|
@ -247,12 +247,14 @@ SOURCES += \
|
|||
src/widgets/dialogs/WelcomeDialog.cpp \
|
||||
src/widgets/helper/Button.cpp \
|
||||
src/widgets/helper/ChannelView.cpp \
|
||||
src/widgets/helper/ColorButton.cpp \
|
||||
src/widgets/helper/ComboBoxItemDelegate.cpp \
|
||||
src/widgets/helper/DebugPopup.cpp \
|
||||
src/widgets/helper/EditableModelView.cpp \
|
||||
src/widgets/helper/EffectLabel.cpp \
|
||||
src/widgets/helper/NotebookButton.cpp \
|
||||
src/widgets/helper/NotebookTab.cpp \
|
||||
src/widgets/helper/QColorPicker.cpp \
|
||||
src/widgets/helper/ResizingTextEdit.cpp \
|
||||
src/widgets/helper/ScrollbarHighlight.cpp \
|
||||
src/widgets/helper/SearchPopup.cpp \
|
||||
|
@ -345,7 +347,6 @@ HEADERS += \
|
|||
src/controllers/taggedusers/TaggedUsersModel.hpp \
|
||||
src/debug/AssertInGuiThread.hpp \
|
||||
src/debug/Benchmark.hpp \
|
||||
src/debug/Log.hpp \
|
||||
src/ForwardDecl.hpp \
|
||||
src/messages/Emote.hpp \
|
||||
src/messages/Image.hpp \
|
||||
|
@ -371,6 +372,7 @@ HEADERS += \
|
|||
src/providers/bttv/BttvEmotes.hpp \
|
||||
src/providers/bttv/LoadBttvChannelEmote.hpp \
|
||||
src/providers/chatterino/ChatterinoBadges.hpp \
|
||||
src/providers/colors/ColorProvider.hpp \
|
||||
src/providers/emoji/Emojis.hpp \
|
||||
src/providers/ffz/FfzEmotes.hpp \
|
||||
src/providers/irc/AbstractIrcServer.hpp \
|
||||
|
@ -418,6 +420,7 @@ HEADERS += \
|
|||
src/singletons/Updates.hpp \
|
||||
src/singletons/WindowManager.hpp \
|
||||
src/util/Clamp.hpp \
|
||||
src/util/Clipboard.hpp \
|
||||
src/util/CombinePath.hpp \
|
||||
src/util/ConcurrentMap.hpp \
|
||||
src/util/DebugCount.hpp \
|
||||
|
@ -455,6 +458,7 @@ HEADERS += \
|
|||
src/widgets/BasePopup.hpp \
|
||||
src/widgets/BaseWidget.hpp \
|
||||
src/widgets/BaseWindow.hpp \
|
||||
src/widgets/dialogs/ColorPickerDialog.hpp \
|
||||
src/widgets/dialogs/EmotePopup.hpp \
|
||||
src/widgets/dialogs/IrcConnectionEditor.hpp \
|
||||
src/widgets/dialogs/LastRunCrashDialog.hpp \
|
||||
|
@ -470,6 +474,7 @@ HEADERS += \
|
|||
src/widgets/dialogs/WelcomeDialog.hpp \
|
||||
src/widgets/helper/Button.hpp \
|
||||
src/widgets/helper/ChannelView.hpp \
|
||||
src/widgets/helper/ColorButton.hpp \
|
||||
src/widgets/helper/ComboBoxItemDelegate.hpp \
|
||||
src/widgets/helper/CommonTexts.hpp \
|
||||
src/widgets/helper/DebugPopup.hpp \
|
||||
|
@ -478,6 +483,7 @@ HEADERS += \
|
|||
src/widgets/helper/Line.hpp \
|
||||
src/widgets/helper/NotebookButton.hpp \
|
||||
src/widgets/helper/NotebookTab.hpp \
|
||||
src/widgets/helper/QColorPicker.hpp \
|
||||
src/widgets/helper/ResizingTextEdit.hpp \
|
||||
src/widgets/helper/ScrollbarHighlight.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>licenses/boost_boost.txt</file>
|
||||
<file>licenses/emoji-data-source.txt</file>
|
||||
<file>licenses/fmt_bsd2.txt</file>
|
||||
<file>licenses/libcommuni_BSD3.txt</file>
|
||||
<file>licenses/openssl.txt</file>
|
||||
<file>licenses/pajlada_settings.txt</file>
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include "controllers/notifications/NotificationController.hpp"
|
||||
#include "controllers/pings/PingController.hpp"
|
||||
#include "controllers/taggedusers/TaggedUsersController.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/bttv/BttvEmotes.hpp"
|
||||
#include "providers/chatterino/ChatterinoBadges.hpp"
|
||||
|
@ -158,11 +157,11 @@ void Application::initNm(Paths &paths)
|
|||
void Application::initPubsub()
|
||||
{
|
||||
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) {
|
||||
log("WHISPER RECEIVED LOL"); //
|
||||
qDebug() << "WHISPER RECEIVED LOL"; //
|
||||
});
|
||||
|
||||
this->twitch.pubsub->signals_.moderation.chatCleared.connect(
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
# include <fmt/format.h>
|
||||
# include <irccommand.h>
|
||||
# include <ircconnection.h>
|
||||
# include <rapidjson/document.h>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
#include "common/Channel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/twitch/IrcMessageHandler.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/Logging.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/CommandController.hpp"
|
||||
#include "debug/Benchmark.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#include "DownloadManager.hpp"
|
||||
|
||||
#include "debug/Log.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
|
||||
#include <QDesktopServices>
|
||||
|
@ -41,7 +40,7 @@ void DownloadManager::setFile(QString fileURL, const QString &channelName)
|
|||
|
||||
void DownloadManager::onDownloadProgress(qint64 bytesRead, qint64 bytesTotal)
|
||||
{
|
||||
log("Download progress: {}/{}", bytesRead, bytesTotal);
|
||||
qDebug() << "Download progress: " << bytesRead << "/" << bytesTotal;
|
||||
}
|
||||
|
||||
void DownloadManager::onFinished(QNetworkReply *reply)
|
||||
|
|
|
@ -61,7 +61,7 @@ Env::Env()
|
|||
"https://i.nuuls.com/upload"))
|
||||
, twitchServerHost(
|
||||
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))
|
||||
{
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include "common/NetworkResult.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "debug/AssertInGuiThread.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
|
@ -124,7 +123,7 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
|
|||
|
||||
if (reply == nullptr)
|
||||
{
|
||||
log("Unhandled request type");
|
||||
qDebug() << "Unhandled request type";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -132,7 +131,7 @@ void loadUncached(const std::shared_ptr<NetworkData> &data)
|
|||
{
|
||||
QObject::connect(
|
||||
data->timer_, &QTimer::timeout, worker, [reply, data]() {
|
||||
log("Aborted!");
|
||||
qDebug() << "Aborted!";
|
||||
reply->abort();
|
||||
if (data->onError_)
|
||||
{
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include "common/Outcome.hpp"
|
||||
#include "common/Version.hpp"
|
||||
#include "debug/AssertInGuiThread.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#include "common/NetworkResult.hpp"
|
||||
|
||||
#include "debug/Log.hpp"
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/error/en.h>
|
||||
#include <QJsonDocument>
|
||||
|
@ -45,8 +43,9 @@ rapidjson::Document NetworkResult::parseRapidJson() const
|
|||
|
||||
if (result.Code() != rapidjson::kParseErrorNone)
|
||||
{
|
||||
log("JSON parse error: {} ({})",
|
||||
rapidjson::GetParseError_En(result.Code()), result.Offset());
|
||||
qDebug() << "JSON parse error:"
|
||||
<< rapidjson::GetParseError_En(result.Code()) << "("
|
||||
<< result.Offset() << ")";
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/commands/Command.hpp"
|
||||
#include "controllers/commands/CommandModel.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
|
@ -18,6 +17,7 @@
|
|||
#include "singletons/Theme.hpp"
|
||||
#include "util/CombinePath.hpp"
|
||||
#include "widgets/dialogs/LogsPopup.hpp"
|
||||
#include "widgets/dialogs/UserInfoPopup.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFile>
|
||||
|
@ -482,6 +482,21 @@ QString CommandController::execCommand(const QString &textNoEmoji,
|
|||
channelName + "/viewercard/" + words[1]);
|
||||
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
|
||||
|
||||
return HighlightBlacklistUser{row[0]->data(Qt::DisplayRole).toString(),
|
||||
row[1]->data(Qt::CheckStateRole).toBool()};
|
||||
return HighlightBlacklistUser{
|
||||
row[Column::Pattern]->data(Qt::DisplayRole).toString(),
|
||||
row[Column::UseRegex]->data(Qt::CheckStateRole).toBool()};
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void HighlightBlacklistModel::getRowFromItem(const HighlightBlacklistUser &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item.getPattern());
|
||||
setBoolItem(row[1], item.isRegex());
|
||||
setStringItem(row[Column::Pattern], item.getPattern());
|
||||
setBoolItem(row[Column::UseRegex], item.isRegex());
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -13,6 +13,12 @@ class HighlightBlacklistModel : public SignalVectorModel<HighlightBlacklistUser>
|
|||
{
|
||||
explicit HighlightBlacklistModel(QObject *parent);
|
||||
|
||||
public:
|
||||
enum Column {
|
||||
Pattern = 0,
|
||||
UseRegex = 1,
|
||||
};
|
||||
|
||||
protected:
|
||||
// turn a vector item into a model row
|
||||
virtual HighlightBlacklistUser getItemFromRow(
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace chatterino {
|
|||
|
||||
// commandmodel
|
||||
HighlightModel::HighlightModel(QObject *parent)
|
||||
: SignalVectorModel<HighlightPhrase>(5, parent)
|
||||
: SignalVectorModel<HighlightPhrase>(7, parent)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -16,52 +16,103 @@ HighlightModel::HighlightModel(QObject *parent)
|
|||
HighlightPhrase HighlightModel::getItemFromRow(
|
||||
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(),
|
||||
row[1]->data(Qt::CheckStateRole).toBool(),
|
||||
row[2]->data(Qt::CheckStateRole).toBool(),
|
||||
row[3]->data(Qt::CheckStateRole).toBool(),
|
||||
row[4]->data(Qt::CheckStateRole).toBool()};
|
||||
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};
|
||||
}
|
||||
|
||||
// turns a row in the model into a vector item
|
||||
void HighlightModel::getRowFromItem(const HighlightPhrase &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item.getPattern());
|
||||
setBoolItem(row[1], item.getAlert());
|
||||
setBoolItem(row[2], item.getSound());
|
||||
setBoolItem(row[3], item.isRegex());
|
||||
setBoolItem(row[4], item.isCaseSensitive());
|
||||
setStringItem(row[Column::Pattern], item.getPattern());
|
||||
setBoolItem(row[Column::FlashTaskbar], item.hasAlert());
|
||||
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());
|
||||
}
|
||||
|
||||
void HighlightModel::afterInit()
|
||||
{
|
||||
// Highlight settings for own username
|
||||
std::vector<QStandardItem *> usernameRow = this->createRow();
|
||||
setBoolItem(usernameRow[0], getSettings()->enableSelfHighlight.getValue(),
|
||||
true, false);
|
||||
usernameRow[0]->setData("Your username (automatic)", Qt::DisplayRole);
|
||||
setBoolItem(usernameRow[1],
|
||||
setBoolItem(usernameRow[Column::Pattern],
|
||||
getSettings()->enableSelfHighlight.getValue(), true, false);
|
||||
usernameRow[Column::Pattern]->setData("Your username (automatic)",
|
||||
Qt::DisplayRole);
|
||||
setBoolItem(usernameRow[Column::FlashTaskbar],
|
||||
getSettings()->enableSelfHighlightTaskbar.getValue(), true,
|
||||
false);
|
||||
setBoolItem(usernameRow[2],
|
||||
setBoolItem(usernameRow[Column::PlaySound],
|
||||
getSettings()->enableSelfHighlightSound.getValue(), true,
|
||||
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);
|
||||
|
||||
// Highlight settings for whispers
|
||||
std::vector<QStandardItem *> whisperRow = this->createRow();
|
||||
setBoolItem(whisperRow[0], getSettings()->enableWhisperHighlight.getValue(),
|
||||
true, false);
|
||||
whisperRow[0]->setData("Whispers", Qt::DisplayRole);
|
||||
setBoolItem(whisperRow[1],
|
||||
setBoolItem(whisperRow[Column::Pattern],
|
||||
getSettings()->enableWhisperHighlight.getValue(), true, false);
|
||||
whisperRow[Column::Pattern]->setData("Whispers", Qt::DisplayRole);
|
||||
setBoolItem(whisperRow[Column::FlashTaskbar],
|
||||
getSettings()->enableWhisperHighlightTaskbar.getValue(), true,
|
||||
false);
|
||||
setBoolItem(whisperRow[2],
|
||||
setBoolItem(whisperRow[Column::PlaySound],
|
||||
getSettings()->enableWhisperHighlightSound.getValue(), true,
|
||||
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);
|
||||
|
||||
// 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,
|
||||
|
@ -70,7 +121,7 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
{
|
||||
switch (column)
|
||||
{
|
||||
case 0: {
|
||||
case Column::Pattern: {
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
|
@ -82,10 +133,14 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
getSettings()->enableWhisperHighlight.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
else if (rowIndex == 2)
|
||||
{
|
||||
getSettings()->enableSubHighlight.setValue(value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1: {
|
||||
case Column::FlashTaskbar: {
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
|
@ -98,10 +153,15 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
getSettings()->enableWhisperHighlightTaskbar.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
else if (rowIndex == 2)
|
||||
{
|
||||
getSettings()->enableSubHighlightTaskbar.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 2: {
|
||||
case Column::PlaySound: {
|
||||
if (role == Qt::CheckStateRole)
|
||||
{
|
||||
if (rowIndex == 0)
|
||||
|
@ -114,11 +174,62 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
|
|||
getSettings()->enableWhisperHighlightSound.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
else if (rowIndex == 2)
|
||||
{
|
||||
getSettings()->enableSubHighlightSound.setValue(
|
||||
value.toBool());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3: {
|
||||
// empty element
|
||||
case Column::UseRegex: {
|
||||
// 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;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,18 @@ class HighlightModel : public SignalVectorModel<HighlightPhrase>
|
|||
{
|
||||
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:
|
||||
// turn a vector item into a model row
|
||||
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
|
||||
|
||||
#include "providers/colors/ColorProvider.hpp"
|
||||
#include "util/RapidJsonSerializeQString.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
|
@ -12,70 +13,74 @@ namespace chatterino {
|
|||
class HighlightPhrase
|
||||
{
|
||||
public:
|
||||
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_);
|
||||
}
|
||||
bool operator==(const HighlightPhrase &other) const;
|
||||
|
||||
HighlightPhrase(const QString &pattern, bool alert, bool sound,
|
||||
bool isRegex, bool caseSensitive)
|
||||
: pattern_(pattern)
|
||||
, alert_(alert)
|
||||
, sound_(sound)
|
||||
, isRegex_(isRegex)
|
||||
, caseSensitive_(caseSensitive)
|
||||
, regex_(
|
||||
isRegex_ ? pattern
|
||||
: "\\b" + QRegularExpression::escape(pattern) + "\\b",
|
||||
QRegularExpression::UseUnicodePropertiesOption |
|
||||
(caseSensitive_ ? QRegularExpression::NoPatternOption
|
||||
: QRegularExpression::CaseInsensitiveOption))
|
||||
{
|
||||
}
|
||||
/**
|
||||
* @brief Create a new HighlightPhrase.
|
||||
*
|
||||
* Use this constructor when creating a new HighlightPhrase.
|
||||
*/
|
||||
HighlightPhrase(const QString &pattern, bool hasAlert, bool hasSound,
|
||||
bool isRegex, bool isCaseSensitive, const QString &soundUrl,
|
||||
QColor color);
|
||||
|
||||
const QString &getPattern() const
|
||||
{
|
||||
return this->pattern_;
|
||||
}
|
||||
bool getAlert() const
|
||||
{
|
||||
return this->alert_;
|
||||
}
|
||||
bool getSound() const
|
||||
{
|
||||
return this->sound_;
|
||||
}
|
||||
bool isRegex() const
|
||||
{
|
||||
return this->isRegex_;
|
||||
}
|
||||
/**
|
||||
* @brief Create a new HighlightPhrase.
|
||||
*
|
||||
* Use this constructor when updating an existing HighlightPhrase's color.
|
||||
*/
|
||||
HighlightPhrase(const QString &pattern, bool hasAlert, bool hasSound,
|
||||
bool isRegex, bool isCaseSensitive, const QString &soundUrl,
|
||||
std::shared_ptr<QColor> color);
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return !this->pattern_.isEmpty() && this->regex_.isValid();
|
||||
}
|
||||
const QString &getPattern() const;
|
||||
bool hasAlert() const;
|
||||
|
||||
bool isMatch(const QString &subject) const
|
||||
{
|
||||
return this->isValid() && this->regex_.match(subject).hasMatch();
|
||||
}
|
||||
/**
|
||||
* @brief Check if this highlight phrase should play a sound when
|
||||
* 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
|
||||
{
|
||||
return this->caseSensitive_;
|
||||
}
|
||||
/**
|
||||
* @brief Check if this highlight phrase has a custom sound set.
|
||||
*
|
||||
* 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:
|
||||
QString pattern_;
|
||||
bool alert_;
|
||||
bool sound_;
|
||||
bool hasAlert_;
|
||||
bool hasSound_;
|
||||
bool isRegex_;
|
||||
bool caseSensitive_;
|
||||
bool isCaseSensitive_;
|
||||
QUrl soundUrl_;
|
||||
std::shared_ptr<QColor> color_;
|
||||
QRegularExpression regex_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
namespace pajlada {
|
||||
|
@ -88,10 +93,13 @@ struct Serialize<chatterino::HighlightPhrase> {
|
|||
rapidjson::Value ret(rapidjson::kObjectType);
|
||||
|
||||
chatterino::rj::set(ret, "pattern", value.getPattern(), a);
|
||||
chatterino::rj::set(ret, "alert", value.getAlert(), a);
|
||||
chatterino::rj::set(ret, "sound", value.getSound(), a);
|
||||
chatterino::rj::set(ret, "alert", value.hasAlert(), a);
|
||||
chatterino::rj::set(ret, "sound", value.hasSound(), a);
|
||||
chatterino::rj::set(ret, "regex", value.isRegex(), 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;
|
||||
}
|
||||
|
@ -104,23 +112,30 @@ struct Deserialize<chatterino::HighlightPhrase> {
|
|||
if (!value.IsObject())
|
||||
{
|
||||
return chatterino::HighlightPhrase(QString(), true, false, false,
|
||||
false);
|
||||
false, "", QColor());
|
||||
}
|
||||
|
||||
QString _pattern;
|
||||
bool _alert = true;
|
||||
bool _sound = false;
|
||||
bool _hasAlert = true;
|
||||
bool _hasSound = false;
|
||||
bool _isRegex = false;
|
||||
bool _caseSensitive = false;
|
||||
bool _isCaseSensitive = false;
|
||||
QString _soundUrl;
|
||||
QString encodedColor;
|
||||
|
||||
chatterino::rj::getSafe(value, "pattern", _pattern);
|
||||
chatterino::rj::getSafe(value, "alert", _alert);
|
||||
chatterino::rj::getSafe(value, "sound", _sound);
|
||||
chatterino::rj::getSafe(value, "alert", _hasAlert);
|
||||
chatterino::rj::getSafe(value, "sound", _hasSound);
|
||||
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,
|
||||
_caseSensitive);
|
||||
auto _color = QColor(encodedColor);
|
||||
|
||||
return chatterino::HighlightPhrase(_pattern, _hasAlert, _hasSound,
|
||||
_isRegex, _isCaseSensitive,
|
||||
_soundUrl, _color);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "UserHighlightModel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "controllers/highlights/HighlightModel.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
|
||||
|
@ -8,7 +9,7 @@ namespace chatterino {
|
|||
|
||||
// commandmodel
|
||||
UserHighlightModel::UserHighlightModel(QObject *parent)
|
||||
: SignalVectorModel<HighlightPhrase>(5, parent)
|
||||
: SignalVectorModel<HighlightPhrase>(7, parent)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -16,24 +17,37 @@ UserHighlightModel::UserHighlightModel(QObject *parent)
|
|||
HighlightPhrase UserHighlightModel::getItemFromRow(
|
||||
std::vector<QStandardItem *> &row, const HighlightPhrase &original)
|
||||
{
|
||||
// key, regex
|
||||
using Column = HighlightModel::Column;
|
||||
|
||||
return HighlightPhrase{row[0]->data(Qt::DisplayRole).toString(),
|
||||
row[1]->data(Qt::CheckStateRole).toBool(),
|
||||
row[2]->data(Qt::CheckStateRole).toBool(),
|
||||
row[3]->data(Qt::CheckStateRole).toBool(),
|
||||
row[4]->data(Qt::CheckStateRole).toBool()};
|
||||
// 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[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
|
||||
void UserHighlightModel::getRowFromItem(const HighlightPhrase &item,
|
||||
std::vector<QStandardItem *> &row)
|
||||
{
|
||||
setStringItem(row[0], item.getPattern());
|
||||
setBoolItem(row[1], item.getAlert());
|
||||
setBoolItem(row[2], item.getSound());
|
||||
setBoolItem(row[3], item.isRegex());
|
||||
setBoolItem(row[4], item.isCaseSensitive());
|
||||
using Column = HighlightModel::Column;
|
||||
|
||||
setStringItem(row[Column::Pattern], item.getPattern());
|
||||
setBoolItem(row[Column::FlashTaskbar], item.hasAlert());
|
||||
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
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include "common/NetworkRequest.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "controllers/notifications/NotificationModel.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchApi.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Toasts.hpp"
|
||||
|
@ -147,12 +146,13 @@ void NotificationController::getFakeTwitchChannelLiveStatus(
|
|||
TwitchApi::findUserId(channelName, [channelName, this](QString roomID) {
|
||||
if (roomID.isEmpty())
|
||||
{
|
||||
log("[TwitchChannel:{}] Refreshing live status (Missing ID)",
|
||||
channelName);
|
||||
qDebug() << "[TwitchChannel" << channelName
|
||||
<< "] Refreshing live status (Missing ID)";
|
||||
removeFakeChannel(channelName);
|
||||
return;
|
||||
}
|
||||
log("[TwitchChannel:{}] Refreshing live status", channelName);
|
||||
qDebug() << "[TwitchChannel" << channelName
|
||||
<< "] Refreshing live status";
|
||||
|
||||
QString url("https://api.twitch.tv/kraken/streams/" + roomID);
|
||||
NetworkRequest::twitchRequest(url)
|
||||
|
@ -160,15 +160,15 @@ void NotificationController::getFakeTwitchChannelLiveStatus(
|
|||
rapidjson::Document document = result.parseRapidJson();
|
||||
if (!document.IsObject())
|
||||
{
|
||||
log("[TwitchChannel:refreshLiveStatus]root is not an "
|
||||
"object");
|
||||
qDebug() << "[TwitchChannel:refreshLiveStatus] root is not "
|
||||
"an object";
|
||||
return Failure;
|
||||
}
|
||||
|
||||
if (!document.HasMember("stream"))
|
||||
{
|
||||
log("[TwitchChannel:refreshLiveStatus] Missing stream in "
|
||||
"root");
|
||||
qDebug() << "[TwitchChannel:refreshLiveStatus] Missing "
|
||||
"stream in root";
|
||||
return Failure;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@ BenchmarkGuard::BenchmarkGuard(const QString &_name)
|
|||
|
||||
BenchmarkGuard::~BenchmarkGuard()
|
||||
{
|
||||
log("{} {} ms", this->name_, float(timer_.nsecsElapsed()) / 1000000.0f);
|
||||
qDebug() << this->name_ << float(timer_.nsecsElapsed()) / 1000000.0f
|
||||
<< "ms";
|
||||
}
|
||||
|
||||
qreal BenchmarkGuard::getElapsedMs()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "debug/Log.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QElapsedTimer>
|
||||
#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 "debug/AssertInGuiThread.hpp"
|
||||
#include "debug/Benchmark.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
|
@ -108,8 +107,8 @@ namespace detail {
|
|||
|
||||
if (reader.imageCount() == 0)
|
||||
{
|
||||
log("Error while reading image {}: '{}'", url.string,
|
||||
reader.errorString());
|
||||
qDebug() << "Error while reading image" << url.string << ": '"
|
||||
<< reader.errorString() << "'";
|
||||
return frames;
|
||||
}
|
||||
|
||||
|
@ -127,8 +126,8 @@ namespace detail {
|
|||
|
||||
if (frames.size() == 0)
|
||||
{
|
||||
log("Error while reading image {}: '{}'", url.string,
|
||||
reader.errorString());
|
||||
qDebug() << "Error while reading image" << url.string << ": '"
|
||||
<< reader.errorString() << "'";
|
||||
}
|
||||
|
||||
return frames;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
#include "messages/Message.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "MessageElement.hpp"
|
||||
#include "providers/twitch/PubsubActions.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/DebugCount.hpp"
|
||||
#include "util/IrcHelpers.hpp"
|
||||
|
||||
|
@ -24,11 +27,13 @@ SBHighlight Message::getScrollBarHighlight() const
|
|||
if (this->flags.has(MessageFlag::Highlighted) ||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ enum class MessageFlag : uint32_t {
|
|||
Whisper = (1 << 16),
|
||||
HighlightedWhisper = (1 << 17),
|
||||
Debug = (1 << 18),
|
||||
Similar = (1 << 19),
|
||||
};
|
||||
using MessageFlags = FlagsEnum<MessageFlag>;
|
||||
|
||||
|
@ -55,6 +56,7 @@ struct Message : boost::noncopyable {
|
|||
QString displayName;
|
||||
QString localizedName;
|
||||
QString timeoutUser;
|
||||
std::shared_ptr<QColor> highlightColor;
|
||||
uint32_t count = 1;
|
||||
std::vector<std::unique_ptr<MessageElement>> elements;
|
||||
|
||||
|
|
|
@ -25,6 +25,19 @@
|
|||
|
||||
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)
|
||||
: message_(message)
|
||||
, container_(std::make_shared<MessageLayoutContainer>())
|
||||
|
@ -128,6 +141,12 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags)
|
|||
continue;
|
||||
}
|
||||
|
||||
if (getSettings()->hideSimilar &&
|
||||
this->message_->flags.has(MessageFlag::Similar))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
element->addToContainer(*this->container_, flags);
|
||||
}
|
||||
|
||||
|
@ -249,21 +268,33 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
|
|||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
|
||||
// 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) ||
|
||||
this->message_->flags.has(MessageFlag::HighlightedWhisper)) &&
|
||||
!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;
|
||||
}
|
||||
else if (getSettings()->alternateMessages.getValue() &&
|
||||
this->flags.has(MessageLayoutFlag::AlternateBackground))
|
||||
{
|
||||
backgroundColor = app->themes->messages.backgrounds.alternate;
|
||||
// Blend highlight color with usual background color
|
||||
backgroundColor = blendColors(
|
||||
backgroundColor,
|
||||
*ColorProvider::instance().color(ColorType::Subscription));
|
||||
}
|
||||
else if (this->message_->flags.has(MessageFlag::AutoMod))
|
||||
{
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
#include "common/Common.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
#include "messages/Image.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 "Application.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
|
@ -132,8 +131,9 @@ void Emojis::loadEmojis()
|
|||
|
||||
if (result.Code() != rapidjson::kParseErrorNone)
|
||||
{
|
||||
log("JSON parse error: {} ({})",
|
||||
rapidjson::GetParseError_En(result.Code()), result.Offset());
|
||||
qDebug() << "JSON parse error:"
|
||||
<< rapidjson::GetParseError_En(result.Code()) << "("
|
||||
<< result.Offset() << ")";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -165,8 +165,8 @@ void Emojis::loadEmojis()
|
|||
auto toneNameIt = toneNames.find(tone);
|
||||
if (toneNameIt == toneNames.end())
|
||||
{
|
||||
log("Tone with key {} does not exist in tone names map",
|
||||
tone);
|
||||
qDebug() << "Tone with key" << tone.c_str()
|
||||
<< "does not exist in tone names map";
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
#include "messages/Image.hpp"
|
||||
|
||||
|
@ -186,7 +185,8 @@ void FfzEmotes::loadChannel(
|
|||
const QString &channelId, std::function<void(EmoteMap &&)> emoteCallback,
|
||||
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)
|
||||
|
||||
|
@ -211,13 +211,13 @@ void FfzEmotes::loadChannel(
|
|||
else if (result.status() == NetworkResult::timedoutStatus)
|
||||
{
|
||||
// TODO: Auto retry in case of a timeout, with a delay
|
||||
log("Fetching FFZ emotes for channel {} failed due to timeout",
|
||||
channelId);
|
||||
qDebug() << "Fetching FFZ emotes for channel" << channelId
|
||||
<< "failed due to timeout";
|
||||
}
|
||||
else
|
||||
{
|
||||
log("Error fetching FFZ emotes for channel {}, error {}",
|
||||
channelId, result.status());
|
||||
qDebug() << "Error fetching FFZ emotes for channel" << channelId
|
||||
<< ", error" << result.status();
|
||||
}
|
||||
})
|
||||
.execute();
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/Common.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/LimitedQueueSnapshot.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
|
@ -66,7 +65,7 @@ AbstractIrcServer::AbstractIrcServer()
|
|||
|
||||
if (!this->readConnection_->isConnected())
|
||||
{
|
||||
log("Trying to reconnect... {}", this->falloffCounter_);
|
||||
qDebug() << "Trying to reconnect..." << this->falloffCounter_;
|
||||
this->connect();
|
||||
}
|
||||
});
|
||||
|
@ -159,11 +158,12 @@ ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName)
|
|||
}
|
||||
|
||||
this->channels.insert(channelName, chan);
|
||||
this->connections_.emplace_back(chan->destroyed.connect([this,
|
||||
channelName] {
|
||||
this->connections_.emplace_back(
|
||||
chan->destroyed.connect([this, channelName] {
|
||||
// fourtf: issues when the server itself is destroyed
|
||||
|
||||
log("[AbstractIrcServer::addChannel] {} was destroyed", channelName);
|
||||
qDebug() << "[AbstractIrcServer::addChannel]" << channelName
|
||||
<< "was destroyed";
|
||||
this->channels.remove(channelName);
|
||||
|
||||
if (this->readConnection_)
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
#include "IrcConnection2.hpp"
|
||||
|
||||
#include "common/Version.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
namespace {
|
||||
|
||||
const auto payload = QString("chatterino/" CHATTERINO_VERSION);
|
||||
|
||||
} // namespace
|
||||
|
||||
IrcConnection::IrcConnection(QObject *parent)
|
||||
: Communi::IrcConnection(parent)
|
||||
{
|
||||
|
@ -11,10 +19,17 @@ IrcConnection::IrcConnection(QObject *parent)
|
|||
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
|
||||
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())
|
||||
{
|
||||
this->sendRaw("PING chatterino/ping");
|
||||
this->sendRaw("PING " + payload);
|
||||
this->reconnectTimer_.start();
|
||||
this->waitingForPong_ = true;
|
||||
}
|
||||
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,
|
||||
[this](Communi::IrcMessage *) {
|
||||
this->recentlyReceivedMessage_ = true;
|
||||
|
|
|
@ -18,6 +18,9 @@ private:
|
|||
QTimer pingTimer_;
|
||||
QTimer reconnectTimer_;
|
||||
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
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
#include "Application.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/highlights/HighlightController.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/LimitedQueue.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "providers/twitch/TwitchAccountManager.hpp"
|
||||
|
@ -22,6 +21,97 @@
|
|||
|
||||
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)
|
||||
{
|
||||
QMap<QString, QString> badges;
|
||||
|
@ -134,7 +224,16 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *_message,
|
|||
}
|
||||
|
||||
auto msg = builder.build();
|
||||
|
||||
IrcMessageHandler::setSimilarityFlags(msg, chan);
|
||||
|
||||
if (!msg->flags.has(MessageFlag::Similar) ||
|
||||
(!getSettings()->hideSimilar &&
|
||||
getSettings()->shownSimilarTriggerHighlights))
|
||||
{
|
||||
builder.triggerHighlights();
|
||||
}
|
||||
|
||||
auto highlighted = msg->flags.has(MessageFlag::Highlighted);
|
||||
|
||||
if (!isSub)
|
||||
|
@ -235,9 +334,8 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
|
|||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
|
||||
"found",
|
||||
chanName);
|
||||
qDebug() << "[IrcMessageHandler:handleClearChatMessage] Twitch channel"
|
||||
<< chanName << "not found";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -300,10 +398,9 @@ void IrcMessageHandler::handleClearMessageMessage(Communi::IrcMessage *message)
|
|||
|
||||
if (chan->isEmpty())
|
||||
{
|
||||
log("[IrcMessageHandler:handleClearMessageMessage] Twitch channel {} "
|
||||
"not "
|
||||
"found",
|
||||
chanName);
|
||||
qDebug()
|
||||
<< "[IrcMessageHandler:handleClearMessageMessage] Twitch channel"
|
||||
<< chanName << "not found";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -356,7 +453,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
|
|||
void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
|
||||
{
|
||||
auto app = getApp();
|
||||
log("Received whisper!");
|
||||
qDebug() << "Received whisper!";
|
||||
MessageParseArgs args;
|
||||
|
||||
args.isReceivedWhisper = true;
|
||||
|
@ -568,10 +665,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
|
|||
|
||||
if (channel->isEmpty())
|
||||
{
|
||||
log("[IrcManager:handleNoticeMessage] Channel {} not found in "
|
||||
"channel "
|
||||
"manager ",
|
||||
channelName);
|
||||
qDebug() << "[IrcManager:handleNoticeMessage] Channel"
|
||||
<< channelName << "not found in channel manager";
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <IrcMessage>
|
||||
#include "common/Channel.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
@ -49,6 +50,10 @@ public:
|
|||
void handleJoinMessage(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:
|
||||
void addMessage(Communi::IrcMessage *message, const QString &target,
|
||||
const QString &content, TwitchIrcServer &server,
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include "common/Common.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
|
||||
#include <QJsonArray>
|
||||
|
@ -28,6 +27,13 @@ PartialTwitchUser PartialTwitchUser::byId(const QString &id)
|
|||
|
||||
void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
|
||||
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());
|
||||
|
||||
|
@ -35,35 +41,38 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
|
|||
this->username_)
|
||||
.caller(caller)
|
||||
.authorizeTwitchV5(getDefaultClientID())
|
||||
.onSuccess([successCallback](auto result) -> Outcome {
|
||||
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||
auto root = result.parseJson();
|
||||
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;
|
||||
}
|
||||
|
||||
auto users = root.value("users").toArray();
|
||||
if (users.size() != 1)
|
||||
{
|
||||
log("API Error while getting user id, users array size is not "
|
||||
"1");
|
||||
qDebug() << "API Error while getting user id, users array size "
|
||||
"is not 1";
|
||||
failureCallback();
|
||||
return Failure;
|
||||
}
|
||||
if (!users[0].isObject())
|
||||
{
|
||||
log("API Error while getting user id, first user is not an "
|
||||
"object");
|
||||
qDebug() << "API Error while getting user id, first user is "
|
||||
"not an object";
|
||||
failureCallback();
|
||||
return Failure;
|
||||
}
|
||||
auto firstUser = users[0].toObject();
|
||||
auto id = firstUser.value("_id");
|
||||
if (!id.isString())
|
||||
{
|
||||
log("API Error: while getting user id, first user object `_id` "
|
||||
"key "
|
||||
"is not a "
|
||||
"string");
|
||||
qDebug() << "API Error: while getting user id, first user "
|
||||
"object `_id` key is not a string";
|
||||
failureCallback();
|
||||
return Failure;
|
||||
}
|
||||
successCallback(id.toString());
|
||||
|
|
|
@ -21,6 +21,10 @@ public:
|
|||
|
||||
void getId(std::function<void(QString)> successCallback,
|
||||
const QObject *caller = nullptr);
|
||||
|
||||
void getId(std::function<void(QString)> successCallback,
|
||||
std::function<void()> failureCallback,
|
||||
const QObject *caller = nullptr);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#include "providers/twitch/PubsubClient.hpp"
|
||||
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/PubsubActions.hpp"
|
||||
#include "providers/twitch/PubsubHelpers.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
#include <rapidjson/error/en.h>
|
||||
|
@ -77,14 +77,14 @@ namespace detail {
|
|||
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();)
|
||||
{
|
||||
const auto &listener = *it;
|
||||
if (listener.topic.find(prefix) == 0)
|
||||
if (listener.topic.startsWith(prefix))
|
||||
{
|
||||
topics.push_back(listener.topic);
|
||||
it = this->listeners_.erase(it);
|
||||
|
@ -116,16 +116,14 @@ namespace detail {
|
|||
{
|
||||
assert(this->awaitingPong_);
|
||||
|
||||
log("Got pong!");
|
||||
|
||||
this->awaitingPong_ = false;
|
||||
}
|
||||
|
||||
bool PubSubClient::isListeningToTopic(const std::string &payload)
|
||||
bool PubSubClient::isListeningToTopic(const QString &topic)
|
||||
{
|
||||
for (const auto &listener : this->listeners_)
|
||||
{
|
||||
if (listener.topic == payload)
|
||||
if (listener.topic == topic)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -156,9 +154,8 @@ namespace detail {
|
|||
|
||||
if (self->awaitingPong_)
|
||||
{
|
||||
log("No pong response, disconnect!");
|
||||
// TODO(pajlada): Label this connection as "disconnect
|
||||
// me"
|
||||
qDebug() << "No pong response, disconnect!";
|
||||
// TODO(pajlada): Label this connection as "disconnect me"
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -181,7 +178,8 @@ namespace detail {
|
|||
|
||||
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
|
||||
// gracefully handle it
|
||||
|
||||
|
@ -223,7 +221,7 @@ PubSub::PubSub()
|
|||
|
||||
if (!data.HasMember("args"))
|
||||
{
|
||||
log("Missing required args member");
|
||||
qDebug() << "Missing required args member";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -231,13 +229,13 @@ PubSub::PubSub()
|
|||
|
||||
if (!args.IsArray())
|
||||
{
|
||||
log("args member must be an array");
|
||||
qDebug() << "args member must be an array";
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Size() == 0)
|
||||
{
|
||||
log("Missing duration argument in slowmode on");
|
||||
qDebug() << "Missing duration argument in slowmode on";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -245,7 +243,7 @@ PubSub::PubSub()
|
|||
|
||||
if (!durationArg.IsString())
|
||||
{
|
||||
log("Duration arg must be a string");
|
||||
qDebug() << "Duration arg must be a string";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -338,7 +336,7 @@ PubSub::PubSub()
|
|||
}
|
||||
catch (const std::runtime_error &ex)
|
||||
{
|
||||
log("Error parsing moderation action: {}", ex.what());
|
||||
qDebug() << "Error parsing moderation action:" << ex.what();
|
||||
}
|
||||
|
||||
action.modded = false;
|
||||
|
@ -368,7 +366,7 @@ PubSub::PubSub()
|
|||
}
|
||||
catch (const std::runtime_error &ex)
|
||||
{
|
||||
log("Error parsing moderation action: {}", ex.what());
|
||||
qDebug() << "Error parsing moderation action:" << ex.what();
|
||||
}
|
||||
|
||||
action.modded = true;
|
||||
|
@ -417,7 +415,7 @@ PubSub::PubSub()
|
|||
}
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
log("Error parsing moderation action: {}", ex.what());
|
||||
qDebug() << "Error parsing moderation action:" << ex.what();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -738,7 +736,7 @@ void PubSub::addClient()
|
|||
|
||||
if (ec)
|
||||
{
|
||||
log("Unable to establish connection: {}", ec.message());
|
||||
qDebug() << "Unable to establish connection:" << ec.message().c_str();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -753,20 +751,23 @@ void PubSub::start()
|
|||
|
||||
void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
|
||||
{
|
||||
static const QString topicFormat("whispers.%1");
|
||||
|
||||
assert(account != nullptr);
|
||||
|
||||
std::string userID = account->getUserId().toStdString();
|
||||
auto userID = account->getUserId();
|
||||
|
||||
log("Connection open!");
|
||||
qDebug() << "Connection open!";
|
||||
websocketpp::lib::error_code ec;
|
||||
|
||||
std::vector<std::string> topics({"whispers." + userID});
|
||||
std::vector<QString> topics({topicFormat.arg(userID)});
|
||||
|
||||
this->listen(createListenMessage(topics, account));
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -783,26 +784,26 @@ void PubSub::unlistenAllModerationActions()
|
|||
void PubSub::listenToChannelModerationActions(
|
||||
const QString &channelID, std::shared_ptr<TwitchAccount> account)
|
||||
{
|
||||
static const QString topicFormat("chat_moderator_actions.%1.%2");
|
||||
assert(!channelID.isEmpty());
|
||||
assert(account != nullptr);
|
||||
QString userID = account->getUserId();
|
||||
if (userID.isEmpty())
|
||||
return;
|
||||
|
||||
std::string topic(fS("chat_moderator_actions.{}.{}", userID, channelID));
|
||||
auto topic = topicFormat.arg(userID).arg(channelID);
|
||||
|
||||
if (this->isListeningToTopic(topic))
|
||||
{
|
||||
log("We are already listening to topic {}", topic);
|
||||
return;
|
||||
}
|
||||
|
||||
log("Listen to topic {}", topic);
|
||||
qDebug() << "Listen to topic" << topic;
|
||||
|
||||
this->listenToTopic(topic, account);
|
||||
}
|
||||
|
||||
void PubSub::listenToTopic(const std::string &topic,
|
||||
void PubSub::listenToTopic(const QString &topic,
|
||||
std::shared_ptr<TwitchAccount> account)
|
||||
{
|
||||
auto message = createListenMessage({topic}, account);
|
||||
|
@ -814,20 +815,19 @@ void PubSub::listen(rapidjson::Document &&msg)
|
|||
{
|
||||
if (this->tryListen(msg))
|
||||
{
|
||||
log("Successfully listened!");
|
||||
return;
|
||||
}
|
||||
|
||||
this->addClient();
|
||||
|
||||
log("Added to the back of the queue");
|
||||
qDebug() << "Added to the back of the queue";
|
||||
this->requests.emplace_back(
|
||||
std::make_unique<rapidjson::Document>(std::move(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)
|
||||
{
|
||||
const auto &client = p.second;
|
||||
|
@ -840,7 +840,7 @@ bool PubSub::tryListen(rapidjson::Document &msg)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool PubSub::isListeningToTopic(const std::string &topic)
|
||||
bool PubSub::isListeningToTopic(const QString &topic)
|
||||
{
|
||||
for (const auto &p : this->clients)
|
||||
{
|
||||
|
@ -865,24 +865,24 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
|||
|
||||
if (!res)
|
||||
{
|
||||
log("Error parsing message '{}' from PubSub: {}", payload,
|
||||
rapidjson::GetParseError_En(res.Code()));
|
||||
qDebug() << "Error parsing message '" << payload.c_str()
|
||||
<< "' from PubSub:" << rapidjson::GetParseError_En(res.Code());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!msg.IsObject())
|
||||
{
|
||||
log("Error parsing message '{}' from PubSub. Root object is not an "
|
||||
"object",
|
||||
payload);
|
||||
qDebug() << "Error parsing message '" << payload.c_str()
|
||||
<< "' from PubSub. Root object is not an "
|
||||
"object";
|
||||
return;
|
||||
}
|
||||
|
||||
std::string type;
|
||||
QString 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;
|
||||
}
|
||||
|
||||
|
@ -894,7 +894,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
|||
{
|
||||
if (!msg.HasMember("data"))
|
||||
{
|
||||
log("Missing required object member `data` in message root");
|
||||
qDebug() << "Missing required object member `data` in message root";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -902,7 +902,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
|||
|
||||
if (!data.IsObject())
|
||||
{
|
||||
log("Member `data` must be an object");
|
||||
qDebug() << "Member `data` must be an object";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -922,7 +922,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
|||
}
|
||||
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)
|
||||
{
|
||||
log("Exception caught in OnTLSInit: {}", e.what());
|
||||
qDebug() << "Exception caught in OnTLSInit:" << e.what();
|
||||
}
|
||||
|
||||
return ctx;
|
||||
|
@ -991,21 +991,21 @@ PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl)
|
|||
|
||||
void PubSub::handleListenResponse(const rapidjson::Document &msg)
|
||||
{
|
||||
std::string error;
|
||||
QString error;
|
||||
|
||||
if (rj::getSafe(msg, "error", error))
|
||||
{
|
||||
std::string nonce;
|
||||
QString 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
|
||||
return;
|
||||
}
|
||||
|
||||
log("PubSub error: {} on nonce {}", error, nonce);
|
||||
qDebug() << "PubSub error:" << error << "on nonce" << nonce;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1016,7 +1016,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
|||
|
||||
if (!rj::getSafe(outerData, "topic", topic))
|
||||
{
|
||||
log("Missing required string member `topic` in outerData");
|
||||
qDebug() << "Missing required string member `topic` in outerData";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1024,7 +1024,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
|||
|
||||
if (!rj::getSafe(outerData, "message", payload))
|
||||
{
|
||||
log("Expected string message in outerData");
|
||||
qDebug() << "Expected string message in outerData";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1034,8 +1034,8 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
|||
|
||||
if (!res)
|
||||
{
|
||||
log("Error parsing message '{}' from PubSub: {}", payload,
|
||||
rapidjson::GetParseError_En(res.Code()));
|
||||
qDebug() << "Error parsing message '" << payload.c_str()
|
||||
<< "' from PubSub:" << rapidjson::GetParseError_En(res.Code());
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1045,7 +1045,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
|||
|
||||
if (!rj::getSafe(msg, "type", whisperType))
|
||||
{
|
||||
log("Bad whisper data");
|
||||
qDebug() << "Bad whisper data";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1063,7 +1063,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
|||
}
|
||||
else
|
||||
{
|
||||
log("Invalid whisper type: {}", whisperType);
|
||||
qDebug() << "Invalid whisper type:" << whisperType.c_str();
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
@ -1078,7 +1078,8 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1086,7 +1087,8 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
|||
|
||||
if (handlerIt == this->moderationActionHandlers.end())
|
||||
{
|
||||
log("No handler found for moderation action {}", moderationAction);
|
||||
qDebug() << "No handler found for moderation action"
|
||||
<< moderationAction.c_str();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1095,16 +1097,16 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
|
|||
}
|
||||
else
|
||||
{
|
||||
log("Unknown topic: {}", topic);
|
||||
qDebug() << "Unknown topic:" << topic;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PubSub::runThread()
|
||||
{
|
||||
log("Start pubsub manager thread");
|
||||
qDebug() << "Start pubsub manager thread";
|
||||
this->websocketClient.run();
|
||||
log("Done with pubsub manager thread");
|
||||
qDebug() << "Done with pubsub manager thread";
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
@ -33,7 +32,7 @@ using WebsocketErrorCode = websocketpp::lib::error_code;
|
|||
namespace detail {
|
||||
|
||||
struct Listener {
|
||||
std::string topic;
|
||||
QString topic;
|
||||
bool authed;
|
||||
bool persistent;
|
||||
bool confirmed = false;
|
||||
|
@ -49,11 +48,11 @@ namespace detail {
|
|||
void stop();
|
||||
|
||||
bool listen(rapidjson::Document &message);
|
||||
void unlistenPrefix(const std::string &prefix);
|
||||
void unlistenPrefix(const QString &prefix);
|
||||
|
||||
void handlePong();
|
||||
|
||||
bool isListeningToTopic(const std::string &topic);
|
||||
bool isListeningToTopic(const QString &topic);
|
||||
|
||||
private:
|
||||
void ping();
|
||||
|
@ -135,13 +134,13 @@ public:
|
|||
std::vector<std::unique_ptr<rapidjson::Document>> requests;
|
||||
|
||||
private:
|
||||
void listenToTopic(const std::string &topic,
|
||||
void listenToTopic(const QString &topic,
|
||||
std::shared_ptr<TwitchAccount> account);
|
||||
|
||||
void listen(rapidjson::Document &&msg);
|
||||
bool tryListen(rapidjson::Document &msg);
|
||||
|
||||
bool isListeningToTopic(const std::string &topic);
|
||||
bool isListeningToTopic(const QString &topic);
|
||||
|
||||
void addClient();
|
||||
|
||||
|
|
|
@ -46,8 +46,7 @@ bool getTargetUser(const rapidjson::Value &data, ActionUser &user)
|
|||
return rj::getSafe(data, "target_user_id", user.id);
|
||||
}
|
||||
|
||||
rapidjson::Document createListenMessage(
|
||||
const std::vector<std::string> &topicsVec,
|
||||
rapidjson::Document createListenMessage(const std::vector<QString> &topicsVec,
|
||||
std::shared_ptr<TwitchAccount> account)
|
||||
{
|
||||
rapidjson::Document msg(rapidjson::kObjectType);
|
||||
|
@ -75,8 +74,7 @@ rapidjson::Document createListenMessage(
|
|||
return msg;
|
||||
}
|
||||
|
||||
rapidjson::Document createUnlistenMessage(
|
||||
const std::vector<std::string> &topicsVec)
|
||||
rapidjson::Document createUnlistenMessage(const std::vector<QString> &topicsVec)
|
||||
{
|
||||
rapidjson::Document msg(rapidjson::kObjectType);
|
||||
auto &a = msg.GetAllocator();
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <memory>
|
||||
#include "debug/Log.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
@ -18,11 +17,10 @@ bool getCreatedByUser(const rapidjson::Value &data, ActionUser &user);
|
|||
|
||||
bool getTargetUser(const rapidjson::Value &data, ActionUser &user);
|
||||
|
||||
rapidjson::Document createListenMessage(
|
||||
const std::vector<std::string> &topicsVec,
|
||||
rapidjson::Document createListenMessage(const std::vector<QString> &topicsVec,
|
||||
std::shared_ptr<TwitchAccount> account);
|
||||
rapidjson::Document createUnlistenMessage(
|
||||
const std::vector<std::string> &topicsVec);
|
||||
const std::vector<QString> &topicsVec);
|
||||
|
||||
// Create timer using given ioService
|
||||
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) {
|
||||
if (ec)
|
||||
{
|
||||
log("Error in runAfter: {}", ec.message());
|
||||
qDebug() << "Error in runAfter:" << ec.message().c_str();
|
||||
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) {
|
||||
if (ec)
|
||||
{
|
||||
log("Error in runAfter: {}", ec.message());
|
||||
qDebug() << "Error in runAfter:" << ec.message().c_str();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include "common/Env.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/PartialTwitchUser.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
|
@ -134,8 +133,8 @@ void TwitchAccount::loadIgnores()
|
|||
TwitchUser ignoredUser;
|
||||
if (!rj::getSafe(userIt->value, ignoredUser))
|
||||
{
|
||||
log("Error parsing twitch user JSON {}",
|
||||
rj::stringify(userIt->value));
|
||||
qDebug() << "Error parsing twitch user JSON"
|
||||
<< rj::stringify(userIt->value).c_str();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -345,14 +344,14 @@ std::set<TwitchUser> TwitchAccount::getIgnores() const
|
|||
|
||||
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 &oauthToken = this->getOAuthToken();
|
||||
|
||||
if (clientID.isEmpty() || oauthToken.isEmpty())
|
||||
{
|
||||
log("Missing Client ID or OAuth token");
|
||||
qDebug() << "Missing Client ID or OAuth token";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -363,7 +362,7 @@ void TwitchAccount::loadEmotes()
|
|||
|
||||
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
|
||||
.onError([=](NetworkResult result) {
|
||||
log("[TwitchAccount::loadEmotes] Error {}", result.status());
|
||||
qDebug() << "[TwitchAccount::loadEmotes] Error" << result.status();
|
||||
if (result.status() == 203)
|
||||
{
|
||||
// onFinished(FollowResult_NotFollowing);
|
||||
|
@ -402,7 +401,8 @@ void TwitchAccount::autoModAllow(const QString msgID)
|
|||
|
||||
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
|
||||
.onError([=](NetworkResult result) {
|
||||
log("[TwitchAccounts::autoModAllow] Error {}", result.status());
|
||||
qDebug() << "[TwitchAccounts::autoModAllow] Error"
|
||||
<< result.status();
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
@ -421,7 +421,8 @@ void TwitchAccount::autoModDeny(const QString msgID)
|
|||
|
||||
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
|
||||
.onError([=](NetworkResult result) {
|
||||
log("[TwitchAccounts::autoModDeny] Error {}", result.status());
|
||||
qDebug() << "[TwitchAccounts::autoModDeny] Error"
|
||||
<< result.status();
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
@ -436,7 +437,7 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
|
|||
auto emoticonSets = root.FindMember("emoticon_sets");
|
||||
if (emoticonSets == root.MemberEnd() || !emoticonSets->value.IsObject())
|
||||
{
|
||||
log("No emoticon_sets in load emotes response");
|
||||
qDebug() << "No emoticon_sets in load emotes response";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -452,21 +453,21 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
|
|||
{
|
||||
if (!emoteJSON.IsObject())
|
||||
{
|
||||
log("Emote value was invalid");
|
||||
qDebug() << "Emote value was invalid";
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t idNumber;
|
||||
if (!rj::getSafe(emoteJSON, "id", idNumber))
|
||||
{
|
||||
log("No ID key found in Emote value");
|
||||
qDebug() << "No ID key found in Emote value";
|
||||
return;
|
||||
}
|
||||
|
||||
QString _code;
|
||||
if (!rj::getSafe(emoteJSON, "code", _code))
|
||||
{
|
||||
log("No code key found in Emote value");
|
||||
qDebug() << "No code key found in Emote value";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -481,6 +482,10 @@ void TwitchAccount::parseEmotes(const rapidjson::Document &root)
|
|||
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);
|
||||
}
|
||||
};
|
||||
|
@ -489,7 +494,7 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
|
|||
{
|
||||
if (!emoteSet)
|
||||
{
|
||||
log("null emote set sent");
|
||||
qDebug() << "null emote set sent";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -505,7 +510,8 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
|
|||
NetworkRequest(Env::get().twitchEmoteSetResolverUrl.arg(emoteSet->key))
|
||||
.cache()
|
||||
.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 {
|
||||
auto root = result.parseRapidJson();
|
||||
|
@ -527,7 +533,7 @@ void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
|
|||
return Failure;
|
||||
}
|
||||
|
||||
log("Loaded twitch emote set data for {}!", emoteSet->key);
|
||||
qDebug() << "Loaded twitch emote set data for" << emoteSet->key;
|
||||
|
||||
auto name = channelName;
|
||||
name.detach();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "providers/twitch/TwitchAccountManager.hpp"
|
||||
|
||||
#include "common/Common.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchAccount.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
|
||||
|
@ -103,23 +102,23 @@ void TwitchAccountManager::reloadUsers()
|
|||
switch (this->addUser(userData))
|
||||
{
|
||||
case AddUserResponse::UserAlreadyExists: {
|
||||
log("User {} already exists", userData.username);
|
||||
qDebug() << "User" << userData.username << "already exists";
|
||||
// Do nothing
|
||||
}
|
||||
break;
|
||||
case AddUserResponse::UserValuesUpdated: {
|
||||
log("User {} already exists, and values updated!",
|
||||
userData.username);
|
||||
qDebug() << "User" << userData.username
|
||||
<< "already exists, and values updated!";
|
||||
if (userData.username == this->getCurrent()->getUserName())
|
||||
{
|
||||
log("It was the current user, so we need to reconnect "
|
||||
"stuff!");
|
||||
qDebug() << "It was the current user, so we need to "
|
||||
"reconnect stuff!";
|
||||
this->currentUserChanged.invoke();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AddUserResponse::UserAdded: {
|
||||
log("Added user {}", userData.username);
|
||||
qDebug() << "Added user" << userData.username;
|
||||
listUpdated = true;
|
||||
}
|
||||
break;
|
||||
|
@ -140,15 +139,12 @@ void TwitchAccountManager::load()
|
|||
auto user = this->findUserByUsername(newUsername);
|
||||
if (user)
|
||||
{
|
||||
log("[AccountManager:currentUsernameChanged] User successfully "
|
||||
"updated to {}",
|
||||
newUsername);
|
||||
qDebug() << "Twitch user updated to" << newUsername;
|
||||
this->currentUser_ = user;
|
||||
}
|
||||
else
|
||||
{
|
||||
log("[AccountManager:currentUsernameChanged] User successfully "
|
||||
"updated to anonymous");
|
||||
qDebug() << "Twitch user updated to anonymous";
|
||||
this->currentUser_ = this->anonymousUser_;
|
||||
}
|
||||
|
||||
|
@ -170,11 +166,13 @@ bool TwitchAccountManager::isLoggedIn() const
|
|||
|
||||
bool TwitchAccountManager::removeUser(TwitchAccount *account)
|
||||
{
|
||||
static const QString accountFormat("/accounts/uid%1");
|
||||
|
||||
auto userID(account->getUserId());
|
||||
if (!userID.isEmpty())
|
||||
{
|
||||
pajlada::Settings::SettingManager::removeSetting(
|
||||
fS("/accounts/uid{}", userID));
|
||||
accountFormat.arg(userID).toStdString());
|
||||
}
|
||||
|
||||
if (account->getUserName() == this->currentUsername)
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include "common/Common.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
|
||||
#include <QString>
|
||||
|
@ -23,22 +22,23 @@ void TwitchApi::findUserId(const QString user,
|
|||
auto root = result.parseJson();
|
||||
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("");
|
||||
return Failure;
|
||||
}
|
||||
auto users = root.value("users").toArray();
|
||||
if (users.size() != 1)
|
||||
{
|
||||
log("API Error while getting user id, users array size is not "
|
||||
"1");
|
||||
qDebug() << "API Error while getting user id, users array size "
|
||||
"is not 1";
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
if (!users[0].isObject())
|
||||
{
|
||||
log("API Error while getting user id, first user is not an "
|
||||
"object");
|
||||
qDebug() << "API Error while getting user id, first user is "
|
||||
"not an object";
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
|
@ -46,10 +46,8 @@ void TwitchApi::findUserId(const QString user,
|
|||
auto id = firstUser.value("_id");
|
||||
if (!id.isString())
|
||||
{
|
||||
log("API Error: while getting user id, first user object `_id` "
|
||||
"key "
|
||||
"is not a "
|
||||
"string");
|
||||
qDebug() << "API Error: while getting user id, first user "
|
||||
"object `_id` key is not a string";
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
|
@ -73,8 +71,8 @@ void TwitchApi::findUserName(const QString userid,
|
|||
auto name = root.value("name");
|
||||
if (!name.isString())
|
||||
{
|
||||
log("API Error: while getting user name, `name` is not a "
|
||||
"string");
|
||||
qDebug() << "API Error: while getting user name, `name` is not "
|
||||
"a string";
|
||||
successCallback("");
|
||||
return Failure;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include "common/NetworkRequest.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "controllers/notifications/NotificationController.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "providers/bttv/BttvEmotes.hpp"
|
||||
#include "providers/bttv/LoadBttvChannelEmote.hpp"
|
||||
|
@ -92,7 +91,7 @@ TwitchChannel::TwitchChannel(const QString &name,
|
|||
, mod_(false)
|
||||
, titleRefreshedTime_(QTime::currentTime().addSecs(-TITLE_REFRESH_PERIOD))
|
||||
{
|
||||
log("[TwitchChannel:{}] Opened", name);
|
||||
qDebug() << "[TwitchChannel" << name << "] Opened";
|
||||
|
||||
this->liveStatusChanged.connect([this]() {
|
||||
if (this->isLive() == 1)
|
||||
|
@ -194,7 +193,8 @@ void TwitchChannel::sendMessage(const QString &message)
|
|||
return;
|
||||
}
|
||||
|
||||
log("[TwitchChannel:{}] Send message: {}", this->getName(), message);
|
||||
qDebug() << "[TwitchChannel" << this->getName()
|
||||
<< "] Send message:" << message;
|
||||
|
||||
// Do last message processing
|
||||
QString parsedMessage = app->emotes->emojis.replaceShortCodes(message);
|
||||
|
@ -491,8 +491,8 @@ void TwitchChannel::refreshLiveStatus()
|
|||
|
||||
if (roomID.isEmpty())
|
||||
{
|
||||
log("[TwitchChannel:{}] Refreshing live status (Missing ID)",
|
||||
this->getName());
|
||||
qDebug() << "[TwitchChannel" << this->getName()
|
||||
<< "] Refreshing live status (Missing ID)";
|
||||
this->setLive(false);
|
||||
return;
|
||||
}
|
||||
|
@ -517,13 +517,13 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
|||
{
|
||||
if (!document.IsObject())
|
||||
{
|
||||
log("[TwitchChannel:refreshLiveStatus] root is not an object");
|
||||
qDebug() << "[TwitchChannel:refreshLiveStatus] root is not an object";
|
||||
return Failure;
|
||||
}
|
||||
|
||||
if (!document.HasMember("stream"))
|
||||
{
|
||||
log("[TwitchChannel:refreshLiveStatus] Missing stream in root");
|
||||
qDebug() << "[TwitchChannel:refreshLiveStatus] Missing stream in root";
|
||||
return Failure;
|
||||
}
|
||||
|
||||
|
@ -539,7 +539,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
|||
if (!stream.HasMember("viewers") || !stream.HasMember("game") ||
|
||||
!stream.HasMember("channel") || !stream.HasMember("created_at"))
|
||||
{
|
||||
log("[TwitchChannel:refreshLiveStatus] Missing members in stream");
|
||||
qDebug()
|
||||
<< "[TwitchChannel:refreshLiveStatus] Missing members in stream";
|
||||
this->setLive(false);
|
||||
return Failure;
|
||||
}
|
||||
|
@ -548,8 +549,8 @@ Outcome TwitchChannel::parseLiveStatus(const rapidjson::Document &document)
|
|||
|
||||
if (!streamChannel.IsObject() || !streamChannel.HasMember("status"))
|
||||
{
|
||||
log("[TwitchChannel:refreshLiveStatus] Missing member \"status\" in "
|
||||
"channel");
|
||||
qDebug() << "[TwitchChannel:refreshLiveStatus] Missing member "
|
||||
"\"status\" in channel";
|
||||
return Failure;
|
||||
}
|
||||
|
||||
|
@ -743,6 +744,12 @@ void TwitchChannel::refreshCheerEmotes()
|
|||
NetworkRequest::twitchRequest(url)
|
||||
.onSuccess([this,
|
||||
weak = weakOf<Channel>(this)](auto result) -> Outcome {
|
||||
auto shared = weak.lock();
|
||||
if (!shared)
|
||||
{
|
||||
return Failure;
|
||||
}
|
||||
|
||||
auto cheerEmoteSets = ParseCheermoteSets(result.parseRapidJson());
|
||||
std::vector<CheerEmoteSet> emoteSets;
|
||||
|
||||
|
@ -836,7 +843,7 @@ boost::optional<CheerEmote> TwitchChannel::cheerEmote(const QString &string)
|
|||
int bitAmount = amount.toInt(&ok);
|
||||
if (!ok)
|
||||
{
|
||||
log("Error parsing bit amount in cheerEmote");
|
||||
qDebug() << "Error parsing bit amount in cheerEmote";
|
||||
}
|
||||
for (const auto &emote : set.cheerEmotes)
|
||||
{
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "debug/Benchmark.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
#include "messages/Image.hpp"
|
||||
#include "util/RapidjsonHelpers.hpp"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "providers/twitch/TwitchHelpers.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
|
@ -7,7 +6,7 @@ bool trimChannelName(const QString &channelName, QString &outChannelName)
|
|||
{
|
||||
if (channelName.length() < 2)
|
||||
{
|
||||
log("channel name length below 2");
|
||||
qDebug() << "channel name length below 2";
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include "controllers/highlights/HighlightController.hpp"
|
||||
#include "controllers/ignores/IgnoreController.hpp"
|
||||
#include "controllers/pings/PingController.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "providers/chatterino/ChatterinoBadges.hpp"
|
||||
#include "providers/twitch/TwitchBadges.hpp"
|
||||
|
@ -65,6 +64,25 @@ QColor getRandomColor(const QVariant &userId)
|
|||
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 chatterino {
|
||||
|
@ -90,8 +108,6 @@ namespace {
|
|||
QStringList parts = badgeInfo.split('/');
|
||||
if (parts.size() != 2)
|
||||
{
|
||||
log("Skipping badge-info because it split weird: {}",
|
||||
badgeInfo);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -110,7 +126,6 @@ namespace {
|
|||
QStringList parts = badge.split('/');
|
||||
if (parts.size() != 2)
|
||||
{
|
||||
log("Skipping badge because it split weird: {}", badge);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -159,8 +174,8 @@ bool TwitchMessageBuilder::isIgnored() const
|
|||
{
|
||||
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_))
|
||||
{
|
||||
log("Blocking message because it contains ignored phrase {}",
|
||||
phrase.getPattern());
|
||||
qDebug() << "Blocking message because it contains ignored phrase"
|
||||
<< phrase.getPattern();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -190,8 +205,8 @@ bool TwitchMessageBuilder::isIgnored() const
|
|||
case ShowIgnoredUsersMessages::Never:
|
||||
break;
|
||||
}
|
||||
log("Blocking message because it's from blocked user {}",
|
||||
user.name);
|
||||
qDebug() << "Blocking message because it's from blocked user"
|
||||
<< user.name;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -237,17 +252,11 @@ void TwitchMessageBuilder::triggerHighlights()
|
|||
if (auto player = getPlayer())
|
||||
{
|
||||
// update the media player url if necessary
|
||||
QUrl highlightSoundUrl =
|
||||
getSettings()->customHighlightSound
|
||||
? QUrl::fromLocalFile(
|
||||
getSettings()->pathHighlightSound.getValue())
|
||||
: QUrl("qrc:/sounds/ping2.wav");
|
||||
|
||||
if (currentPlayerUrl != highlightSoundUrl)
|
||||
if (currentPlayerUrl != this->highlightSoundUrl_)
|
||||
{
|
||||
player->setMedia(highlightSoundUrl);
|
||||
player->setMedia(this->highlightSoundUrl_);
|
||||
|
||||
currentPlayerUrl = highlightSoundUrl;
|
||||
currentPlayerUrl = this->highlightSoundUrl_;
|
||||
}
|
||||
|
||||
player->play();
|
||||
|
@ -427,8 +436,8 @@ void TwitchMessageBuilder::addWords(
|
|||
auto emoteImage = std::get<1>(*currentTwitchEmote);
|
||||
if (emoteImage == nullptr)
|
||||
{
|
||||
log("emoteImage nullptr {}",
|
||||
std::get<2>(*currentTwitchEmote).string);
|
||||
qDebug() << "emoteImage nullptr"
|
||||
<< std::get<2>(*currentTwitchEmote).string;
|
||||
}
|
||||
this->emplace<EmoteElement>(emoteImage,
|
||||
MessageElementFlag::TwitchEmote);
|
||||
|
@ -771,7 +780,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
|
|||
{
|
||||
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(
|
||||
|
@ -809,7 +818,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
|
|||
{
|
||||
if (emote.second == nullptr)
|
||||
{
|
||||
log("emote null {}", emote.first.string);
|
||||
qDebug() << "emote null" << emote.first.string;
|
||||
}
|
||||
twitchEmotes.push_back(std::tuple<int, EmotePtr, EmoteName>{
|
||||
startIndex + pos, emote.second, emote.first});
|
||||
|
@ -872,7 +881,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
|
|||
{
|
||||
if (std::get<1>(tup) == nullptr)
|
||||
{
|
||||
log("v nullptr {}", std::get<2>(tup).string);
|
||||
qDebug() << "v nullptr" << std::get<2>(tup).string;
|
||||
continue;
|
||||
}
|
||||
QRegularExpression emoteregex(
|
||||
|
@ -941,7 +950,7 @@ void TwitchMessageBuilder::runIgnoreReplaces(
|
|||
{
|
||||
if (std::get<1>(tup) == nullptr)
|
||||
{
|
||||
log("v nullptr {}", std::get<2>(tup).string);
|
||||
qDebug() << "v nullptr" << std::get<2>(tup).string;
|
||||
continue;
|
||||
}
|
||||
QRegularExpression emoteregex(
|
||||
|
@ -971,6 +980,39 @@ void TwitchMessageBuilder::parseHighlights()
|
|||
{
|
||||
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();
|
||||
|
||||
QString currentUsername = currentUser->getUserName();
|
||||
|
@ -981,6 +1023,39 @@ void TwitchMessageBuilder::parseHighlights()
|
|||
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 =
|
||||
app->highlights->highlightedUsers.cloneVector();
|
||||
|
||||
|
@ -991,29 +1066,39 @@ void TwitchMessageBuilder::parseHighlights()
|
|||
{
|
||||
continue;
|
||||
}
|
||||
log("Highlight because user {} sent a message",
|
||||
this->ircMessage->nick());
|
||||
if (!this->highlightVisual_)
|
||||
{
|
||||
this->highlightVisual_ = true;
|
||||
this->message().flags.set(MessageFlag::Highlighted);
|
||||
}
|
||||
qDebug() << "Highlight because user" << this->ircMessage->nick()
|
||||
<< "sent a message";
|
||||
|
||||
if (userHighlight.getAlert())
|
||||
this->message().flags.set(MessageFlag::Highlighted);
|
||||
this->message().highlightColor = userHighlight.getColor();
|
||||
|
||||
if (userHighlight.hasAlert())
|
||||
{
|
||||
this->highlightAlert_ = true;
|
||||
}
|
||||
|
||||
if (userHighlight.getSound())
|
||||
if (userHighlight.hasSound())
|
||||
{
|
||||
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_)
|
||||
{
|
||||
// Break if no further action can be taken from other
|
||||
// usernames Mostly used for regex stuff
|
||||
break;
|
||||
/*
|
||||
* User name highlights "beat" highlight phrases: If a message has
|
||||
* all attributes (color, taskbar flashing, sound) set, highlight
|
||||
* phrases will not be checked.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1032,7 +1117,9 @@ void TwitchMessageBuilder::parseHighlights()
|
|||
{
|
||||
HighlightPhrase selfHighlight(
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -1044,45 +1131,43 @@ void TwitchMessageBuilder::parseHighlights()
|
|||
continue;
|
||||
}
|
||||
|
||||
log("Highlight because {} matches {}", this->originalMessage_,
|
||||
highlight.getPattern());
|
||||
if (!this->highlightVisual_)
|
||||
{
|
||||
this->highlightVisual_ = true;
|
||||
this->message().flags.set(MessageFlag::Highlighted);
|
||||
}
|
||||
qDebug() << "Highlight because" << this->originalMessage_ << "matches"
|
||||
<< highlight.getPattern();
|
||||
|
||||
if (highlight.getAlert())
|
||||
this->message().flags.set(MessageFlag::Highlighted);
|
||||
this->message().highlightColor = highlight.getColor();
|
||||
|
||||
if (highlight.hasAlert())
|
||||
{
|
||||
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;
|
||||
|
||||
// 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_)
|
||||
{
|
||||
// Break if no further action can be taken from other
|
||||
// highlights This might change if highlights can have
|
||||
// custom colors/sounds/actions
|
||||
/*
|
||||
* Break once no further attributes (taskbar, sound) can be
|
||||
* applied.
|
||||
*/
|
||||
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(
|
||||
|
@ -1130,7 +1215,7 @@ void TwitchMessageBuilder::appendTwitchEmote(
|
|||
start, app->emotes->twitch.getOrCreateEmote(id, name), name};
|
||||
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));
|
||||
}
|
||||
|
@ -1216,7 +1301,7 @@ void TwitchMessageBuilder::appendTwitchBadges()
|
|||
auto badgeEmote = this->getTwitchBadge(badge);
|
||||
if (!badgeEmote)
|
||||
{
|
||||
log("No channel/global variant found {}", badge.key_);
|
||||
qDebug() << "No channel/global variant found" << badge.key_;
|
||||
continue;
|
||||
}
|
||||
auto tooltip = (*badgeEmote)->tooltip.string;
|
||||
|
|
|
@ -92,9 +92,10 @@ private:
|
|||
|
||||
const bool action_ = false;
|
||||
|
||||
bool highlightVisual_ = false;
|
||||
bool highlightAlert_ = false;
|
||||
bool highlightSound_ = false;
|
||||
|
||||
QUrl highlightSoundUrl_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "singletons/Logging.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "singletons/Settings.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "singletons/Resources.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
|
|
|
@ -146,18 +146,39 @@ public:
|
|||
/// Highlighting
|
||||
// BoolSetting enableHighlights = {"/highlighting/enabled", true};
|
||||
BoolSetting customHighlightSound = {"/highlighting/useCustomSound", false};
|
||||
|
||||
BoolSetting enableSelfHighlight = {
|
||||
"/highlighting/selfHighlight/nameIsHighlightKeyword", true};
|
||||
BoolSetting enableSelfHighlightSound = {
|
||||
"/highlighting/selfHighlight/enableSound", true};
|
||||
BoolSetting enableSelfHighlightTaskbar = {
|
||||
"/highlighting/selfHighlight/enableTaskbarFlashing", true};
|
||||
QStringSetting selfHighlightSoundUrl = {
|
||||
"/highlighting/selfHighlightSoundUrl", ""};
|
||||
QStringSetting selfHighlightColor = {"/highlighting/selfHighlightColor",
|
||||
""};
|
||||
|
||||
BoolSetting enableWhisperHighlight = {
|
||||
"/highlighting/whisperHighlight/whispersHighlighted", true};
|
||||
BoolSetting enableWhisperHighlightSound = {
|
||||
"/highlighting/whisperHighlight/enableSound", false};
|
||||
BoolSetting enableWhisperHighlightTaskbar = {
|
||||
"/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", ""};
|
||||
|
||||
BoolSetting longAlerts = {"/highlighting/alerts", false};
|
||||
|
@ -168,7 +189,7 @@ public:
|
|||
QStringSetting logPath = {"/logging/path", ""};
|
||||
|
||||
QStringSetting pathHighlightSound = {"/highlighting/highlightSoundPath",
|
||||
"qrc:/sounds/ping2.wav"};
|
||||
""};
|
||||
|
||||
BoolSetting highlightAlwaysPlaySound = {"/highlighting/alwaysPlaySound",
|
||||
false};
|
||||
|
@ -228,6 +249,20 @@ public:
|
|||
IntSetting lastSelectChannelTab = {"/ui/lastSelectChannelTab", 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:
|
||||
void updateModerationActions();
|
||||
};
|
||||
|
|
|
@ -38,9 +38,6 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
|||
|
||||
this->splits.resizeHandle = QColor(0, 148, 255, 0xff);
|
||||
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x50);
|
||||
|
||||
// Highlighted Messages: theme support quick-fix
|
||||
this->messages.backgrounds.highlighted = QColor("#BD8489");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -49,11 +46,10 @@ void Theme::actuallyUpdate(double hue, double multiplier)
|
|||
|
||||
this->splits.resizeHandle = QColor(0, 148, 255, 0x70);
|
||||
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.border = getColor(0, sat, flat ? 1 : 0.85);
|
||||
this->splits.header.text = this->messages.textColors.regular;
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
#include "Application.hpp"
|
||||
#include "debug/AssertInGuiThread.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/MessageElement.hpp"
|
||||
#include "providers/irc/Irc2.hpp"
|
||||
#include "providers/irc/IrcChannel2.hpp"
|
||||
|
@ -264,7 +263,7 @@ Window *WindowManager::windowAt(int index)
|
|||
{
|
||||
return nullptr;
|
||||
}
|
||||
log("getting window at bad index {}", index);
|
||||
qDebug() << "getting window at bad index" << index;
|
||||
|
||||
return this->windows_.at(index);
|
||||
}
|
||||
|
@ -432,7 +431,7 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
|
|||
|
||||
void WindowManager::save()
|
||||
{
|
||||
log("[WindowManager] Saving");
|
||||
qDebug() << "[WindowManager] Saving";
|
||||
assertInGuiThread();
|
||||
QJsonDocument document;
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "LoggingChannel.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "singletons/Paths.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
|
@ -64,13 +63,13 @@ void LoggingChannel::openLogFile()
|
|||
|
||||
if (!QDir().mkpath(directory))
|
||||
{
|
||||
log("Unable to create logging path");
|
||||
qDebug() << "Unable to create logging path";
|
||||
return;
|
||||
}
|
||||
|
||||
// Open file handle to log file of current date
|
||||
QString fileName = directory + QDir::separator() + baseFileName;
|
||||
log("Logging to {}", fileName);
|
||||
qDebug() << "Logging to" << fileName;
|
||||
this->fileHandle.setFileName(fileName);
|
||||
|
||||
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
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
template <typename... Args>
|
||||
auto fS(Args &&... args)
|
||||
{
|
||||
return fmt::format(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
QString generateUuid();
|
||||
|
||||
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);
|
||||
|
||||
} // 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 <QVariant>
|
||||
|
||||
#include "debug/Log.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -63,7 +61,7 @@ namespace {
|
|||
if (command.isNull())
|
||||
return QString();
|
||||
|
||||
log(command);
|
||||
qDebug() << command;
|
||||
|
||||
// inject switch to enable private browsing
|
||||
command = injectPrivateSwitch(command);
|
||||
|
|
|
@ -22,6 +22,19 @@ static void setStringItem(QStandardItem *item, const QString &value,
|
|||
(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()
|
||||
{
|
||||
auto *item = new QStandardItem();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "util/StreamLink.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
#include "widgets/dialogs/QualityPopup.hpp"
|
||||
|
@ -92,7 +91,7 @@ namespace {
|
|||
}
|
||||
else
|
||||
{
|
||||
log("Error occured {}", err);
|
||||
qDebug() << "Error occured" << err;
|
||||
}
|
||||
|
||||
p->deleteLater();
|
||||
|
@ -119,7 +118,7 @@ void getStreamQualities(const QString &channelURL,
|
|||
[=](int res) {
|
||||
if (res != 0)
|
||||
{
|
||||
log("Got error code {}", res);
|
||||
qDebug() << "Got error code" << res;
|
||||
// return;
|
||||
}
|
||||
QString lastLine = QString(p->readAllStandardOutput());
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "widgets/AccountSwitchPopup.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "widgets/dialogs/SettingsDialog.hpp"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include "BaseSettings.hpp"
|
||||
#include "BaseTheme.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "widgets/BaseWindow.hpp"
|
||||
|
||||
#include <QChildEvent>
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
#include "BaseSettings.hpp"
|
||||
#include "BaseTheme.hpp"
|
||||
#include "boost/algorithm/algorithm.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "util/PostToThread.hpp"
|
||||
#include "util/Shortcut.hpp"
|
||||
#include "util/WindowsHelper.hpp"
|
||||
|
@ -385,7 +384,7 @@ void BaseWindow::mousePressEvent(QMouseEvent *event)
|
|||
|
||||
if (!recursiveCheckMouseTracking(widget))
|
||||
{
|
||||
log("Start moving");
|
||||
qDebug() << "Start moving";
|
||||
this->moving = true;
|
||||
}
|
||||
}
|
||||
|
@ -402,7 +401,7 @@ void BaseWindow::mouseReleaseEvent(QMouseEvent *event)
|
|||
{
|
||||
if (this->moving)
|
||||
{
|
||||
log("Stop moving");
|
||||
qDebug() << "Stop moving";
|
||||
this->moving = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
#include "widgets/Notebook.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
|
|
|
@ -282,18 +282,7 @@ void Scrollbar::paintEvent(QPaintEvent *)
|
|||
|
||||
if (!highlight.isNull())
|
||||
{
|
||||
QColor color = [&] {
|
||||
switch (highlight.getColor())
|
||||
{
|
||||
case ScrollbarHighlight::Highlight:
|
||||
return getApp()
|
||||
->themes->scrollbars.highlights.highlight;
|
||||
case ScrollbarHighlight::Subscription:
|
||||
return getApp()
|
||||
->themes->scrollbars.highlights.subscription;
|
||||
}
|
||||
return QColor();
|
||||
}();
|
||||
QColor color = highlight.getColor();
|
||||
|
||||
switch (highlight.getStyle())
|
||||
{
|
||||
|
|
|
@ -351,6 +351,11 @@ void Window::addShortcuts()
|
|||
getApp()->twitch.server->getOrAddChannel(si.channelName));
|
||||
splitContainer->appendSplit(split);
|
||||
});
|
||||
|
||||
createWindowShortcut(this, "CTRL+H", [this] {
|
||||
getSettings()->hideSimilar.setValue(!getSettings()->hideSimilar);
|
||||
getApp()->windows->forceLayoutChannelViews();
|
||||
});
|
||||
}
|
||||
|
||||
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 "providers/twitch/TwitchChannel.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "util/Shortcut.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
|
||||
|
@ -31,7 +32,14 @@ namespace {
|
|||
|
||||
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
|
||||
.emplace<EmoteElement>(emote.second,
|
||||
|
@ -131,6 +139,10 @@ EmotePopup::EmotePopup(QWidget *parent)
|
|||
this->viewEmojis_ = makeView("Emojis");
|
||||
|
||||
this->loadEmojis();
|
||||
|
||||
createWindowShortcut(this, "CTRL+Tab", [=] { notebook->selectNextTab(); });
|
||||
createWindowShortcut(this, "CTRL+Shift+Tab",
|
||||
[=] { notebook->selectPreviousTab(); });
|
||||
}
|
||||
|
||||
void EmotePopup::loadChannel(ChannelPtr _channel)
|
||||
|
|
|
@ -65,7 +65,7 @@ namespace {
|
|||
// messageBox.setIcon(QMessageBox::Information);
|
||||
// messageBox.setText("Successfully logged in with user <b>" +
|
||||
// qS(username) + "</b>!");
|
||||
auto basePath = fS("/accounts/uid{}", userID);
|
||||
std::string basePath = "/accounts/uid" + userID.toStdString();
|
||||
pajlada::Settings::Setting<QString>::set(basePath + "/username",
|
||||
username);
|
||||
pajlada::Settings::Setting<QString>::set(basePath + "/userID", userID);
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
#include "IrcMessage"
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "providers/twitch/PartialTwitchUser.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
|
@ -55,7 +54,7 @@ void LogsPopup::getLogs()
|
|||
dynamic_cast<TwitchChannel *>(this->channel_.get()))
|
||||
{
|
||||
this->channelName_ = twitchChannel->getName();
|
||||
this->getLogviewerLogs(twitchChannel->roomId());
|
||||
this->getOverrustleLogs();
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -63,14 +62,11 @@ void LogsPopup::getLogs()
|
|||
|
||||
if (!this->channelName_.isEmpty())
|
||||
{
|
||||
PartialTwitchUser::byName(this->channelName_)
|
||||
.getId(
|
||||
[=](const QString &roomID) { this->getLogviewerLogs(roomID); },
|
||||
this);
|
||||
this->getOverrustleLogs();
|
||||
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)
|
||||
|
@ -81,57 +77,9 @@ void LogsPopup::setMessages(std::vector<MessagePtr> &messages)
|
|||
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()
|
||||
{
|
||||
QString url =
|
||||
auto url =
|
||||
QString("https://overrustlelogs.net/api/v1/stalk/%1/%2.json?limit=500")
|
||||
.arg(this->channelName_, this->userName_);
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ private:
|
|||
|
||||
void setMessages(std::vector<MessagePtr> &messages);
|
||||
void getOverrustleLogs();
|
||||
void getLogviewerLogs(const QString &roomID);
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "QualityPopup.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "util/StreamLink.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
@ -50,7 +49,7 @@ void QualityPopup::okButtonClicked()
|
|||
}
|
||||
catch (const Exception &ex)
|
||||
{
|
||||
log("Exception caught trying to open streamlink: {}", ex.what());
|
||||
qDebug() << "Exception caught trying to open streamlink:" << ex.what();
|
||||
}
|
||||
|
||||
this->close();
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#define TEXT_VIEWS "Views: "
|
||||
#define TEXT_CREATED "Created: "
|
||||
#define TEXT_USER_ID "ID: "
|
||||
#define TEXT_UNAVAILABLE "(not available)"
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
|
@ -382,6 +383,22 @@ void UserInfoPopup::updateUserData()
|
|||
{
|
||||
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) {
|
||||
auto currentUser = getApp()->accounts->twitch.getCurrent();
|
||||
|
||||
|
@ -462,7 +479,8 @@ void UserInfoPopup::updateUserData()
|
|||
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_.ignore->setEnabled(false);
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
#include "common/Common.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "debug/Benchmark.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
#include "messages/LimitedQueueSnapshot.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
|
@ -29,6 +28,7 @@
|
|||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/TooltipPreviewImage.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/Clipboard.hpp"
|
||||
#include "util/DistanceBetweenPoints.hpp"
|
||||
#include "util/IncognitoBrowser.hpp"
|
||||
#include "widgets/Scrollbar.hpp"
|
||||
|
@ -65,9 +65,8 @@ namespace {
|
|||
if (!image->isEmpty())
|
||||
{
|
||||
copyMenu->addAction(
|
||||
QString(scale) + "x link", [url = image->url()] {
|
||||
QApplication::clipboard()->setText(url.string);
|
||||
});
|
||||
QString(scale) + "x link",
|
||||
[url = image->url()] { crossPlatformCopy(url.string); });
|
||||
openMenu->addAction(
|
||||
QString(scale) + "x link", [url = image->url()] {
|
||||
QDesktopServices::openUrl(QUrl(url.string));
|
||||
|
@ -85,9 +84,8 @@ namespace {
|
|||
openMenu->addSeparator();
|
||||
|
||||
copyMenu->addAction(
|
||||
"Copy " + name + " emote link", [url = emote.homePage] {
|
||||
QApplication::clipboard()->setText(url.string); //
|
||||
});
|
||||
"Copy " + name + " emote link",
|
||||
[url = emote.homePage] { crossPlatformCopy(url.string); });
|
||||
openMenu->addAction(
|
||||
"Open " + name + " emote link", [url = emote.homePage] {
|
||||
QDesktopServices::openUrl(QUrl(url.string)); //
|
||||
|
@ -125,9 +123,8 @@ ChannelView::ChannelView(BaseWidget *parent)
|
|||
});
|
||||
|
||||
auto shortcut = new QShortcut(QKeySequence("Ctrl+C"), this);
|
||||
QObject::connect(shortcut, &QShortcut::activated, [this] {
|
||||
QGuiApplication::clipboard()->setText(this->getSelectedText());
|
||||
});
|
||||
QObject::connect(shortcut, &QShortcut::activated,
|
||||
[this] { crossPlatformCopy(this->getSelectedText()); });
|
||||
|
||||
this->clickTimer_ = new QTimer(this);
|
||||
this->clickTimer_->setSingleShot(true);
|
||||
|
@ -651,7 +648,8 @@ void ChannelView::messageAppended(MessagePtr &message,
|
|||
|
||||
if (!messageFlags->has(MessageFlag::DoNotTriggerNotification))
|
||||
{
|
||||
if (messageFlags->has(MessageFlag::Highlighted))
|
||||
if (messageFlags->has(MessageFlag::Highlighted) &&
|
||||
!messageFlags->has(MessageFlag::Subscription))
|
||||
{
|
||||
this->tabHighlightRequested.invoke(HighlightState::Highlighted);
|
||||
}
|
||||
|
@ -740,9 +738,8 @@ void ChannelView::messageReplaced(size_t index, MessagePtr &replacement)
|
|||
auto snapshot = this->messages_.getSnapshot();
|
||||
if (index >= snapshot.size())
|
||||
{
|
||||
log("Tried to replace out of bounds message. Index: {}. "
|
||||
"Length: {}",
|
||||
index, snapshot.size());
|
||||
qDebug() << "Tried to replace out of bounds message. Index:" << index
|
||||
<< ". Length:" << snapshot.size();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1554,8 +1551,7 @@ void ChannelView::addContextMenuItems(
|
|||
menu->addAction("Open link incognito",
|
||||
[url] { openLinkIncognito(url); });
|
||||
}
|
||||
menu->addAction("Copy link",
|
||||
[url] { QApplication::clipboard()->setText(url); });
|
||||
menu->addAction("Copy link", [url] { crossPlatformCopy(url); });
|
||||
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
@ -1563,9 +1559,8 @@ void ChannelView::addContextMenuItems(
|
|||
// Copy actions
|
||||
if (!this->selection_.isEmpty())
|
||||
{
|
||||
menu->addAction("Copy selection", [this] {
|
||||
QGuiApplication::clipboard()->setText(this->getSelectedText());
|
||||
});
|
||||
menu->addAction("Copy selection",
|
||||
[this] { crossPlatformCopy(this->getSelectedText()); });
|
||||
}
|
||||
|
||||
menu->addAction("Copy message", [layout] {
|
||||
|
@ -1573,14 +1568,14 @@ void ChannelView::addContextMenuItems(
|
|||
layout->addSelectionText(copyString, 0, INT_MAX,
|
||||
CopyMode::OnlyTextAndEmotes);
|
||||
|
||||
QGuiApplication::clipboard()->setText(copyString);
|
||||
crossPlatformCopy(copyString);
|
||||
});
|
||||
|
||||
menu->addAction("Copy full message", [layout] {
|
||||
QString copyString;
|
||||
layout->addSelectionText(copyString);
|
||||
|
||||
QGuiApplication::clipboard()->setText(copyString);
|
||||
crossPlatformCopy(copyString);
|
||||
});
|
||||
|
||||
// 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 "common/Common.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "singletons/Fonts.hpp"
|
||||
#include "singletons/Settings.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 "Application.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "widgets/Scrollbar.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
ScrollbarHighlight::ScrollbarHighlight()
|
||||
: color_(Color::Highlight)
|
||||
: color_(std::make_shared<QColor>())
|
||||
, style_(Style::None)
|
||||
{
|
||||
}
|
||||
|
||||
ScrollbarHighlight::ScrollbarHighlight(Color color, Style style)
|
||||
ScrollbarHighlight::ScrollbarHighlight(const std::shared_ptr<QColor> color,
|
||||
Style style)
|
||||
: color_(color)
|
||||
, style_(style)
|
||||
{
|
||||
}
|
||||
|
||||
ScrollbarHighlight::Color ScrollbarHighlight::getColor() const
|
||||
QColor ScrollbarHighlight::getColor() const
|
||||
{
|
||||
return this->color_;
|
||||
return *this->color_;
|
||||
}
|
||||
|
||||
ScrollbarHighlight::Style ScrollbarHighlight::getStyle() const
|
||||
|
|
|
@ -6,17 +6,25 @@ class ScrollbarHighlight
|
|||
{
|
||||
public:
|
||||
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(Color color, Style style = Default);
|
||||
|
||||
Color getColor() const;
|
||||
ScrollbarHighlight(const std::shared_ptr<QColor> color,
|
||||
Style style = Default);
|
||||
|
||||
QColor getColor() const;
|
||||
Style getStyle() const;
|
||||
bool isNull() const;
|
||||
|
||||
private:
|
||||
Color color_;
|
||||
std::shared_ptr<QColor> color_;
|
||||
Style style_;
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include "common/Modes.hpp"
|
||||
#include "common/Version.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "util/RemoveScrollAreaBackground.hpp"
|
||||
#include "widgets/helper/SignalLabel.hpp"
|
||||
|
@ -102,8 +101,6 @@ AboutPage::AboutPage()
|
|||
":/licenses/qt_lgpl-3.0.txt");
|
||||
addLicense(form.getElement(), "Boost", "https://www.boost.org/",
|
||||
":/licenses/boost_boost.txt");
|
||||
addLicense(form.getElement(), "Fmt", "http://fmtlib.net/",
|
||||
":/licenses/fmt_bsd2.txt");
|
||||
addLicense(form.getElement(), "LibCommuni",
|
||||
"https://github.com/communi/libcommuni",
|
||||
":/licenses/libcommuni_BSD3.txt");
|
||||
|
@ -166,7 +163,7 @@ AboutPage::AboutPage()
|
|||
|
||||
if (contributorParts.size() != 4)
|
||||
{
|
||||
log("Missing parts in line '{}'", line);
|
||||
qDebug() << "Missing parts in line" << line;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -547,6 +547,33 @@ void GeneralPage::initLayout(SettingsLayout &layout)
|
|||
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
|
||||
auto inv = new BaseWidget(this);
|
||||
inv->setScaleIndependantWidth(500);
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
#include "controllers/highlights/HighlightController.hpp"
|
||||
#include "controllers/highlights/HighlightModel.hpp"
|
||||
#include "controllers/highlights/UserHighlightModel.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "util/StandardItemHelper.hpp"
|
||||
#include "widgets/helper/EditableModelView.hpp"
|
||||
#include "widgets/dialogs/ColorPickerDialog.hpp"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QHeaderView>
|
||||
|
@ -46,8 +46,10 @@ HighlightingPage::HighlightingPage()
|
|||
// HIGHLIGHTS
|
||||
auto highlights = tabs.appendTab(new QVBoxLayout, "Messages");
|
||||
{
|
||||
highlights.emplace<QLabel>("Messages can be highlighted if "
|
||||
"they match a certain pattern.");
|
||||
highlights.emplace<QLabel>(
|
||||
"Messages can be highlighted if they match a certain "
|
||||
"pattern.\n"
|
||||
"NOTE: User highlights will override phrase highlights.");
|
||||
|
||||
EditableModelView *view =
|
||||
highlights
|
||||
|
@ -57,7 +59,8 @@ HighlightingPage::HighlightingPage()
|
|||
|
||||
view->addRegexHelpLink();
|
||||
view->setTitles({"Pattern", "Flash\ntaskbar", "Play\nsound",
|
||||
"Enable\nregex", "Case-\nsensitive"});
|
||||
"Enable\nregex", "Case-\nsensitive",
|
||||
"Custom\nsound", "Color"});
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
QHeaderView::Fixed);
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
|
@ -72,14 +75,22 @@ HighlightingPage::HighlightingPage()
|
|||
|
||||
view->addButtonPressed.connect([] {
|
||||
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");
|
||||
{
|
||||
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 =
|
||||
pingUsers
|
||||
.emplace<EditableModelView>(
|
||||
|
@ -90,9 +101,10 @@ HighlightingPage::HighlightingPage()
|
|||
view->getTableView()->horizontalHeader()->hideSection(4);
|
||||
|
||||
// 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",
|
||||
"Enable\nregex"});
|
||||
"Enable\nregex", "Case-\nsensitive",
|
||||
"Custom\nsound", "Color"});
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
QHeaderView::Fixed);
|
||||
view->getTableView()->horizontalHeader()->setSectionResizeMode(
|
||||
|
@ -108,7 +120,14 @@ HighlightingPage::HighlightingPage()
|
|||
view->addButtonPressed.connect([] {
|
||||
getApp()->highlights->highlightedUsers.appendItem(
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -148,16 +167,32 @@ HighlightingPage::HighlightingPage()
|
|||
// MISC
|
||||
auto customSound = layout.emplace<QHBoxLayout>().withoutMargin();
|
||||
{
|
||||
customSound.append(this->createCheckBox(
|
||||
"Custom sound", getSettings()->customHighlightSound));
|
||||
auto fallbackSound = customSound.append(this->createCheckBox(
|
||||
"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 =
|
||||
customSound.emplace<QPushButton>("Select custom sound file");
|
||||
QObject::connect(selectFile.getElement(), &QPushButton::clicked,
|
||||
this, [this] {
|
||||
customSound.emplace<QPushButton>(getSelectFileText());
|
||||
|
||||
QObject::connect(
|
||||
selectFile.getElement(), &QPushButton::clicked, this,
|
||||
[=]() mutable {
|
||||
auto fileName = QFileDialog::getOpenFileName(
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -172,4 +207,71 @@ HighlightingPage::HighlightingPage()
|
|||
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
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "widgets/helper/EditableModelView.hpp"
|
||||
#include "widgets/settingspages/SettingsPage.hpp"
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
@ -17,6 +18,8 @@ public:
|
|||
|
||||
private:
|
||||
QTimer disabledUsersChangedTimer_;
|
||||
|
||||
void tableCellClicked(const QModelIndex &clicked, EditableModelView *view);
|
||||
};
|
||||
|
||||
} // 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 + W"),
|
||||
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->addRow(new QLabel("Ctrl + 1/2/3/..."),
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
#include "common/Env.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "providers/twitch/EmoteValue.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
|
@ -13,6 +12,7 @@
|
|||
#include "singletons/Theme.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "util/NuulsUploader.hpp"
|
||||
#include "util/Clipboard.hpp"
|
||||
#include "util/Shortcut.hpp"
|
||||
#include "util/StreamLink.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
|
@ -537,7 +537,7 @@ void Split::openInStreamlink()
|
|||
}
|
||||
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()
|
||||
{
|
||||
QApplication::clipboard()->setText(this->view_->getSelectedText());
|
||||
crossPlatformCopy(this->view_->getSelectedText());
|
||||
}
|
||||
|
||||
void Split::showSearch()
|
||||
|
|
|
@ -577,10 +577,15 @@ void SplitHeader::updateModerationModeIcon()
|
|||
auto channel = this->split_->getChannel();
|
||||
auto twitchChannel = dynamic_cast<TwitchChannel *>(channel.get());
|
||||
|
||||
if (twitchChannel != nullptr && twitchChannel->hasModRights())
|
||||
if (twitchChannel != nullptr &&
|
||||
(twitchChannel->hasModRights() || moderationMode))
|
||||
{
|
||||
this->moderationButton_->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->moderationButton_->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void SplitHeader::paintEvent(QPaintEvent *)
|
||||
|
|
Loading…
Reference in a new issue