Merge branch 'master' into logging

This commit is contained in:
pajlada 2018-06-05 13:16:20 +02:00 committed by GitHub
commit 4ad0ed4d45
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
172 changed files with 5997 additions and 3348 deletions

13
BUILDING_ON_LINUX.md Normal file
View file

@ -0,0 +1,13 @@
# Linux
## Ubuntu 18.04
*most likely works the same for other Debian-like distros*
1. Install dependencies (and the C++ IDE Qt Creator) `sudo apt install qtcreator qtmultimedia5-dev libqt5svg5-dev libboost-dev libssl-dev libboost-system-dev`
2. Open `chatterino.pro` with QT Creator and build
## Arch Linux
install [chatterino2-git](https://aur.archlinux.org/packages/chatterino2-git/) from the aur or build manually as follows:
1. `sudo pacman -S qt5-base qt5-multimedia qt5-svg gst-plugins-ugly gst-plugins-good boost rapidjson`
2. go into project directory
3. create build folder `mkdir build && cd build`
4. `qmake .. && make`

22
BUILDING_ON_MAC.md Normal file
View file

@ -0,0 +1,22 @@
# Building on Mac OSX
1. install Xcode and Xcode Command Line Utilites
2. start Xcode, settings -> Locations, activate your Command Line Tools
3. install Qt Creator
4. install brew https://brew.sh/
5. `brew install boost openssl rapidjson`
6. build the project using Qt Creator
If the Project does not build at this point, you might need to add additional Paths/Libs, because brew does not install openssl and boost in the common path. You can get their path using
`brew info openssl`
`brew info boost`
The lines which you need to add to your project file should look similar to this
```
INCLUDEPATH += /usr/local/opt/openssl/include
LIBS += -L/usr/local/opt/openssl/lib
INCLUDEPATH += "/usr/local/Cellar/boost/1.67.0_1/include"
LIBS += -L"/usr/local/Cellar/boost/1.67.0_1/lib"
```

47
BUILDING_ON_WINDOWS.md Normal file
View file

@ -0,0 +1,47 @@
# Building on Windows (Recommended)
## Using Qt Creator
### Visual Studio 2017
1. Install Visual Studio 2017 and select "Desktop development with C++" and "Universal Windows Platform development.
### Boost
1. Visual Studio 2017 64-bit: https://dl.bintray.com/boostorg/release/1.66.0/binaries/boost_1_66_0-msvc-14.1-64.exe
2. When prompted, install boost to C:\local\boost
3. When the installation is finished, go to C:\local\boost and rename the "lib64-msvc-14.1" folder to "lib"
### OpenSSL
#### For our websocket library, we need OpenSSL 1.1
1. Download OpenSSL development library: https://slproweb.com/download/Win64OpenSSL-1_1_0h.exe
2. When prompted, install openssl to C:\local\openssl
3. When prompted, copy the OpenSSL DLLs to "The OpenSSL binaries (/bin) directory"
#### For Qt SSL, we need OpenSSL 1.0
1. Download OpenSSL light: https://slproweb.com/download/Win64OpenSSL_Light-1_0_2o.exe
2. When prompted, install it anywhere
3. When prompted, copy the OpenSSL DLLS to "The OpenSSL binaries (/bin) directory"
4. Copy the OpenSSL 1.0 files from its /bin folder to C:/local/bin (You will need to create the folder)
5. Then copy the OpenSSL 1.1 files from its /bin folder to C:/local/bin (Overwrite any duplicate files)
6. Add C:/local/bin to your path folder (Follow guide here if you don't know how to do it: https://www.computerhope.com/issues/ch000549.htm#windows8 )
### Qt
1. Download Qt: https://www.qt.io/download
2. Select "Open source" at the bottom of this page
3. Then select "Download"
#### When prompted which components to install:
1. Under the latest Qt version:
- Select MSVC 2017 64-bit (or MSVC 2015 64-bit if you still use Visual Studio 2015)
- Optionally, enable Qt WebEngine
2. Under Tools:
- Select Qt Creator, and Qt Creator CDB Debugger Support
# Windows (Using MSYS2, not recommended)
Note: This guide is currently out of date and will not work as is.
Note: This build will have some features missing from the build.
Building using MSYS2 can be quite easier process. Check out MSYS2 at [msys2.org](http://www.msys2.org/).
Be sure to add `-j <number of threads>` as a make argument so it will use all your cpu cores to build. [example setup](https://i.imgur.com/qlESlS1.png)
You can also add `-o2` to optimize the final binary size but increase compilation time, and add `-pipe` to use more ram in compilation but increase compilation speed
1. open appropriate MSYS2 terminal and do `pacman -S mingw-w64-<arch>-boost mingw-w64-<arch>-qt5 mingw-w64-<arch>-rapidjson` where `<arch>` is `x86_64` or `i686`
2. go into the project directory
3. create build folder `mkdir build && cd build`
4. `qmake .. && mingw32-make`

110
README.md
View file

@ -4,107 +4,27 @@ Chatterino 2
Chatterino 2 is the second installment of the Twitch chat client series "Chatterino". For now you can check out Chatterino 1 at [https://chatterino.com](https://chatterino.com). Chatterino 2 is the second installment of the Twitch chat client series "Chatterino". For now you can check out Chatterino 1 at [https://chatterino.com](https://chatterino.com).
## Code style
The code is normally formated using clang format in Qt Creator. [.clang-format](https://github.com/fourtf/chatterino2/blob/master/.clang-format) contains the style file for clang format.
To setup automatic code formating with QT Creator, see [this guide](https://gist.github.com/pajlada/0296454198eb8f8789fd6fe7ea660c5b).
## Building ## Building
Before building run `git submodule update --init --recursive` to get required submodules. Before building run `git submodule update --init --recursive` to get required submodules.
### Windows [Building on Windows](../master/BUILDING_ON_WINDOWS.md)
#### Using Qt Creator
##### Visual Studio 2017
1. Install Visual Studio 2017 and select "Desktop development with C++" and "Universal Windows Platform development.
##### Boost [Building on Linux](../master/BUILDING_ON_LINUX.md)
1. Visual Studio 2017 64-bit: https://dl.bintray.com/boostorg/release/1.66.0/binaries/boost_1_66_0-msvc-14.1-64.exe
2. When prompted, install boost to C:\local\boost
3. When the installation is finished, go to C:\local\boost and rename the "lib64-msvc-14.1" folder to "lib"
##### OpenSSL [Building on Mac](../master/BUILDING_ON_MAC.md)
###### For our websocket library, we need OpenSSL 1.1
1. Download OpenSSL development library: https://slproweb.com/download/Win64OpenSSL-1_1_0h.exe
2. When prompted, install openssl to C:\local\openssl
3. When prompted, copy the OpenSSL DLLs to "The OpenSSL binaries (/bin) directory"
###### For Qt SSL, we need OpenSSL 1.0
1. Download OpenSSL light: https://slproweb.com/download/Win64OpenSSL_Light-1_0_2o.exe
2. When prompted, install it anywhere
3. When prompted, copy the OpenSSL DLLS to "The OpenSSL binaries (/bin) directory"
4. Copy the OpenSSL 1.0 files from its /bin folder to C:/local/bin (You will need to create the folder)
5. Then copy the OpenSSL 1.1 files from its /bin folder to C:/local/bin (Overwrite any duplicate files)
6. Add C:/local/bin to your path folder (Follow guide here if you don't know how to do it: https://www.computerhope.com/issues/ch000549.htm#windows8 )
##### Qt ## Code style
1. Download Qt: https://www.qt.io/download The code is formated using clang format in Qt Creator. [.clang-format](https://github.com/fourtf/chatterino2/blob/master/.clang-format) contains the style file for clang format.
2. Select "Open source" at the bottom of this page
3. Then select "Download"
###### When prompted which components to install:
1. Under the latest Qt version:
- Select MSVC 2017 64-bit (or MSVC 2015 64-bit if you still use Visual Studio 2015)
- Optionally, enable Qt WebEngine
2. Under Tools:
- Select Qt Creator, and Qt Creator CDB Debugger Support
To setup automatic code formating with QT Creator, see [this guide](https://gist.github.com/pajlada/0296454198eb8f8789fd6fe7ea660c5b).
### Windows (Using MSYS2) ### Get it automated with QT Creator + Beautifier + Clang Format
Note: This guide is currently out of date and will not work as is. 1. Download LLVM: http://releases.llvm.org/5.0.1/LLVM-5.0.1-win64.exe
Note: This build will have some features missing from the build. 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
5. Select `Tools` > `Options` > `Beautifier`
6. Under `General` select `Tool: ClangFormat` and enable `Automatic Formatting on File Save`
7. Under `Clang Format` select `Use predefined style: File` and `Fallback style: None`
Building using MSYS2 can be quite easier process. Check out MSYS2 at [msys2.org](http://www.msys2.org/). Qt creator should now format the documents when saving it.
Be sure to add `-j <number of threads>` as a make argument so it will use all your cpu cores to build. [example setup](https://i.imgur.com/qlESlS1.png)
You can also add `-o2` to optimize the final binary size but increase compilation time, and add `-pipe` to use more ram in compilation but increase compilation speed
1. open appropriate MSYS2 terminal and do `pacman -S mingw-w64-<arch>-boost mingw-w64-<arch>-qt5 mingw-w64-<arch>-rapidjson` where `<arch>` is `x86_64` or `i686`
2. go into the project directory
3. create build folder `mkdir build && cd build`
4. `qmake .. && mingw32-make`
###
### Linux
#### Ubuntu 16.04.2 LTS
*most likely works the same for other Debian-like distros*
1. install QT Creator `sudo apt-get install qtcreator qtmultimedia5-dev`
2. install boost-dev `sudo apt-get install libboost-dev`
3. open `chatterino.pro` with QT Creator and build
#### Ubuntu 18.04
*most likely works the same for other Debian-like distros*
1. Install dependencies (and the C++ IDE Qt Creator) `sudo apt install qtcreator qtmultimedia5-dev libqt5svg5-dev libboost-dev`
2. Install rapidjson to `/usr/local/` like this: From the Chatterino2 root folder: `sudo cp -r lib/rapidjson/include/rapidjson /usr/local/include`. If you want to install it to another place, you have to make sure it's in the chatterino.pro include path
3. open `chatterino.pro` with QT Creator and build
#### Arch Linux
install [chatterino2-git](https://aur.archlinux.org/packages/chatterino2-git/) from the aur or build manually as follows:
1. `sudo pacman -S qt5-base qt5-multimedia qt5-svg gst-plugins-ugly gst-plugins-good boost rapidjson`
2. go into project directory
3. create build folder `mkdir build && cd build`
4. `qmake .. && make`
### Mac OSX
1. install Xcode and Xcode Command Line Utilites
2. start Xcode, settings -> Locations, activate your Command Line Tools
3. install Qt Creator
4. install brew https://brew.sh/
5. `brew install boost openssl rapidjson`
6. build the project using Qt Creator
If the Project does not build at this point, you might need to add additional Paths/Libs, because brew does not install openssl and boost in the common path. You can get their path using
`brew info openssl`
`brew info boost`
The lines which you need to add to your project file should look similar to this
```
INCLUDEPATH += /usr/local/opt/openssl/include
LIBS += -L/usr/local/opt/openssl/lib
INCLUDEPATH += "/usr/local/Cellar/boost/1.67.0_1/include"
LIBS += -L"/usr/local/Cellar/boost/1.67.0_1/lib"
```
Test 1

View file

@ -1,19 +0,0 @@
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

View file

@ -1,102 +0,0 @@
const ignoredPages = {
"settings": true,
"payments": true,
"inventory": true,
"messages": true,
"subscriptions": true,
"friends": true,
"directory": true,
};
const appName = "com.chatterino.chatterino";
let port = null;
/// Connect to port
function connectPort() {
port = chrome.runtime.connectNative("com.chatterino.chatterino");
console.log("port connected");
port.onMessage.addListener(function (msg) {
console.log(msg);
});
port.onDisconnect.addListener(function () {
console.log("port disconnected");
port = null;
});
}
function getPort() {
if (port) {
return port;
} else {
// TODO: add cooldown
connectPort();
return port;
}
}
/// Tab listeners
chrome.tabs.onActivated.addListener((activeInfo) => {
chrome.tabs.get(activeInfo.tabId, (tab) => {
if (!tab)
return;
if (!tab.url)
return;
matchUrl(tab.url, tab);
});
});
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (!tab.highlighted)
return;
matchUrl(changeInfo.url, tab);
});
/// Misc
function matchUrl(url, tab) {
if (!url)
return;
const match = url.match(/^https?:\/\/(www\.)?twitch.tv\/([a-zA-Z0-9]+)\/?$/);
let channelName;
console.log(tab);
if (match && (channelName = match[2], !ignoredPages[channelName])) {
console.log("channelName " + channelName);
console.log("winId " + tab.windowId);
chrome.windows.get(tab.windowId, {}, (window) => {
let yOffset = window.height - tab.height;
let port = getPort();
if (port) {
port.postMessage({
action: "select",
attach: true,
type: "twitch",
name: channelName,
winId: "" + tab.windowId,
yOffset: yOffset
});
}
});
} else {
let port = getPort();
if (port) {
port.postMessage({
action: "detach",
winId: "" + tab.windowId
})
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View file

@ -1,21 +0,0 @@
{
"name": "Chatterino",
"version": "1.0",
"description": "xd",
"permissions": [
"tabs", "nativeMessaging"
],
"icons": {
"256": "icon.png"
},
"manifest_version": 2,
"background": {
"scripts": [
"background.js"
],
"persistent": false
},
"browser_action": {
"default_popup": "popup.html"
}
}

View file

@ -1,6 +0,0 @@
<!DOCTYPE html>
<html>
<body>
xd
</body>
</html>

View file

@ -26,6 +26,11 @@ equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
macx:ICON = resources/images/chatterino2.icns macx:ICON = resources/images/chatterino2.icns
win32:RC_FILE = resources/windows.rc win32:RC_FILE = resources/windows.rc
macx {
LIBS += -L/usr/local/lib
}
# Submodules # Submodules
include(dependencies/rapidjson.pri) include(dependencies/rapidjson.pri)
include(dependencies/settings.pri) include(dependencies/settings.pri)
@ -39,10 +44,14 @@ include(dependencies/openssl.pri)
include(dependencies/boost.pri) include(dependencies/boost.pri)
# Optional feature: QtWebEngine # Optional feature: QtWebEngine
exists ($(QTDIR)/include/QtWebEngine/QtWebEngine) { #exists ($(QTDIR)/include/QtWebEngine/QtWebEngine) {
message(Using QWebEngine) # message(Using QWebEngine)
QT += webenginewidgets # QT += webenginewidgets
DEFINES += "USEWEBENGINE" # DEFINES += "USEWEBENGINE"
#}
linux {
LIBS += -lrt
} }
win32 { win32 {
@ -105,7 +114,6 @@ SOURCES += \
src/providers/twitch/twitchmessagebuilder.cpp \ src/providers/twitch/twitchmessagebuilder.cpp \
src/providers/twitch/twitchserver.cpp \ src/providers/twitch/twitchserver.cpp \
src/providers/twitch/pubsub.cpp \ src/providers/twitch/pubsub.cpp \
src/singletons/accountmanager.cpp \
src/singletons/commandmanager.cpp \ src/singletons/commandmanager.cpp \
src/singletons/emotemanager.cpp \ src/singletons/emotemanager.cpp \
src/singletons/fontmanager.cpp \ src/singletons/fontmanager.cpp \
@ -185,7 +193,6 @@ SOURCES += \
src/widgets/attachedwindow.cpp \ src/widgets/attachedwindow.cpp \
src/widgets/settingspages/externaltoolspage.cpp \ src/widgets/settingspages/externaltoolspage.cpp \
src/widgets/helper/comboboxitemdelegate.cpp \ src/widgets/helper/comboboxitemdelegate.cpp \
src/util/signalvectormodel.cpp \
src/controllers/commands/command.cpp \ src/controllers/commands/command.cpp \
src/controllers/commands/commandmodel.cpp \ src/controllers/commands/commandmodel.cpp \
src/controllers/commands/commandcontroller.cpp \ src/controllers/commands/commandcontroller.cpp \
@ -197,7 +204,15 @@ SOURCES += \
src/controllers/accounts/accountcontroller.cpp \ src/controllers/accounts/accountcontroller.cpp \
src/controllers/accounts/accountmodel.cpp \ src/controllers/accounts/accountmodel.cpp \
src/controllers/accounts/account.cpp \ src/controllers/accounts/account.cpp \
src/widgets/helper/splitoverlay.cpp src/widgets/helper/splitoverlay.cpp \
src/widgets/helper/dropoverlay.cpp \
src/widgets/helper/splitnode.cpp \
src/widgets/notificationpopup.cpp \
src/controllers/taggedusers/taggeduserscontroller.cpp \
src/controllers/taggedusers/taggeduser.cpp \
src/controllers/taggedusers/taggedusersmodel.cpp \
src/util/emotemap.cpp \
src/providers/irc/ircconnection2.cpp
HEADERS += \ HEADERS += \
src/precompiled_header.hpp \ src/precompiled_header.hpp \
@ -226,7 +241,6 @@ HEADERS += \
src/providers/twitch/twitchmessagebuilder.hpp \ src/providers/twitch/twitchmessagebuilder.hpp \
src/providers/twitch/twitchserver.hpp \ src/providers/twitch/twitchserver.hpp \
src/providers/twitch/pubsub.hpp \ src/providers/twitch/pubsub.hpp \
src/singletons/accountmanager.hpp \
src/singletons/commandmanager.hpp \ src/singletons/commandmanager.hpp \
src/singletons/emotemanager.hpp \ src/singletons/emotemanager.hpp \
src/singletons/fontmanager.hpp \ src/singletons/fontmanager.hpp \
@ -343,7 +357,17 @@ HEADERS += \
src/controllers/accounts/accountmodel.hpp \ src/controllers/accounts/accountmodel.hpp \
src/controllers/accounts/account.hpp \ src/controllers/accounts/account.hpp \
src/util/sharedptrelementless.hpp \ src/util/sharedptrelementless.hpp \
src/widgets/helper/splitoverlay.hpp src/widgets/helper/splitoverlay.hpp \
src/widgets/helper/dropoverlay.hpp \
src/widgets/helper/splitnode.hpp \
src/widgets/notificationpopup.hpp \
src/controllers/taggedusers/taggeduserscontroller.hpp \
src/controllers/taggedusers/taggeduser.hpp \
src/providerid.hpp \
src/controllers/taggedusers/taggedusersmodel.hpp \
src/util/qstringhash.hpp \
src/util/mutexvalue.hpp \
src/providers/irc/ircconnection2.hpp
RESOURCES += \ RESOURCES += \
resources/resources.qrc resources/resources.qrc

Binary file not shown.

Before

Width:  |  Height:  |  Size: 793 B

After

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 812 B

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 811 B

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 793 B

After

Width:  |  Height:  |  Size: 361 B

View file

@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,23 @@
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.

View file

@ -0,0 +1,26 @@
Copyright (C) 2008-2016 The Communi Project
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* 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.
* Neither the name of the {organization} nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
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 HOLDER 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.

View file

@ -0,0 +1,125 @@
LICENSE ISSUES
==============
The OpenSSL toolkit stays under a double license, i.e. both the conditions of
the OpenSSL License and the original SSLeay license apply to the toolkit.
See below for the actual license texts.
OpenSSL License
---------------
/* ====================================================================
* Copyright (c) 1998-2018 The OpenSSL Project. 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.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
*
* 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* openssl-core@openssl.org.
*
* 5. Products derived from this software may not be called "OpenSSL"
* nor may "OpenSSL" appear in their names without prior written
* permission of the OpenSSL Project.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the OpenSSL Project
* for use in the OpenSSL Toolkit (http://www.openssl.org/)"
*
* THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
* EXPRESSED 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 OpenSSL PROJECT OR
* ITS 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.
* ====================================================================
*
* This product includes cryptographic software written by Eric Young
* (eay@cryptsoft.com). This product includes software written by Tim
* Hudson (tjh@cryptsoft.com).
*
*/
Original SSLeay License
-----------------------
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
* All rights reserved.
*
* This package is an SSL implementation written
* by Eric Young (eay@cryptsoft.com).
* The implementation was written so as to conform with Netscapes SSL.
*
* This library is free for commercial and non-commercial use as long as
* the following conditions are aheared to. The following conditions
* apply to all code found in this distribution, be it the RC4, RSA,
* lhash, DES, etc., code; not just the SSL code. The SSL documentation
* included with this distribution is covered by the same copyright terms
* except that the holder is Tim Hudson (tjh@cryptsoft.com).
*
* Copyright remains Eric Young's, and as such any Copyright notices in
* the code are not to be removed.
* If this package is used in a product, Eric Young should be given attribution
* as the author of the parts of the library used.
* This can be in the form of a textual message at program startup or
* in documentation (online or textual) provided with the package.
*
* 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 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* "This product includes cryptographic software written by
* Eric Young (eay@cryptsoft.com)"
* The word 'cryptographic' can be left out if the rouines from the library
* being used are not cryptographic related :-).
* 4. If you include any Windows specific code (or a derivative thereof) from
* the apps directory (application code) you must include an acknowledgement:
* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
*
* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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.
*
* The licence and distribution terms for any publically available version or
* derivative of this code cannot be changed. i.e. this code cannot simply be
* copied and put under another distribution licence
* [including the GNU Public Licence.]
*/

View file

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

View file

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

View file

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View file

@ -0,0 +1,57 @@
Tencent is pleased to support the open source community by making RapidJSON available.
Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
If you have downloaded a copy of the RapidJSON binary from Tencent, please note that the RapidJSON binary is licensed under the MIT License.
If you have downloaded a copy of the RapidJSON source code from Tencent, please note that RapidJSON source code is licensed under the MIT License, except for the third-party components listed below which are subject to different license terms. Your integration of RapidJSON into your own projects may require compliance with the MIT License, as well as the other licenses applicable to the third-party components included within RapidJSON. To avoid the problematic JSON license in your own projects, it's sufficient to exclude the bin/jsonchecker/ directory, as it's the only code under the JSON license.
A copy of the MIT License is included in this file.
Other dependencies and licenses:
Open Source Software Licensed Under the BSD License:
--------------------------------------------------------------------
The msinttypes r29
Copyright (c) 2006-2013 Alexander Chemeris
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS AND 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.
Open Source Software Licensed Under the JSON License:
--------------------------------------------------------------------
json.org
Copyright (c) 2002 JSON.org
All Rights Reserved.
JSON_checker
Copyright (c) 2002 JSON.org
All Rights Reserved.
Terms of the JSON License:
---------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
The Software shall be used for Good, not Evil.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Terms of the MIT License:
--------------------------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,145 @@
Main Library:
Copyright (c) 2014, Peter Thorson. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of the WebSocket++ Project nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
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 PETER THORSON 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.
Bundled Libraries:
****** Base 64 Library (base64/base64.hpp) ******
base64.hpp is a repackaging of the base64.cpp and base64.h files into a
single header suitable for use as a header only library. This conversion was
done by Peter Thorson (webmaster@zaphoyd.com) in 2012. All modifications to
the code are redistributed under the same license as the original, which is
listed below.
base64.cpp and base64.h
Copyright (C) 2004-2008 René Nyffenegger
This source code is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code.
3. This notice may not be removed or altered from any source distribution.
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
****** SHA1 Library (sha1/sha1.hpp) ******
sha1.hpp is a repackaging of the sha1.cpp and sha1.h files from the shallsha1
library (http://code.google.com/p/smallsha1/) into a single header suitable for
use as a header only library. This conversion was done by Peter Thorson
(webmaster@zaphoyd.com) in 2013. All modifications to the code are redistributed
under the same license as the original, which is listed below.
Copyright (c) 2011, Micael Hildenborg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of Micael Hildenborg nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY Micael Hildenborg ''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 Micael Hildenborg 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.
****** MD5 Library (common/md5.hpp) ******
md5.hpp is a reformulation of the md5.h and md5.c code from
http://www.opensource.apple.com/source/cups/cups-59/cups/md5.c to allow it to
function as a component of a header only library. This conversion was done by
Peter Thorson (webmaster@zaphoyd.com) in 2012 for the WebSocket++ project. The
changes are released under the same license as the original (listed below)
Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
****** UTF8 Validation logic (utf8_validation.hpp) ******
utf8_validation.hpp is adapted from code originally written by Bjoern Hoehrmann
<bjoern@hoehrmann.de>. See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for
details.
The original license:
Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -51,6 +51,15 @@
<file>images/split/splitright.png</file> <file>images/split/splitright.png</file>
<file>images/split/splitup.png</file> <file>images/split/splitup.png</file>
<file>images/split/splitmove.png</file> <file>images/split/splitmove.png</file>
<file>licenses/boost_boost.txt</file>
<file>licenses/fmt_bsd2.txt</file>
<file>licenses/libcommuni_BSD3.txt</file>
<file>licenses/openssl.txt</file>
<file>licenses/pajlada_settings.txt</file>
<file>licenses/pajlada_signals.txt</file>
<file>licenses/qt_lgpl-3.0.txt</file>
<file>licenses/rapidjson.txt</file>
<file>licenses/websocketpp.txt</file>
</qresource> </qresource>
<qresource prefix="/qt/etc"> <qresource prefix="/qt/etc">
<file>qt.conf</file> <file>qt.conf</file>

View file

@ -1,11 +1,12 @@
#include "application.hpp" #include "application.hpp"
#include "controllers/accounts/accountcontroller.hpp"
#include "controllers/commands/commandcontroller.hpp" #include "controllers/commands/commandcontroller.hpp"
#include "controllers/highlights/highlightcontroller.hpp" #include "controllers/highlights/highlightcontroller.hpp"
#include "controllers/ignores/ignorecontroller.hpp" #include "controllers/ignores/ignorecontroller.hpp"
#include "controllers/taggedusers/taggeduserscontroller.hpp"
#include "providers/twitch/pubsub.hpp" #include "providers/twitch/pubsub.hpp"
#include "providers/twitch/twitchserver.hpp" #include "providers/twitch/twitchserver.hpp"
#include "singletons/accountmanager.hpp"
#include "singletons/emotemanager.hpp" #include "singletons/emotemanager.hpp"
#include "singletons/fontmanager.hpp" #include "singletons/fontmanager.hpp"
#include "singletons/loggingmanager.hpp" #include "singletons/loggingmanager.hpp"
@ -19,12 +20,6 @@
#include <atomic> #include <atomic>
#ifdef Q_OS_WIN
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#endif
using namespace chatterino::singletons; using namespace chatterino::singletons;
namespace chatterino { namespace chatterino {
@ -34,7 +29,7 @@ namespace {
bool isBigEndian() bool isBigEndian()
{ {
int test = 1; int test = 1;
char *p = (char *)&test; char *p = reinterpret_cast<char *>(&test);
return p[0] == 0; return p[0] == 0;
} }
@ -61,6 +56,7 @@ void Application::construct()
isAppConstructed = true; isAppConstructed = true;
// 1. Instantiate all classes // 1. Instantiate all classes
this->settings = new singletons::SettingManager;
this->paths = new singletons::PathManager(this->argc, this->argv); this->paths = new singletons::PathManager(this->argc, this->argv);
this->themes = new singletons::ThemeManager; this->themes = new singletons::ThemeManager;
this->windows = new singletons::WindowManager; this->windows = new singletons::WindowManager;
@ -68,9 +64,9 @@ void Application::construct()
this->commands = new controllers::commands::CommandController; this->commands = new controllers::commands::CommandController;
this->highlights = new controllers::highlights::HighlightController; this->highlights = new controllers::highlights::HighlightController;
this->ignores = new controllers::ignores::IgnoreController; this->ignores = new controllers::ignores::IgnoreController;
this->accounts = new singletons::AccountManager; this->taggedUsers = new controllers::taggedusers::TaggedUsersController;
this->accounts = new controllers::accounts::AccountController;
this->emotes = new singletons::EmoteManager; this->emotes = new singletons::EmoteManager;
this->settings = new singletons::SettingManager;
this->fonts = new singletons::FontManager; this->fonts = new singletons::FontManager;
this->resources = new singletons::ResourceManager; this->resources = new singletons::ResourceManager;
@ -92,12 +88,13 @@ void Application::initialize()
// 2. Initialize/load classes // 2. Initialize/load classes
this->settings->initialize(); this->settings->initialize();
this->windows->initialize();
this->nativeMessaging->registerHost(); this->nativeMessaging->registerHost();
this->settings->load(); this->settings->load();
this->commands->load(); this->commands->load();
this->logging->initialize();
this->windows->initialize();
this->resources->initialize(); this->resources->initialize();
@ -109,7 +106,6 @@ void Application::initialize()
this->accounts->load(); this->accounts->load();
this->twitch.server->initialize(); this->twitch.server->initialize();
this->logging->initialize();
// XXX // XXX
this->settings->updateWordTypeMask(); this->settings->updateWordTypeMask();
@ -182,8 +178,9 @@ void Application::initialize()
} }
auto msg = messages::Message::createTimeoutMessage(action); auto msg = messages::Message::createTimeoutMessage(action);
msg->flags |= messages::Message::PubSub;
util::postToThread([chan, msg] { chan->addMessage(msg); }); util::postToThread([chan, msg] { chan->addOrReplaceTimeout(msg); });
}); });
this->twitch.pubsub->sig.moderation.userUnbanned.connect([&](const auto &action) { this->twitch.pubsub->sig.moderation.userUnbanned.connect([&](const auto &action) {
@ -205,10 +202,10 @@ void Application::initialize()
// TODO(pajlada): Unlisten to all authed topics instead of only moderation topics // TODO(pajlada): Unlisten to all authed topics instead of only moderation topics
// this->twitch.pubsub->UnlistenAllAuthedTopics(); // this->twitch.pubsub->UnlistenAllAuthedTopics();
this->twitch.pubsub->listenToWhispers(this->accounts->Twitch.getCurrent()); // this->twitch.pubsub->listenToWhispers(this->accounts->twitch.getCurrent()); //
}; };
this->accounts->Twitch.currentUserChanged.connect(RequestModerationActions); this->accounts->twitch.currentUserChanged.connect(RequestModerationActions);
RequestModerationActions(); RequestModerationActions();
} }
@ -231,52 +228,6 @@ void Application::save()
this->commands->save(); this->commands->save();
} }
void Application::runNativeMessagingHost()
{
auto app = getApp();
app->nativeMessaging = new singletons::NativeMessagingManager;
#ifdef Q_OS_WIN
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
#endif
#if 0
bool bigEndian = isBigEndian();
#endif
while (true) {
char size_c[4];
std::cin.read(size_c, 4);
if (std::cin.eof()) {
break;
}
uint32_t size = *reinterpret_cast<uint32_t *>(size_c);
#if 0
// To avoid breaking strict-aliasing rules and potentially inducing undefined behaviour, the following code can be run instead
uint32_t size = 0;
if (bigEndian) {
size = size_c[3] | static_cast<uint32_t>(size_c[2]) << 8 |
static_cast<uint32_t>(size_c[1]) << 16 | static_cast<uint32_t>(size_c[0]) << 24;
} else {
size = size_c[0] | static_cast<uint32_t>(size_c[1]) << 8 |
static_cast<uint32_t>(size_c[2]) << 16 | static_cast<uint32_t>(size_c[3]) << 24;
}
#endif
char *b = (char *)malloc(size + 1);
std::cin.read(b, size);
*(b + size) = '\0';
app->nativeMessaging->sendToGuiProcess(QByteArray(b, size));
free(b);
}
}
Application *getApp() Application *getApp()
{ {
assert(staticApp != nullptr); assert(staticApp != nullptr);

View file

@ -26,6 +26,12 @@ class HighlightController;
namespace ignores { namespace ignores {
class IgnoreController; class IgnoreController;
} }
namespace taggedusers {
class TaggedUsersController;
}
namespace accounts {
class AccountController;
}
} // namespace controllers } // namespace controllers
namespace singletons { namespace singletons {
@ -67,7 +73,8 @@ public:
controllers::commands::CommandController *commands = nullptr; controllers::commands::CommandController *commands = nullptr;
controllers::highlights::HighlightController *highlights = nullptr; controllers::highlights::HighlightController *highlights = nullptr;
controllers::ignores::IgnoreController *ignores = nullptr; controllers::ignores::IgnoreController *ignores = nullptr;
singletons::AccountManager *accounts = nullptr; controllers::taggedusers::TaggedUsersController *taggedUsers = nullptr;
controllers::accounts::AccountController *accounts = nullptr;
singletons::EmoteManager *emotes = nullptr; singletons::EmoteManager *emotes = nullptr;
singletons::NativeMessagingManager *nativeMessaging = nullptr; singletons::NativeMessagingManager *nativeMessaging = nullptr;
singletons::SettingManager *settings = nullptr; singletons::SettingManager *settings = nullptr;

View file

@ -45,6 +45,11 @@ Channel::Type Channel::getType() const
return this->type; return this->type;
} }
bool Channel::isTwitchChannel() const
{
return this->type >= Twitch && this->type < TwitchEnd;
}
bool Channel::isEmpty() const bool Channel::isEmpty() const
{ {
return this->name.isEmpty(); return this->name.isEmpty();
@ -60,57 +65,14 @@ void Channel::addMessage(MessagePtr message)
auto app = getApp(); auto app = getApp();
MessagePtr deleted; MessagePtr deleted;
bool isTimeout = (message->flags & Message::Timeout) != 0; const QString &username = message->loginName;
if (!username.isEmpty()) {
if (!isTimeout) { // TODO: Add recent chatters display name. This should maybe be a setting
const QString &username = message->loginName; this->addRecentChatter(message);
if (!username.isEmpty()) {
// TODO: Add recent chatters display name. This should maybe be a setting
this->addRecentChatter(message);
}
} }
app->logging->addMessage(this->name, message); app->logging->addMessage(this->name, message);
if (isTimeout) {
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
bool addMessage = true;
int snapshotLength = snapshot.getLength();
int end = std::max(0, snapshotLength - 20);
for (int i = snapshotLength - 1; i >= end; --i) {
auto &s = snapshot[i];
if (s->flags.HasFlag(Message::Untimeout) && s->timeoutUser == message->timeoutUser) {
break;
}
if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == message->timeoutUser) {
assert(message->banAction != nullptr);
MessagePtr replacement(
Message::createTimeoutMessage(*(message->banAction), s->count + 1));
this->replaceMessage(s, replacement);
addMessage = false;
}
}
// disable the messages from the user
for (int i = 0; i < snapshotLength; i++) {
auto &s = snapshot[i];
if ((s->flags & (Message::Timeout | Message::Untimeout)) == 0 &&
s->loginName == message->timeoutUser) {
s->flags.EnableFlag(Message::Disabled);
}
}
// XXX: Might need the following line
// WindowManager::getInstance().repaintVisibleChatWidgets(this);
if (!addMessage) {
return;
}
}
if (this->messages.pushBack(message, deleted)) { if (this->messages.pushBack(message, deleted)) {
this->messageRemovedFromStart.invoke(deleted); this->messageRemovedFromStart.invoke(deleted);
} }
@ -118,6 +80,73 @@ void Channel::addMessage(MessagePtr message)
this->messageAppended.invoke(message); this->messageAppended.invoke(message);
} }
void Channel::addOrReplaceTimeout(messages::MessagePtr message)
{
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
int snapshotLength = snapshot.getLength();
int end = std::max(0, snapshotLength - 20);
bool addMessage = true;
QTime minimumTime = QTime::currentTime().addSecs(-5);
for (int i = snapshotLength - 1; i >= end; --i) {
auto &s = snapshot[i];
qDebug() << s->parseTime << minimumTime;
if (s->parseTime < minimumTime) {
break;
}
if (s->flags.HasFlag(Message::Untimeout) && s->timeoutUser == message->timeoutUser) {
break;
}
if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == message->timeoutUser) {
if (message->flags.HasFlag(Message::PubSub) && !s->flags.HasFlag(Message::PubSub)) {
this->replaceMessage(s, message);
addMessage = false;
break;
}
if (!message->flags.HasFlag(Message::PubSub) && s->flags.HasFlag(Message::PubSub)) {
addMessage = false;
break;
}
int count = s->count + 1;
messages::MessagePtr replacement(Message::createSystemMessage(
message->searchText + QString("(") + QString::number(count) + " times)"));
replacement->timeoutUser = message->timeoutUser;
replacement->count = count;
replacement->flags = message->flags;
this->replaceMessage(s, replacement);
return;
}
}
// disable the messages from the user
for (int i = 0; i < snapshotLength; i++) {
auto &s = snapshot[i];
if ((s->flags & (Message::Timeout | Message::Untimeout)) == 0 &&
s->loginName == message->timeoutUser) {
s->flags.EnableFlag(Message::Disabled);
}
}
if (addMessage) {
this->addMessage(message);
}
// XXX: Might need the following line
// WindowManager::getInstance().repaintVisibleChatWidgets(this);
}
void Channel::addMessagesAtStart(std::vector<messages::MessagePtr> &_messages) void Channel::addMessagesAtStart(std::vector<messages::MessagePtr> &_messages)
{ {
std::vector<messages::MessagePtr> addedMessages = this->messages.pushFront(_messages); std::vector<messages::MessagePtr> addedMessages = this->messages.pushFront(_messages);

View file

@ -29,6 +29,7 @@ public:
TwitchWhispers, TwitchWhispers,
TwitchWatching, TwitchWatching,
TwitchMentions, TwitchMentions,
TwitchEnd,
}; };
explicit Channel(const QString &_name, Type type); explicit Channel(const QString &_name, Type type);
@ -43,11 +44,13 @@ public:
pajlada::Signals::NoArgSignal destroyed; pajlada::Signals::NoArgSignal destroyed;
Type getType() const; Type getType() const;
bool isTwitchChannel() const;
virtual bool isEmpty() const; virtual bool isEmpty() const;
messages::LimitedQueueSnapshot<messages::MessagePtr> getMessageSnapshot(); messages::LimitedQueueSnapshot<messages::MessagePtr> getMessageSnapshot();
void addMessage(messages::MessagePtr message); void addMessage(messages::MessagePtr message);
void addMessagesAtStart(std::vector<messages::MessagePtr> &messages); void addMessagesAtStart(std::vector<messages::MessagePtr> &messages);
void addOrReplaceTimeout(messages::MessagePtr message);
void replaceMessage(messages::MessagePtr message, messages::MessagePtr replacement); void replaceMessage(messages::MessagePtr message, messages::MessagePtr replacement);
virtual void addRecentChatter(const std::shared_ptr<messages::Message> &message); virtual void addRecentChatter(const std::shared_ptr<messages::Message> &message);

View file

@ -3,6 +3,7 @@
#include "debug/log.hpp" #include "debug/log.hpp"
#include <QString> #include <QString>
#include <QWidget>
#include <boost/preprocessor.hpp> #include <boost/preprocessor.hpp>
#include <string> #include <string>
@ -20,4 +21,8 @@ inline QString qS(const std::string &string)
return QString::fromStdString(string); return QString::fromStdString(string);
} }
const Qt::KeyboardModifiers showSplitOverlayModifiers = Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showAddSplitRegions = Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
} // namespace chatterino } // namespace chatterino

View file

@ -1,11 +1,23 @@
#include "account.hpp" #include "account.hpp"
#include <tuple>
namespace chatterino { namespace chatterino {
namespace controllers { namespace controllers {
namespace accounts { namespace accounts {
Account::Account(const QString &category) Account::Account(ProviderId _providerId)
: providerId(_providerId)
{ {
static QString twitch("Twitch");
this->category = [&]() {
switch (_providerId) {
case ProviderId::Twitch:
return twitch;
}
return QString("Unknown ProviderId");
}();
} }
const QString &Account::getCategory() const const QString &Account::getCategory() const
@ -13,16 +25,17 @@ const QString &Account::getCategory() const
return this->category; return this->category;
} }
ProviderId Account::getProviderId() const
{
return this->providerId;
}
bool Account::operator<(const Account &other) const bool Account::operator<(const Account &other) const
{ {
if (this->category < other.category) { QString a = this->toString();
return true; QString b = other.toString();
} else if (this->category == other.category) {
if (this->toString() < other.toString()) { return std::tie(this->category, a) < std::tie(other.category, b);
return true;
}
}
return false;
} }
} // namespace accounts } // namespace accounts

View file

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "providerid.hpp"
#include <QString> #include <QString>
namespace chatterino { namespace chatterino {
@ -9,14 +11,17 @@ namespace accounts {
class Account class Account
{ {
public: public:
Account(const QString &category); Account(ProviderId providerId);
virtual ~Account() = default;
virtual QString toString() const = 0; virtual QString toString() const = 0;
const QString &getCategory() const; const QString &getCategory() const;
ProviderId getProviderId() const;
bool operator<(const Account &other) const; bool operator<(const Account &other) const;
private: private:
ProviderId providerId;
QString category; QString category;
}; };

View file

@ -8,13 +8,42 @@ namespace accounts {
AccountController::AccountController() AccountController::AccountController()
{ {
this->twitch.accounts.itemInserted.connect([this](const auto &args) {
this->accounts.insertItem(std::dynamic_pointer_cast<Account>(args.item));
});
this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
if (args.caller != this) {
auto &accs = this->twitch.accounts.getVector();
auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end());
this->accounts.removeItem(it - accs.begin());
}
});
this->accounts.itemRemoved.connect([this](const auto &args) {
switch (args.item->getProviderId()) {
case ProviderId::Twitch: {
auto &accs = this->twitch.accounts.getVector();
auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end());
this->twitch.accounts.removeItem(it - accs.begin(), this);
} break;
}
});
}
void AccountController::load()
{
this->twitch.load();
} }
AccountModel *AccountController::createModel(QObject *parent) AccountModel *AccountController::createModel(QObject *parent)
{ {
AccountModel *model = new AccountModel(parent); AccountModel *model = new AccountModel(parent);
//(util::BaseSignalVector<stdAccount> *)
model->init(&this->accounts); model->init(&this->accounts);
return model; return model;
} }

View file

@ -3,6 +3,7 @@
#include <QObject> #include <QObject>
#include "controllers/accounts/account.hpp" #include "controllers/accounts/account.hpp"
#include "providers/twitch/twitchaccountmanager.hpp"
#include "util/sharedptrelementless.hpp" #include "util/sharedptrelementless.hpp"
#include "util/signalvector2.hpp" #include "util/signalvector2.hpp"
@ -19,6 +20,10 @@ public:
AccountModel *createModel(QObject *parent); AccountModel *createModel(QObject *parent);
void load();
providers::twitch::TwitchAccountManager twitch;
private: private:
util::SortedSignalVector<std::shared_ptr<Account>, util::SharedPtrElementLess<Account>> util::SortedSignalVector<std::shared_ptr<Account>, util::SharedPtrElementLess<Account>>
accounts; accounts;

View file

@ -1,5 +1,7 @@
#include "accountmodel.hpp" #include "accountmodel.hpp"
#include "util/standarditemhelper.hpp"
namespace chatterino { namespace chatterino {
namespace controllers { namespace controllers {
namespace accounts { namespace accounts {
@ -10,16 +12,49 @@ AccountModel::AccountModel(QObject *parent)
} }
// turn a vector item into a model row // turn a vector item into a model row
std::shared_ptr<Account> AccountModel::getItemFromRow(std::vector<QStandardItem *> &row) std::shared_ptr<Account> AccountModel::getItemFromRow(std::vector<QStandardItem *> &,
const std::shared_ptr<Account> &original)
{ {
return nullptr; return original;
} }
// turns a row in the model into a vector item // turns a row in the model into a vector item
void AccountModel::getRowFromItem(const std::shared_ptr<Account> &item, void AccountModel::getRowFromItem(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row) std::vector<QStandardItem *> &row)
{ {
row[0]->setData(item->toString(), Qt::DisplayRole); util::setStringItem(row[0], item->toString(), false);
row[0]->setData(QFont("Segoe UI", 10), Qt::FontRole);
}
int AccountModel::beforeInsert(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, int proposedIndex)
{
if (this->categoryCount[item->getCategory()]++ == 0) {
auto row = this->createRow();
util::setStringItem(row[0], item->getCategory(), false, false);
row[0]->setData(QFont("Segoe UI Light", 16), Qt::FontRole);
this->insertCustomRow(std::move(row), proposedIndex);
return proposedIndex + 1;
}
return proposedIndex;
}
void AccountModel::afterRemoved(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, int index)
{
auto it = this->categoryCount.find(item->getCategory());
assert(it != this->categoryCount.end());
if (it->second <= 1) {
this->categoryCount.erase(it);
this->removeCustomRow(index - 1);
} else {
it->second--;
}
} }
} // namespace accounts } // namespace accounts

View file

@ -1,8 +1,11 @@
#pragma once #pragma once
#include "controllers/accounts/account.hpp" #include "controllers/accounts/account.hpp"
#include "util/qstringhash.hpp"
#include "util/signalvectormodel.hpp" #include "util/signalvectormodel.hpp"
#include <unordered_map>
namespace chatterino { namespace chatterino {
namespace controllers { namespace controllers {
namespace accounts { namespace accounts {
@ -16,13 +19,23 @@ public:
protected: protected:
// turn a vector item into a model row // turn a vector item into a model row
virtual std::shared_ptr<Account> getItemFromRow(std::vector<QStandardItem *> &row) override; virtual std::shared_ptr<Account> getItemFromRow(
std::vector<QStandardItem *> &row, const std::shared_ptr<Account> &original) override;
// turns a row in the model into a vector item // turns a row in the model into a vector item
virtual void getRowFromItem(const std::shared_ptr<Account> &item, virtual void getRowFromItem(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row) override; std::vector<QStandardItem *> &row) override;
virtual int beforeInsert(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, int proposedIndex) override;
virtual void afterRemoved(const std::shared_ptr<Account> &item,
std::vector<QStandardItem *> &row, int index) override;
friend class AccountController; friend class AccountController;
private:
std::unordered_map<QString, int> categoryCount;
}; };
} // namespace accounts } // namespace accounts

View file

@ -1,12 +1,12 @@
#include "commandcontroller.hpp" #include "commandcontroller.hpp"
#include "application.hpp" #include "application.hpp"
#include "controllers/accounts/accountcontroller.hpp"
#include "controllers/commands/command.hpp" #include "controllers/commands/command.hpp"
#include "controllers/commands/commandmodel.hpp" #include "controllers/commands/commandmodel.hpp"
#include "messages/messagebuilder.hpp" #include "messages/messagebuilder.hpp"
#include "providers/twitch/twitchchannel.hpp" #include "providers/twitch/twitchchannel.hpp"
#include "providers/twitch/twitchserver.hpp" #include "providers/twitch/twitchserver.hpp"
#include "singletons/accountmanager.hpp"
#include "singletons/pathmanager.hpp" #include "singletons/pathmanager.hpp"
#include "util/signalvector2.hpp" #include "util/signalvector2.hpp"
@ -110,7 +110,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
return ""; return "";
} else if (commandName == "/uptime") { } else if (commandName == "/uptime") {
const auto &streamStatus = twitchChannel->GetStreamStatus(); const auto &streamStatus = twitchChannel->getStreamStatus();
QString messageText = QString messageText =
streamStatus.live ? streamStatus.uptime : "Channel is not live."; streamStatus.live ? streamStatus.uptime : "Channel is not live.";
@ -121,7 +121,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
} else if (commandName == "/ignore" && words.size() >= 2) { } else if (commandName == "/ignore" && words.size() >= 2) {
auto app = getApp(); auto app = getApp();
auto user = app->accounts->Twitch.getCurrent(); auto user = app->accounts->twitch.getCurrent();
auto target = words.at(1); auto target = words.at(1);
if (user->isAnon()) { if (user->isAnon()) {
@ -138,7 +138,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
} else if (commandName == "/unignore" && words.size() >= 2) { } else if (commandName == "/unignore" && words.size() >= 2) {
auto app = getApp(); auto app = getApp();
auto user = app->accounts->Twitch.getCurrent(); auto user = app->accounts->twitch.getCurrent();
auto target = words.at(1); auto target = words.at(1);
if (user->isAnon()) { if (user->isAnon()) {
@ -161,7 +161,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
messages::MessageBuilder b; messages::MessageBuilder b;
b.emplace<messages::TextElement>(app->accounts->Twitch.getCurrent()->getUserName(), b.emplace<messages::TextElement>(app->accounts->twitch.getCurrent()->getUserName(),
messages::MessageElement::Text); messages::MessageElement::Text);
b.emplace<messages::TextElement>("->", messages::MessageElement::Text); b.emplace<messages::TextElement>("->", messages::MessageElement::Text);
b.emplace<messages::TextElement>(words[1], messages::MessageElement::Text); b.emplace<messages::TextElement>(words[1], messages::MessageElement::Text);
@ -169,7 +169,7 @@ QString CommandController::execCommand(const QString &text, ChannelPtr channel,
QString rest = ""; QString rest = "";
for (int i = 2; i < words.length(); i++) { for (int i = 2; i < words.length(); i++) {
rest += words[i]; rest += words[i] + " ";
} }
b.emplace<messages::TextElement>(rest, messages::MessageElement::Text); b.emplace<messages::TextElement>(rest, messages::MessageElement::Text);

View file

@ -11,7 +11,7 @@ CommandModel::CommandModel(QObject *parent)
} }
// turn a vector item into a model row // turn a vector item into a model row
Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row) Command CommandModel::getItemFromRow(std::vector<QStandardItem *> &row, const Command &original)
{ {
return Command(row[0]->data(Qt::EditRole).toString(), row[1]->data(Qt::EditRole).toString()); return Command(row[0]->data(Qt::EditRole).toString(), row[1]->data(Qt::EditRole).toString());
} }

View file

@ -17,7 +17,8 @@ class CommandModel : public util::SignalVectorModel<Command>
protected: protected:
// turn a vector item into a model row // turn a vector item into a model row
virtual Command getItemFromRow(std::vector<QStandardItem *> &row) override; virtual Command getItemFromRow(std::vector<QStandardItem *> &row,
const Command &command) override;
// turns a row in the model into a vector item // turns a row in the model into a vector item
virtual void getRowFromItem(const Command &item, std::vector<QStandardItem *> &row) override; virtual void getRowFromItem(const Command &item, std::vector<QStandardItem *> &row) override;

View file

@ -2,6 +2,7 @@
#include "application.hpp" #include "application.hpp"
#include "controllers/highlights/highlightmodel.hpp" #include "controllers/highlights/highlightmodel.hpp"
#include "widgets/notificationpopup.hpp"
namespace chatterino { namespace chatterino {
namespace controllers { namespace controllers {
@ -21,7 +22,6 @@ void HighlightController::initialize()
} }
this->phrases.delayedItemsChanged.connect([this] { // this->phrases.delayedItemsChanged.connect([this] { //
int xd = this->phrases.getVector().size();
this->highlightsSetting.setValue(this->phrases.getVector()); this->highlightsSetting.setValue(this->phrases.getVector());
}); });
} }
@ -34,6 +34,15 @@ HighlightModel *HighlightController::createModel(QObject *parent)
return model; return model;
} }
void HighlightController::addHighlight(const messages::MessagePtr &msg)
{
// static widgets::NotificationPopup popup;
// popup.updatePosition();
// popup.addMessage(msg);
// popup.show();
}
} // namespace highlights } // namespace highlights
} // namespace controllers } // namespace controllers
} // namespace chatterino } // namespace chatterino

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "controllers/highlights/highlightphrase.hpp" #include "controllers/highlights/highlightphrase.hpp"
#include "messages/message.hpp"
#include "singletons/settingsmanager.hpp" #include "singletons/settingsmanager.hpp"
#include "util/signalvector2.hpp" #include "util/signalvector2.hpp"
@ -21,6 +22,8 @@ public:
HighlightModel *createModel(QObject *parent); HighlightModel *createModel(QObject *parent);
void addHighlight(const messages::MessagePtr &msg);
private: private:
bool initialized = false; bool initialized = false;

View file

@ -15,7 +15,8 @@ HighlightModel::HighlightModel(QObject *parent)
} }
// turn a vector item into a model row // turn a vector item into a model row
HighlightPhrase HighlightModel::getItemFromRow(std::vector<QStandardItem *> &row) HighlightPhrase HighlightModel::getItemFromRow(std::vector<QStandardItem *> &row,
const HighlightPhrase &original)
{ {
// key, alert, sound, regex // key, alert, sound, regex

View file

@ -17,7 +17,8 @@ class HighlightModel : public util::SignalVectorModel<HighlightPhrase>
protected: protected:
// turn a vector item into a model row // turn a vector item into a model row
virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row) override; virtual HighlightPhrase getItemFromRow(std::vector<QStandardItem *> &row,
const HighlightPhrase &original) override;
// turns a row in the model into a vector item // turns a row in the model into a vector item
virtual void getRowFromItem(const HighlightPhrase &item, virtual void getRowFromItem(const HighlightPhrase &item,

View file

@ -15,7 +15,8 @@ IgnoreModel::IgnoreModel(QObject *parent)
} }
// turn a vector item into a model row // turn a vector item into a model row
IgnorePhrase IgnoreModel::getItemFromRow(std::vector<QStandardItem *> &row) IgnorePhrase IgnoreModel::getItemFromRow(std::vector<QStandardItem *> &row,
const IgnorePhrase &original)
{ {
// key, regex // key, regex

View file

@ -17,7 +17,8 @@ class IgnoreModel : public util::SignalVectorModel<IgnorePhrase>
protected: protected:
// turn a vector item into a model row // turn a vector item into a model row
virtual IgnorePhrase getItemFromRow(std::vector<QStandardItem *> &row) override; virtual IgnorePhrase getItemFromRow(std::vector<QStandardItem *> &row,
const IgnorePhrase &original) override;
// turns a row in the model into a vector item // turns a row in the model into a vector item
virtual void getRowFromItem(const IgnorePhrase &item, virtual void getRowFromItem(const IgnorePhrase &item,

View file

@ -0,0 +1,24 @@
#include "taggeduser.hpp"
#include <tuple>
namespace chatterino {
namespace controllers {
namespace taggedusers {
TaggedUser::TaggedUser(ProviderId _provider, const QString &_name, const QString &_id)
: provider(_provider)
, name(_name)
, id(_id)
{
}
bool TaggedUser::operator<(const TaggedUser &other) const
{
return std::tie(this->provider, this->name, this->id) <
std::tie(other.provider, other.name, other.id);
}
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino

View file

@ -0,0 +1,24 @@
#pragma once
#include <QString>
#include <providerid.hpp>
namespace chatterino {
namespace controllers {
namespace taggedusers {
class TaggedUser
{
public:
TaggedUser(ProviderId provider, const QString &name, const QString &id);
bool operator<(const TaggedUser &other) const;
ProviderId provider;
QString name;
QString id;
};
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino

View file

@ -0,0 +1,23 @@
#include "taggeduserscontroller.hpp"
#include "controllers/taggedusers/taggedusersmodel.hpp"
namespace chatterino {
namespace controllers {
namespace taggedusers {
TaggedUsersController::TaggedUsersController()
{
}
TaggedUsersModel *TaggedUsersController::createModel(QObject *parent)
{
TaggedUsersModel *model = new TaggedUsersModel(parent);
model->init(&this->users);
return model;
}
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino

View file

@ -0,0 +1,24 @@
#pragma once
#include "controllers/taggedusers/taggeduser.hpp"
#include "util/signalvector2.hpp"
namespace chatterino {
namespace controllers {
namespace taggedusers {
class TaggedUsersModel;
class TaggedUsersController
{
public:
TaggedUsersController();
util::SortedSignalVector<TaggedUser, std::less<TaggedUser>> users;
TaggedUsersModel *createModel(QObject *parent = nullptr);
};
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino

View file

@ -0,0 +1,66 @@
#include "taggedusersmodel.hpp"
#include "application.hpp"
#include "util/standarditemhelper.hpp"
namespace chatterino {
namespace controllers {
namespace taggedusers {
// commandmodel
TaggedUsersModel::TaggedUsersModel(QObject *parent)
: util::SignalVectorModel<TaggedUser>(1, parent)
{
}
// turn a vector item into a model row
TaggedUser TaggedUsersModel::getItemFromRow(std::vector<QStandardItem *> &row,
const TaggedUser &original)
{
return original;
}
// turns a row in the model into a vector item
void TaggedUsersModel::getRowFromItem(const TaggedUser &item, std::vector<QStandardItem *> &row)
{
util::setStringItem(row[0], item.name);
}
void TaggedUsersModel::afterInit()
{
// std::vector<QStandardItem *> row = this->createRow();
// util::setBoolItem(row[0], getApp()->settings->enableHighlightsSelf.getValue(), true,
// false); row[0]->setData("Your username (automatic)", Qt::DisplayRole);
// util::setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(), true,
// false); util::setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(),
// true, false); row[3]->setFlags(0); this->insertCustomRow(row, 0);
}
// void TaggedUserModel::customRowSetData(const std::vector<QStandardItem *> &row, int column,
// const QVariant &value, int role)
//{
// switch (column) {
// case 0: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightsSelf.setValue(value.toBool());
// }
// } break;
// case 1: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightTaskbar.setValue(value.toBool());
// }
// } break;
// case 2: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightSound.setValue(value.toBool());
// }
// } break;
// case 3: {
// // empty element
// } break;
// }
//}
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino

View file

@ -0,0 +1,34 @@
#pragma once
#include "controllers/taggedusers/taggeduser.hpp"
#include "util/signalvectormodel.hpp"
namespace chatterino {
namespace controllers {
namespace taggedusers {
class TaggedUsersController;
class TaggedUsersModel : public util::SignalVectorModel<TaggedUser>
{
explicit TaggedUsersModel(QObject *parent);
protected:
// turn a vector item into a model row
virtual TaggedUser getItemFromRow(std::vector<QStandardItem *> &row,
const TaggedUser &original) override;
// turns a row in the model into a vector item
virtual void getRowFromItem(const TaggedUser &item, std::vector<QStandardItem *> &row) override;
virtual void afterInit() override;
// virtual void customRowSetData(const std::vector<QStandardItem *> &row, int column,
// const QVariant &value, int role) override;
friend class TaggedUsersController;
};
} // namespace taggedusers
} // namespace controllers
} // namespace chatterino

View file

@ -1,6 +1,7 @@
#include "application.hpp" #include "application.hpp"
#include "singletons/nativemessagingmanager.hpp" #include "singletons/nativemessagingmanager.hpp"
#include "singletons/pathmanager.hpp" #include "singletons/pathmanager.hpp"
#include "singletons/updatemanager.hpp"
#include "util/networkmanager.hpp" #include "util/networkmanager.hpp"
#include "widgets/lastruncrashdialog.hpp" #include "widgets/lastruncrashdialog.hpp"
@ -9,6 +10,7 @@
#include <QFile> #include <QFile>
#include <QLibrary> #include <QLibrary>
#include <QStringList> #include <QStringList>
#include <QStyleFactory>
#ifdef USEWINSDK #ifdef USEWINSDK
#include "util/nativeeventhelper.hpp" #include "util/nativeeventhelper.hpp"
@ -17,7 +19,15 @@
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#ifdef Q_OS_WIN
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#endif
int runGui(int argc, char *argv[]); int runGui(int argc, char *argv[]);
void runNativeMessagingHost();
void installCustomPalette();
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
@ -30,7 +40,7 @@ int main(int argc, char *argv[])
// TODO: can be any argument // TODO: can be any argument
if (args.size() > 0 && args[0].startsWith("chrome-extension://")) { if (args.size() > 0 && args[0].startsWith("chrome-extension://")) {
chatterino::Application::runNativeMessagingHost(); runNativeMessagingHost();
return 0; return 0;
} }
@ -47,14 +57,16 @@ int runGui(int argc, char *argv[])
// QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL, true); // QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL, true);
QApplication a(argc, argv); QApplication a(argc, argv);
// Install native event handler for hidpi on windows QApplication::setStyle(QStyleFactory::create("Fusion"));
#ifdef USEWINSDK
a.installNativeEventFilter(new chatterino::util::DpiNativeEventFilter); installCustomPalette();
#endif
// Initialize NetworkManager // Initialize NetworkManager
chatterino::util::NetworkManager::init(); chatterino::util::NetworkManager::init();
// Check for upates
chatterino::singletons::UpdateManager::getInstance().checkForUpdates();
// Initialize application // Initialize application
chatterino::Application::instantiate(argc, argv); chatterino::Application::instantiate(argc, argv);
auto app = chatterino::getApp(); auto app = chatterino::getApp();
@ -68,7 +80,14 @@ int runGui(int argc, char *argv[])
if (QFile::exists(runningPath)) { if (QFile::exists(runningPath)) {
#ifndef DISABLE_CRASH_DIALOG #ifndef DISABLE_CRASH_DIALOG
chatterino::widgets::LastRunCrashDialog dialog; chatterino::widgets::LastRunCrashDialog dialog;
dialog.exec();
switch (dialog.exec()) {
case QDialog::Accepted: {
}; break;
default: {
_exit(0);
}
}
#endif #endif
} else { } else {
QFile runningFile(runningPath); QFile runningFile(runningPath);
@ -98,3 +117,86 @@ int runGui(int argc, char *argv[])
_exit(0); _exit(0);
} }
void runNativeMessagingHost()
{
auto *nm = new chatterino::singletons::NativeMessagingManager;
#ifdef Q_OS_WIN
_setmode(_fileno(stdin), _O_BINARY);
_setmode(_fileno(stdout), _O_BINARY);
#endif
#if 0
bool bigEndian = isBigEndian();
#endif
std::atomic<bool> ping(false);
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&ping] {
if (!ping.exchange(false)) {
_exit(0);
}
});
timer.setInterval(11000);
timer.start();
while (true) {
char size_c[4];
std::cin.read(size_c, 4);
if (std::cin.eof()) {
break;
}
uint32_t size = *reinterpret_cast<uint32_t *>(size_c);
#if 0
// To avoid breaking strict-aliasing rules and potentially inducing undefined behaviour, the following code can be run instead
uint32_t size = 0;
if (bigEndian) {
size = size_c[3] | static_cast<uint32_t>(size_c[2]) << 8 |
static_cast<uint32_t>(size_c[1]) << 16 | static_cast<uint32_t>(size_c[0]) << 24;
} else {
size = size_c[0] | static_cast<uint32_t>(size_c[1]) << 8 |
static_cast<uint32_t>(size_c[2]) << 16 | static_cast<uint32_t>(size_c[3]) << 24;
}
#endif
std::unique_ptr<char[]> b(new char[size + 1]);
std::cin.read(b.get(), size);
*(b.get() + size) = '\0';
nm->sendToGuiProcess(QByteArray::fromRawData(b.get(), static_cast<int32_t>(size)));
}
}
void installCustomPalette()
{
// borrowed from
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
QPalette darkPalette = qApp->palette();
darkPalette.setColor(QPalette::Window, QColor(22, 22, 22));
darkPalette.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127));
darkPalette.setColor(QPalette::Base, QColor("#333"));
darkPalette.setColor(QPalette::AlternateBase, QColor("#444"));
darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127));
darkPalette.setColor(QPalette::Dark, QColor(35, 35, 35));
darkPalette.setColor(QPalette::Shadow, QColor(20, 20, 20));
darkPalette.setColor(QPalette::Button, QColor(70, 70, 70));
darkPalette.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127));
darkPalette.setColor(QPalette::BrightText, Qt::red);
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80));
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127));
qApp->setPalette(darkPalette);
}

View file

@ -147,10 +147,10 @@ void Image::loadImage()
loadedEventQueued = true; loadedEventQueued = true;
QTimer::singleShot(500, [] { QTimer::singleShot(500, [] {
getApp()->emotes->incGeneration(); getApp()->windows->incGeneration();
auto app = getApp(); auto app = getApp();
app->windows->layoutVisibleChatWidgets(); app->windows->layoutChannelViews();
loadedEventQueued = false; loadedEventQueued = false;
}); });
} }
@ -240,7 +240,8 @@ int Image::getWidth() const
int Image::getScaledWidth() const int Image::getScaledWidth() const
{ {
return static_cast<int>(this->getWidth() * this->scale); return static_cast<int>((float)this->getWidth() * this->scale *
getApp()->settings->emoteScale.getValue());
} }
int Image::getHeight() const int Image::getHeight() const
@ -253,7 +254,8 @@ int Image::getHeight() const
int Image::getScaledHeight() const int Image::getScaledHeight() const
{ {
return static_cast<int>(this->getHeight() * this->scale); return static_cast<int>((float)this->getHeight() * this->scale *
getApp()->settings->emoteScale.getValue());
} }
} // namespace messages } // namespace messages

View file

@ -3,6 +3,8 @@
#include "application.hpp" #include "application.hpp"
#include "singletons/emotemanager.hpp" #include "singletons/emotemanager.hpp"
#include "singletons/settingsmanager.hpp" #include "singletons/settingsmanager.hpp"
#include "singletons/windowmanager.hpp"
#include "util/benchmark.hpp"
#include <QApplication> #include <QApplication>
#include <QDebug> #include <QDebug>
@ -20,9 +22,9 @@ namespace chatterino {
namespace messages { namespace messages {
namespace layouts { namespace layouts {
MessageLayout::MessageLayout(MessagePtr _message) MessageLayout::MessageLayout(MessagePtr message)
: message(_message) : message_(message)
, buffer(nullptr) , buffer_(nullptr)
{ {
util::DebugCount::increase("message layout"); util::DebugCount::increase("message layout");
} }
@ -34,119 +36,116 @@ MessageLayout::~MessageLayout()
Message *MessageLayout::getMessage() Message *MessageLayout::getMessage()
{ {
return this->message.get(); return this->message_.get();
} }
// Height // Height
int MessageLayout::getHeight() const int MessageLayout::getHeight() const
{ {
return container.getHeight(); return container_.getHeight();
} }
// Layout // Layout
// return true if redraw is required // return true if redraw is required
bool MessageLayout::layout(int width, float scale, MessageElement::Flags flags) bool MessageLayout::layout(int width, float scale, MessageElement::Flags flags)
{ {
// BenchmarkGuard benchmark("MessageLayout::layout()");
auto app = getApp(); auto app = getApp();
bool layoutRequired = false; bool layoutRequired = false;
// check if width changed // check if width changed
bool widthChanged = width != this->currentLayoutWidth; bool widthChanged = width != this->currentLayoutWidth_;
layoutRequired |= widthChanged; layoutRequired |= widthChanged;
this->currentLayoutWidth = width; this->currentLayoutWidth_ = width;
// check if emotes changed // check if layout state changed
bool imagesChanged = this->emoteGeneration != app->emotes->getGeneration(); if (this->layoutState_ != app->windows->getGeneration()) {
layoutRequired |= imagesChanged; layoutRequired = true;
this->emoteGeneration = app->emotes->getGeneration(); this->flags |= RequiresBufferUpdate;
this->layoutState_ = app->windows->getGeneration();
// check if text changed }
bool textChanged = this->fontGeneration != app->fonts->getGeneration();
layoutRequired |= textChanged;
this->fontGeneration = app->fonts->getGeneration();
// check if work mask changed // check if work mask changed
bool wordMaskChanged = this->currentWordFlags != flags; // app->settings->getWordTypeMask(); layoutRequired |= this->currentWordFlags_ != flags;
layoutRequired |= wordMaskChanged; this->currentWordFlags_ = flags; // app->settings->getWordTypeMask();
this->currentWordFlags = flags; // app->settings->getWordTypeMask();
// check if timestamp format changed // check if layout was requested manually
bool timestampFormatChanged = this->timestampFormat != app->settings->timestampFormat; layoutRequired |= bool(this->flags & RequiresLayout);
this->flags &= decltype(RequiresLayout)(~RequiresLayout);
layoutRequired |= timestampFormatChanged;
// check if dpi changed // check if dpi changed
bool scaleChanged = this->scale != scale; layoutRequired |= this->scale_ != scale;
layoutRequired |= scaleChanged; this->scale_ = scale;
this->scale = scale;
imagesChanged |= scaleChanged;
textChanged |= scaleChanged;
// update word sizes if needed
if (imagesChanged) {
// this->container.updateImages();
this->flags |= MessageLayout::RequiresBufferUpdate;
}
if (textChanged) {
// this->container.updateText();
this->flags |= MessageLayout::RequiresBufferUpdate;
}
if (widthChanged || wordMaskChanged) {
this->deleteBuffer();
}
// return if no layout is required
if (!layoutRequired) { if (!layoutRequired) {
return false; return false;
} }
int oldHeight = this->container_.getHeight();
this->actuallyLayout(width, flags); this->actuallyLayout(width, flags);
if (widthChanged || this->container_.getHeight() != oldHeight) {
this->deleteBuffer();
}
this->invalidateBuffer(); this->invalidateBuffer();
return true; return true;
} }
void MessageLayout::actuallyLayout(int width, MessageElement::Flags flags) void MessageLayout::actuallyLayout(int width, MessageElement::Flags _flags)
{ {
this->container.begin(width, this->scale, this->message->flags.value); auto messageFlags = this->message_->flags.value;
for (const std::unique_ptr<MessageElement> &element : this->message->getElements()) { if (this->flags & MessageLayout::Expanded ||
element->addToContainer(this->container, flags); (_flags & MessageElement::ModeratorTools &&
!(this->message_->flags & Message::MessageFlags::Disabled))) {
messageFlags = Message::MessageFlags(messageFlags & ~Message::MessageFlags::Collapsed);
} }
if (this->height != this->container.getHeight()) { this->container_.begin(width, this->scale_, messageFlags);
for (const std::unique_ptr<MessageElement> &element : this->message_->getElements()) {
element->addToContainer(this->container_, _flags);
}
if (this->height_ != this->container_.getHeight()) {
this->deleteBuffer(); this->deleteBuffer();
} }
this->container.end(); this->container_.end();
this->height = this->container.getHeight(); this->height_ = this->container_.getHeight();
// collapsed state
this->flags &= ~Flags::Collapsed;
if (this->container_.isCollapsed()) {
this->flags |= Flags::Collapsed;
}
} }
// Painting // Painting
void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection &selection, void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
bool isLastReadMessage, bool isWindowFocused) Selection &selection, bool isLastReadMessage, bool isWindowFocused)
{ {
auto app = getApp(); auto app = getApp();
QPixmap *pixmap = this->buffer.get(); QPixmap *pixmap = this->buffer_.get();
// create new buffer if required // create new buffer if required
if (!pixmap) { if (!pixmap) {
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
pixmap = pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
new QPixmap((int)(this->container.getWidth() * painter.device()->devicePixelRatioF()), int(container_.getHeight() * painter.device()->devicePixelRatioF()));
(int)(this->container.getHeight() * painter.device()->devicePixelRatioF()));
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF()); pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
#else #else
pixmap = new QPixmap(this->container.getWidth(), std::max(16, this->container.getHeight())); pixmap = new QPixmap(width, std::max(16, this->container_.getHeight()));
#endif #endif
this->buffer = std::shared_ptr<QPixmap>(pixmap); this->buffer_ = std::shared_ptr<QPixmap>(pixmap);
this->bufferValid = false; this->bufferValid_ = false;
util::DebugCount::increase("message drawing buffers"); util::DebugCount::increase("message drawing buffers");
} }
if (!this->bufferValid || !selection.isEmpty()) { if (!this->bufferValid_ || !selection.isEmpty()) {
this->updateBuffer(pixmap, messageIndex, selection); this->updateBuffer(pixmap, messageIndex, selection);
} }
@ -155,21 +154,21 @@ void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection
// painter.drawPixmap(0, y, this->container.width, this->container.getHeight(), *pixmap); // painter.drawPixmap(0, y, this->container.width, this->container.getHeight(), *pixmap);
// draw gif emotes // draw gif emotes
this->container.paintAnimatedElements(painter, y); this->container_.paintAnimatedElements(painter, y);
// draw disabled // draw disabled
if (this->message->flags.HasFlag(Message::Disabled)) { if (this->message_->flags.HasFlag(Message::Disabled)) {
painter.fillRect(0, y, pixmap->width(), pixmap->height(), app->themes->messages.disabled); painter.fillRect(0, y, pixmap->width(), pixmap->height(), app->themes->messages.disabled);
} }
// draw selection // draw selection
if (!selection.isEmpty()) { if (!selection.isEmpty()) {
this->container.paintSelection(painter, messageIndex, selection, y); this->container_.paintSelection(painter, messageIndex, selection, y);
} }
// draw message seperation line // draw message seperation line
if (app->settings->seperateMessages.getValue()) { if (app->settings->seperateMessages.getValue()) {
painter.fillRect(0, y + this->container.getHeight() - 1, this->container.getWidth(), 1, painter.fillRect(0, y, this->container_.getWidth(), 1,
app->themes->splits.messageSeperator); app->themes->splits.messageSeperator);
} }
@ -180,14 +179,13 @@ void MessageLayout::paint(QPainter &painter, int y, int messageIndex, Selection
QBrush brush(color, Qt::VerPattern); QBrush brush(color, Qt::VerPattern);
painter.fillRect(0, y + this->container.getHeight() - 1, this->container.getWidth(), 1, painter.fillRect(0, y + this->container_.getHeight() - 1, pixmap->width(), 1, brush);
brush);
} }
this->bufferValid = true; this->bufferValid_ = true;
} }
void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &selection) void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/, Selection & /*selection*/)
{ {
auto app = getApp(); auto app = getApp();
@ -197,8 +195,10 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &s
// draw background // draw background
QColor backgroundColor; QColor backgroundColor;
if (this->message->flags & Message::Highlighted) { if (this->message_->flags & Message::Highlighted) {
backgroundColor = app->themes->messages.backgrounds.highlighted; backgroundColor = app->themes->messages.backgrounds.highlighted;
} else if (this->message_->flags & Message::Subscription) {
backgroundColor = app->themes->messages.backgrounds.subscription;
} else if (app->settings->alternateMessageBackground.getValue() && } else if (app->settings->alternateMessageBackground.getValue() &&
this->flags & MessageLayout::AlternateBackground) { this->flags & MessageLayout::AlternateBackground) {
backgroundColor = app->themes->messages.backgrounds.alternate; backgroundColor = app->themes->messages.backgrounds.alternate;
@ -208,7 +208,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &s
painter.fillRect(buffer->rect(), backgroundColor); painter.fillRect(buffer->rect(), backgroundColor);
// draw message // draw message
this->container.paintElements(painter); this->container_.paintElements(painter);
#ifdef FOURTF #ifdef FOURTF
// debug // debug
@ -226,15 +226,15 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int messageIndex, Selection &s
void MessageLayout::invalidateBuffer() void MessageLayout::invalidateBuffer()
{ {
this->bufferValid = false; this->bufferValid_ = false;
} }
void MessageLayout::deleteBuffer() void MessageLayout::deleteBuffer()
{ {
if (this->buffer != nullptr) { if (this->buffer_ != nullptr) {
util::DebugCount::decrease("message drawing buffers"); util::DebugCount::decrease("message drawing buffers");
this->buffer = nullptr; this->buffer_ = nullptr;
} }
} }
@ -243,7 +243,7 @@ void MessageLayout::deleteCache()
this->deleteBuffer(); this->deleteBuffer();
#ifdef XD #ifdef XD
this->container.clear(); this->container_.clear();
#endif #endif
} }
@ -256,22 +256,22 @@ void MessageLayout::deleteCache()
const MessageLayoutElement *MessageLayout::getElementAt(QPoint point) const MessageLayoutElement *MessageLayout::getElementAt(QPoint point)
{ {
// go through all words and return the first one that contains the point. // go through all words and return the first one that contains the point.
return this->container.getElementAt(point); return this->container_.getElementAt(point);
} }
int MessageLayout::getLastCharacterIndex() const int MessageLayout::getLastCharacterIndex() const
{ {
return this->container.getLastCharacterIndex(); return this->container_.getLastCharacterIndex();
} }
int MessageLayout::getSelectionIndex(QPoint position) int MessageLayout::getSelectionIndex(QPoint position)
{ {
return this->container.getSelectionIndex(position); return this->container_.getSelectionIndex(position);
} }
void MessageLayout::addSelectionText(QString &str, int from, int to) void MessageLayout::addSelectionText(QString &str, int from, int to)
{ {
this->container.addSelectionText(str, from, to); this->container_.addSelectionText(str, from, to);
} }
} // namespace layouts } // namespace layouts

View file

@ -22,10 +22,12 @@ public:
enum Flags : uint8_t { enum Flags : uint8_t {
RequiresBufferUpdate = 1 << 1, RequiresBufferUpdate = 1 << 1,
RequiresLayout = 1 << 2, RequiresLayout = 1 << 2,
AlternateBackground = 1 << 3 AlternateBackground = 1 << 3,
Collapsed = 1 << 4,
Expanded = 1 << 5,
}; };
MessageLayout(MessagePtr message); MessageLayout(MessagePtr message_);
~MessageLayout(); ~MessageLayout();
Message *getMessage(); Message *getMessage();
@ -37,10 +39,10 @@ public:
util::FlagsEnum<Flags> flags; util::FlagsEnum<Flags> flags;
// Layout // Layout
bool layout(int width, float scale, MessageElement::Flags flags); bool layout(int width, float scale_, MessageElement::Flags flags);
// Painting // Painting
void paint(QPainter &painter, int y, int messageIndex, Selection &selection, void paint(QPainter &painter, int width, int y, int messageIndex, Selection &selection,
bool isLastReadMessage, bool isWindowFocused); bool isLastReadMessage, bool isWindowFocused);
void invalidateBuffer(); void invalidateBuffer();
void deleteBuffer(); void deleteBuffer();
@ -50,30 +52,28 @@ public:
const MessageLayoutElement *getElementAt(QPoint point); const MessageLayoutElement *getElementAt(QPoint point);
int getLastCharacterIndex() const; int getLastCharacterIndex() const;
int getSelectionIndex(QPoint position); int getSelectionIndex(QPoint position);
void addSelectionText(QString &str, int from, int to); void addSelectionText(QString &str, int from = 0, int to = INT_MAX);
// Misc // Misc
bool isDisabled() const; bool isDisabled() const;
private: private:
// variables // variables
MessagePtr message; MessagePtr message_;
MessageLayoutContainer container; MessageLayoutContainer container_;
std::shared_ptr<QPixmap> buffer = nullptr; std::shared_ptr<QPixmap> buffer_ = nullptr;
bool bufferValid = false; bool bufferValid_ = false;
int height = 0; int height_ = 0;
int currentLayoutWidth = -1; int currentLayoutWidth_ = -1;
int fontGeneration = -1; int layoutState_ = -1;
int emoteGeneration = -1; float scale_ = -1;
QString timestampFormat; unsigned int bufferUpdatedCount_ = 0;
float scale = -1;
unsigned int bufferUpdatedCount = 0;
MessageElement::Flags currentWordFlags = MessageElement::None; MessageElement::Flags currentWordFlags_ = MessageElement::None;
int collapsedHeight = 32; int collapsedHeight_ = 32;
// methods // methods
void actuallyLayout(int width, MessageElement::Flags flags); void actuallyLayout(int width, MessageElement::Flags flags);

View file

@ -9,6 +9,7 @@
#include <QPainter> #include <QPainter>
#define COMPACT_EMOTES_OFFSET 6 #define COMPACT_EMOTES_OFFSET 6
#define MAX_UNCOLLAPSED_LINES (getApp()->settings->collpseMessagesMinLines.getValue())
namespace chatterino { namespace chatterino {
namespace messages { namespace messages {
@ -36,6 +37,12 @@ void MessageLayoutContainer::begin(int _width, float _scale, Message::MessageFla
this->width = _width; this->width = _width;
this->scale = _scale; this->scale = _scale;
this->flags = _flags; this->flags = _flags;
auto mediumFontMetrics = getApp()->fonts->getFontMetrics(FontStyle::ChatMedium, _scale);
this->textLineHeight = mediumFontMetrics.height();
this->spaceWidth = mediumFontMetrics.width(' ');
this->dotdotdotWidth = mediumFontMetrics.width("...");
this->_canAddMessages = true;
this->_isCollapsed = false;
} }
void MessageLayoutContainer::clear() void MessageLayoutContainer::clear()
@ -68,12 +75,12 @@ void MessageLayoutContainer::addElementNoLineBreak(MessageLayoutElement *element
bool MessageLayoutContainer::canAddElements() bool MessageLayoutContainer::canAddElements()
{ {
return !(this->flags & Message::MessageFlags::Collapsed && line >= 3); return this->_canAddMessages;
} }
void MessageLayoutContainer::_addElement(MessageLayoutElement *element) void MessageLayoutContainer::_addElement(MessageLayoutElement *element, bool forceAdd)
{ {
if (!this->canAddElements()) { if (!this->canAddElements() && !forceAdd) {
delete element; delete element;
return; return;
} }
@ -129,6 +136,11 @@ void MessageLayoutContainer::breakLine()
yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale; yExtra = (COMPACT_EMOTES_OFFSET / 2) * this->scale;
} }
// if (element->getCreator().getFlags() & MessageElement::Badges) {
if (element->getRect().height() < this->textLineHeight) {
yExtra -= (this->textLineHeight - element->getRect().height()) / 2;
}
element->setPosition(QPoint(element->getRect().x() + xOffset + this->margin.left, element->setPosition(QPoint(element->getRect().x() + xOffset + this->margin.left,
element->getRect().y() + this->lineHeight + yExtra)); element->getRect().y() + this->lineHeight + yExtra));
} }
@ -146,6 +158,12 @@ void MessageLayoutContainer::breakLine()
this->lineStart = this->elements.size(); this->lineStart = this->elements.size();
// this->currentX = (int)(this->scale * 8); // this->currentX = (int)(this->scale * 8);
if (this->canCollapse() && line + 1 >= MAX_UNCOLLAPSED_LINES) {
this->_canAddMessages = false;
return;
}
this->currentX = 0; this->currentX = 0;
this->currentY += this->lineHeight; this->currentY += this->lineHeight;
this->height = this->currentY + (this->margin.bottom * this->scale); this->height = this->currentY + (this->margin.bottom * this->scale);
@ -160,15 +178,32 @@ bool MessageLayoutContainer::atStartOfLine()
bool MessageLayoutContainer::fitsInLine(int _width) bool MessageLayoutContainer::fitsInLine(int _width)
{ {
return this->currentX + _width <= this->width - this->margin.left - this->margin.right; return this->currentX + _width <=
(this->width - this->margin.left - this->margin.right -
(this->line + 1 == MAX_UNCOLLAPSED_LINES ? this->dotdotdotWidth : 0));
} }
void MessageLayoutContainer::end() void MessageLayoutContainer::end()
{ {
if (!this->canAddElements()) {
static TextElement dotdotdot("...", MessageElement::Collapsed, MessageColor::Link);
static QString dotdotdotText("...");
auto *element = new TextLayoutElement(
dotdotdot, dotdotdotText, QSize(this->dotdotdotWidth, this->textLineHeight),
QColor("#00D80A"), FontStyle::ChatMediumBold, this->scale);
// getApp()->themes->messages.textColors.system
this->_addElement(element, true);
this->_isCollapsed = true;
}
if (!this->atStartOfLine()) { if (!this->atStartOfLine()) {
this->breakLine(); this->breakLine();
} }
this->height += this->lineHeight;
if (this->lines.size() != 0) { if (this->lines.size() != 0) {
this->lines[0].rect.setTop(-100000); this->lines[0].rect.setTop(-100000);
this->lines.back().rect.setBottom(100000); this->lines.back().rect.setBottom(100000);
@ -177,6 +212,17 @@ void MessageLayoutContainer::end()
} }
} }
bool MessageLayoutContainer::canCollapse()
{
return getApp()->settings->collpseMessagesMinLines.getValue() > 0 &&
this->flags & Message::MessageFlags::Collapsed;
}
bool MessageLayoutContainer::isCollapsed()
{
return this->_isCollapsed;
}
MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point) MessageLayoutElement *MessageLayoutContainer::getElementAt(QPoint point)
{ {
for (std::unique_ptr<MessageLayoutElement> &element : this->elements) { for (std::unique_ptr<MessageLayoutElement> &element : this->elements) {
@ -429,8 +475,6 @@ void MessageLayoutContainer::addSelectionText(QString &str, int from, int to)
for (std::unique_ptr<MessageLayoutElement> &ele : this->elements) { for (std::unique_ptr<MessageLayoutElement> &ele : this->elements) {
int c = ele->getSelectionIndexCount(); int c = ele->getSelectionIndexCount();
qDebug() << c;
if (first) { if (first) {
if (index + c > from) { if (index + c > from) {
ele->addCopyTextToString(str, from - index, to - index); ele->addCopyTextToString(str, from - index, to - index);

View file

@ -76,6 +76,8 @@ struct MessageLayoutContainer {
int getLastCharacterIndex() const; int getLastCharacterIndex() const;
void addSelectionText(QString &str, int from, int to); void addSelectionText(QString &str, int from, int to);
bool isCollapsed();
private: private:
struct Line { struct Line {
int startIndex; int startIndex;
@ -86,7 +88,7 @@ private:
}; };
// helpers // helpers
void _addElement(MessageLayoutElement *element); void _addElement(MessageLayoutElement *element, bool forceAdd = false);
// variables // variables
float scale = 1.f; float scale = 1.f;
@ -100,6 +102,12 @@ private:
size_t lineStart = 0; size_t lineStart = 0;
int lineHeight = 0; int lineHeight = 0;
int spaceWidth = 4; int spaceWidth = 4;
int textLineHeight = 0;
int dotdotdotWidth = 0;
bool _canAddMessages = true;
bool _isCollapsed = false;
bool canCollapse();
std::vector<std::unique_ptr<MessageLayoutElement>> elements; std::vector<std::unique_ptr<MessageLayoutElement>> elements;
std::vector<Line> lines; std::vector<Line> lines;

View file

@ -188,7 +188,7 @@ int TextLayoutElement::getMouseOverIndex(const QPoint &abs)
auto app = getApp(); auto app = getApp();
QFontMetrics &metrics = app->fonts->getFontMetrics(this->style, this->scale); QFontMetrics metrics = app->fonts->getFontMetrics(this->style, this->scale);
int x = this->getRect().left(); int x = this->getRect().left();
@ -209,7 +209,7 @@ int TextLayoutElement::getXFromIndex(int index)
{ {
auto app = getApp(); auto app = getApp();
QFontMetrics &metrics = app->fonts->getFontMetrics(this->style, this->scale); QFontMetrics metrics = app->fonts->getFontMetrics(this->style, this->scale);
if (index <= 0) { if (index <= 0) {
return this->getRect().left(); return this->getRect().left();

View file

@ -21,6 +21,8 @@ SBHighlight Message::getScrollBarHighlight() const
{ {
if (this->flags & Message::Highlighted) { if (this->flags & Message::Highlighted) {
return SBHighlight(SBHighlight::Highlight); return SBHighlight(SBHighlight::Highlight);
} else if (this->flags & Message::Subscription) {
return SBHighlight(SBHighlight::Subscription);
} }
return SBHighlight(); return SBHighlight();
} }
@ -32,7 +34,19 @@ MessagePtr Message::createSystemMessage(const QString &text)
message->addElement(new TimestampElement(QTime::currentTime())); message->addElement(new TimestampElement(QTime::currentTime()));
message->addElement(new TextElement(text, MessageElement::Text, MessageColor::System)); message->addElement(new TextElement(text, MessageElement::Text, MessageColor::System));
message->flags.EnableFlag(MessageFlags::System); message->flags |= MessageFlags::System;
message->flags |= MessageFlags::DoNotTriggerNotification;
message->searchText = text;
return message;
}
MessagePtr Message::createMessage(const QString &text)
{
MessagePtr message(new Message);
message->addElement(new TimestampElement(QTime::currentTime()));
message->addElement(new TextElement(text, MessageElement::Text, MessageColor::Text));
message->searchText = text; message->searchText = text;
return message; return message;
@ -63,7 +77,7 @@ MessagePtr Message::createTimeoutMessage(const QString &username, const QString
if (reason.length() > 0) { if (reason.length() > 0) {
text.append(": \""); text.append(": \"");
text.append(util::ParseTagString(reason)); text.append(util::parseTagString(reason));
text.append("\""); text.append("\"");
} }
text.append("."); text.append(".");
@ -89,7 +103,6 @@ MessagePtr Message::createTimeoutMessage(const providers::twitch::BanAction &act
msg->timeoutUser = action.target.name; msg->timeoutUser = action.target.name;
msg->count = count; msg->count = count;
msg->banAction.reset(new providers::twitch::BanAction(action));
QString text; QString text;

View file

@ -18,6 +18,7 @@ namespace messages {
struct Message { struct Message {
Message() Message()
: parseTime(QTime::currentTime())
{ {
util::DebugCount::increase("messages"); util::DebugCount::increase("messages");
} }
@ -39,6 +40,8 @@ struct Message {
Collapsed = (1 << 7), Collapsed = (1 << 7),
DisconnectedMessage = (1 << 8), DisconnectedMessage = (1 << 8),
Untimeout = (1 << 9), Untimeout = (1 << 9),
PubSub = (1 << 10),
Subscription = (1 << 11),
}; };
util::FlagsEnum<MessageFlags> flags; util::FlagsEnum<MessageFlags> flags;
@ -50,7 +53,6 @@ struct Message {
QString localizedName; QString localizedName;
QString timeoutUser; QString timeoutUser;
std::unique_ptr<providers::twitch::BanAction> banAction;
uint32_t count = 1; uint32_t count = 1;
// Messages should not be added after the message is done initializing. // Messages should not be added after the message is done initializing.
@ -65,6 +67,7 @@ private:
public: public:
static std::shared_ptr<Message> createSystemMessage(const QString &text); static std::shared_ptr<Message> createSystemMessage(const QString &text);
static std::shared_ptr<Message> createMessage(const QString &text);
static std::shared_ptr<Message> createTimeoutMessage(const QString &username, static std::shared_ptr<Message> createTimeoutMessage(const QString &username,
const QString &durationInSeconds, const QString &durationInSeconds,

View file

@ -70,8 +70,8 @@ ImageElement::ImageElement(Image *_image, MessageElement::Flags flags)
void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags) void ImageElement::addToContainer(MessageLayoutContainer &container, MessageElement::Flags _flags)
{ {
if (_flags & this->getFlags()) { if (_flags & this->getFlags()) {
QSize size(this->image->getWidth() * this->image->getScale() * container.getScale(), QSize size(this->image->getScaledWidth() * container.getScale(),
this->image->getHeight() * this->image->getScale() * container.getScale()); this->image->getScaledHeight() * container.getScale());
container.addElement( container.addElement(
(new ImageLayoutElement(*this, this->image, size))->setLink(this->getLink())); (new ImageLayoutElement(*this, this->image, size))->setLink(this->getLink()));
@ -97,19 +97,10 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container, MessageElem
return; return;
} }
int quality = getApp()->settings->preferredEmoteQuality; Image *_image = this->data.getImage(container.getScale());
Image *_image; QSize size(int(container.getScale() * _image->getScaledWidth()),
if (quality == 3 && this->data.image3x != nullptr) { int(container.getScale() * _image->getScaledHeight()));
_image = this->data.image3x;
} else if (quality >= 2 && this->data.image2x != nullptr) {
_image = this->data.image2x;
} else {
_image = this->data.image1x;
}
QSize size((int)(container.getScale() * _image->getScaledWidth()),
(int)(container.getScale() * _image->getScaledHeight()));
container.addElement( container.addElement(
(new ImageLayoutElement(*this, _image, size))->setLink(this->getLink())); (new ImageLayoutElement(*this, _image, size))->setLink(this->getLink()));
@ -139,7 +130,7 @@ void TextElement::addToContainer(MessageLayoutContainer &container, MessageEleme
auto app = getApp(); auto app = getApp();
if (_flags & this->getFlags()) { if (_flags & this->getFlags()) {
QFontMetrics &metrics = app->fonts->getFontMetrics(this->style, container.getScale()); QFontMetrics metrics = app->fonts->getFontMetrics(this->style, container.getScale());
for (Word &word : this->words) { for (Word &word : this->words) {
auto getTextLayoutElement = [&](QString text, int width, bool trailingSpace) { auto getTextLayoutElement = [&](QString text, int width, bool trailingSpace) {
@ -242,7 +233,7 @@ TextElement *TimestampElement::formatTime(const QTime &time)
QString format = locale.toString(time, getApp()->settings->timestampFormat); QString format = locale.toString(time, getApp()->settings->timestampFormat);
return new TextElement(format, Flags::Timestamp, MessageColor::System, FontStyle::Medium); return new TextElement(format, Flags::Timestamp, MessageColor::System, FontStyle::ChatMedium);
} }
// TWITCH MODERATION // TWITCH MODERATION

View file

@ -158,7 +158,7 @@ class TextElement : public MessageElement
public: public:
TextElement(const QString &text, MessageElement::Flags flags, TextElement(const QString &text, MessageElement::Flags flags,
const MessageColor &color = MessageColor::Text, const MessageColor &color = MessageColor::Text,
FontStyle style = FontStyle::Medium); FontStyle style = FontStyle::ChatMedium);
~TextElement() override = default; ~TextElement() override = default;
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override; void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
@ -169,7 +169,6 @@ public:
// b) which size it wants // b) which size it wants
class EmoteElement : public MessageElement class EmoteElement : public MessageElement
{ {
const util::EmoteData data;
std::unique_ptr<TextElement> textElement; std::unique_ptr<TextElement> textElement;
public: public:
@ -177,6 +176,8 @@ public:
~EmoteElement() override = default; ~EmoteElement() override = default;
void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override; void addToContainer(MessageLayoutContainer &container, MessageElement::Flags flags) override;
const util::EmoteData data;
}; };
// contains a text, formated depending on the preferences // contains a text, formated depending on the preferences

View file

@ -7,6 +7,7 @@ struct MessageParseArgs {
bool disablePingSounds = false; bool disablePingSounds = false;
bool isReceivedWhisper = false; bool isReceivedWhisper = false;
bool isSentWhisper = false; bool isSentWhisper = false;
bool trimSubscriberUsername = false;
}; };
} // namespace messages } // namespace messages

View file

@ -42,6 +42,11 @@ struct SelectionItem {
{ {
return this->messageIndex == b.messageIndex && this->charIndex == b.charIndex; return this->messageIndex == b.messageIndex && this->charIndex == b.charIndex;
} }
bool operator!=(const SelectionItem &b) const
{
return this->operator==(b);
}
}; };
struct Selection { struct Selection {

5
src/providerid.hpp Normal file
View file

@ -0,0 +1,5 @@
#pragma once
namespace chatterino {
enum class ProviderId { Twitch };
}

View file

@ -4,6 +4,8 @@
#include "messages/limitedqueuesnapshot.hpp" #include "messages/limitedqueuesnapshot.hpp"
#include "messages/message.hpp" #include "messages/message.hpp"
#include <QCoreApplication>
using namespace chatterino::messages; using namespace chatterino::messages;
namespace chatterino { namespace chatterino {
@ -13,14 +15,14 @@ namespace irc {
AbstractIrcServer::AbstractIrcServer() AbstractIrcServer::AbstractIrcServer()
{ {
// Initialize the connections // Initialize the connections
this->writeConnection.reset(new Communi::IrcConnection); this->writeConnection.reset(new IrcConnection);
this->writeConnection->moveToThread(QCoreApplication::instance()->thread()); this->writeConnection->moveToThread(QCoreApplication::instance()->thread());
QObject::connect(this->writeConnection.get(), &Communi::IrcConnection::messageReceived, QObject::connect(this->writeConnection.get(), &Communi::IrcConnection::messageReceived,
[this](auto msg) { this->writeConnectionMessageReceived(msg); }); [this](auto msg) { this->writeConnectionMessageReceived(msg); });
// Listen to read connection message signals // Listen to read connection message signals
this->readConnection.reset(new Communi::IrcConnection); this->readConnection.reset(new IrcConnection);
this->readConnection->moveToThread(QCoreApplication::instance()->thread()); this->readConnection->moveToThread(QCoreApplication::instance()->thread());
QObject::connect(this->readConnection.get(), &Communi::IrcConnection::messageReceived, QObject::connect(this->readConnection.get(), &Communi::IrcConnection::messageReceived,
@ -31,9 +33,13 @@ AbstractIrcServer::AbstractIrcServer()
[this] { this->onConnected(); }); [this] { this->onConnected(); });
QObject::connect(this->readConnection.get(), &Communi::IrcConnection::disconnected, QObject::connect(this->readConnection.get(), &Communi::IrcConnection::disconnected,
[this] { this->onDisconnected(); }); [this] { this->onDisconnected(); });
// listen to reconnect request
this->readConnection->reconnectRequested.connect([this] { this->connect(); });
// this->writeConnection->reconnectRequested.connect([this] { this->connect(); });
} }
Communi::IrcConnection *AbstractIrcServer::getReadConnection() const IrcConnection *AbstractIrcServer::getReadConnection() const
{ {
return this->readConnection.get(); return this->readConnection.get();
} }
@ -231,7 +237,7 @@ void AbstractIrcServer::addFakeMessage(const QString &data)
{ {
auto fakeMessage = Communi::IrcMessage::fromData(data.toUtf8(), this->readConnection.get()); auto fakeMessage = Communi::IrcMessage::fromData(data.toUtf8(), this->readConnection.get());
this->privateMessageReceived(qobject_cast<Communi::IrcPrivateMessage *>(fakeMessage)); this->messageReceived(fakeMessage);
} }
void AbstractIrcServer::privateMessageReceived(Communi::IrcPrivateMessage *message) void AbstractIrcServer::privateMessageReceived(Communi::IrcPrivateMessage *message)

View file

@ -2,9 +2,9 @@
#include "channel.hpp" #include "channel.hpp"
#include <IrcConnection>
#include <IrcMessage> #include <IrcMessage>
#include <pajlada/signals/signal.hpp> #include <pajlada/signals/signal.hpp>
#include <providers/irc/ircconnection2.hpp>
#include <functional> #include <functional>
#include <mutex> #include <mutex>
@ -19,7 +19,7 @@ public:
virtual ~AbstractIrcServer() = default; virtual ~AbstractIrcServer() = default;
// connection // connection
Communi::IrcConnection *getReadConnection() const; IrcConnection *getReadConnection() const;
void connect(); void connect();
void disconnect(); void disconnect();
@ -33,7 +33,7 @@ public:
// signals // signals
pajlada::Signals::NoArgSignal connected; pajlada::Signals::NoArgSignal connected;
pajlada::Signals::NoArgSignal disconnected; pajlada::Signals::NoArgSignal disconnected;
pajlada::Signals::Signal<Communi::IrcPrivateMessage *> onPrivateMessage; // pajlada::Signals::Signal<Communi::IrcPrivateMessage *> onPrivateMessage;
void addFakeMessage(const QString &data); void addFakeMessage(const QString &data);
@ -43,8 +43,7 @@ public:
protected: protected:
AbstractIrcServer(); AbstractIrcServer();
virtual void initializeConnection(Communi::IrcConnection *connection, bool isRead, virtual void initializeConnection(IrcConnection *connection, bool isRead, bool isWrite) = 0;
bool isWrite) = 0;
virtual std::shared_ptr<Channel> createChannel(const QString &channelName) = 0; virtual std::shared_ptr<Channel> createChannel(const QString &channelName) = 0;
virtual void privateMessageReceived(Communi::IrcPrivateMessage *message); virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
@ -64,10 +63,12 @@ protected:
private: private:
void initConnection(); void initConnection();
std::unique_ptr<Communi::IrcConnection> writeConnection = nullptr; std::unique_ptr<IrcConnection> writeConnection = nullptr;
std::unique_ptr<Communi::IrcConnection> readConnection = nullptr; std::unique_ptr<IrcConnection> readConnection = nullptr;
std::mutex connectionMutex; std::mutex connectionMutex;
QTimer pingTimer;
}; };
} // namespace irc } // namespace irc

View file

@ -0,0 +1,36 @@
#include "ircconnection2.hpp"
namespace chatterino {
namespace providers {
namespace irc {
IrcConnection::IrcConnection(QObject *parent)
: Communi::IrcConnection(parent)
{
this->pingTimer_.setInterval(5000);
this->pingTimer_.start();
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
if (!this->recentlyReceivedMessage_.load()) {
this->sendRaw("PING");
this->reconnectTimer_.start();
}
this->recentlyReceivedMessage_ = false;
});
this->reconnectTimer_.setInterval(5000);
this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout,
[this] { reconnectRequested.invoke(); });
QObject::connect(this, &Communi::IrcConnection::messageReceived, [this](Communi::IrcMessage *) {
this->recentlyReceivedMessage_ = true;
if (this->reconnectTimer_.isActive()) {
this->reconnectTimer_.stop();
}
});
}
} // namespace irc
} // namespace providers
} // namespace chatterino

View file

@ -0,0 +1,27 @@
#pragma once
#include <pajlada/signals/signal.hpp>
#include <IrcConnection>
#include <QTimer>
namespace chatterino {
namespace providers {
namespace irc {
class IrcConnection : public Communi::IrcConnection
{
public:
IrcConnection(QObject *parent = nullptr);
pajlada::Signals::NoArgSignal reconnectRequested;
private:
QTimer pingTimer_;
QTimer reconnectTimer_;
std::atomic<bool> recentlyReceivedMessage_{true};
};
} // namespace irc
} // namespace providers
} // namespace chatterino

View file

@ -1,6 +1,7 @@
#include "ircmessagehandler.hpp" #include "ircmessagehandler.hpp"
#include "application.hpp" #include "application.hpp"
#include "controllers/highlights/highlightcontroller.hpp"
#include "debug/log.hpp" #include "debug/log.hpp"
#include "messages/limitedqueue.hpp" #include "messages/limitedqueue.hpp"
#include "messages/message.hpp" #include "messages/message.hpp"
@ -10,6 +11,9 @@
#include "providers/twitch/twitchserver.hpp" #include "providers/twitch/twitchserver.hpp"
#include "singletons/resourcemanager.hpp" #include "singletons/resourcemanager.hpp"
#include "singletons/windowmanager.hpp" #include "singletons/windowmanager.hpp"
#include "util/irchelpers.hpp"
#include <IrcMessage>
using namespace chatterino::singletons; using namespace chatterino::singletons;
using namespace chatterino::messages; using namespace chatterino::messages;
@ -24,116 +28,145 @@ IrcMessageHandler &IrcMessageHandler::getInstance()
return instance; return instance;
} }
void IrcMessageHandler::handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchServer &server)
{
this->addMessage(message, message->target(), message->content(), server, false);
}
void IrcMessageHandler::addMessage(Communi::IrcMessage *message, const QString &target,
const QString &content, TwitchServer &server, bool isSub)
{
QString channelName;
if (!trimChannelName(target, channelName)) {
return;
}
auto chan = server.getChannelOrEmpty(channelName);
if (chan->isEmpty()) {
return;
}
messages::MessageParseArgs args;
if (isSub) {
args.trimSubscriberUsername = true;
}
TwitchMessageBuilder builder(chan.get(), message, content, args);
if (isSub || !builder.isIgnored()) {
messages::MessagePtr msg = builder.build();
if (isSub) {
msg->flags |= messages::Message::Subscription;
msg->flags &= ~messages::Message::Highlighted;
} else {
if (msg->flags & messages::Message::Highlighted) {
server.mentionsChannel->addMessage(msg);
getApp()->highlights->addHighlight(msg);
}
}
chan->addMessage(msg);
}
}
void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message) void IrcMessageHandler::handleRoomStateMessage(Communi::IrcMessage *message)
{ {
const auto &tags = message->tags(); const auto &tags = message->tags();
auto iterator = tags.find("room-id"); auto app = getApp();
if (iterator != tags.end()) { // get twitch channel
auto roomID = iterator.value().toString(); QString chanName;
if (!trimChannelName(message->parameter(0), chanName)) {
return;
}
auto chan = app->twitch.server->getChannelOrEmpty(chanName);
TwitchChannel *twitchChannel = dynamic_cast<twitch::TwitchChannel *>(chan.get());
QStringList words = QString(message->toData()).split("#"); if (twitchChannel) {
// room-id
decltype(tags.find("xD")) it;
// ensure the format is valid if ((it = tags.find("room-id")) != tags.end()) {
if (words.length() < 2) { auto roomID = it.value().toString();
return;
}
auto app = getApp();
QString channelName = words.at(1);
auto channel = app->twitch.server->getChannelOrEmpty(channelName);
if (channel->isEmpty()) {
return;
}
if (auto twitchChannel = dynamic_cast<twitch::TwitchChannel *>(channel.get())) {
// set the room id of the channel
twitchChannel->setRoomID(roomID); twitchChannel->setRoomID(roomID);
app->resources->loadChannelData(roomID);
} }
app->resources->loadChannelData(roomID); // Room modes
TwitchChannel::RoomModes roomModes = twitchChannel->getRoomModes();
if ((it = tags.find("emote-only")) != tags.end()) {
roomModes.emoteOnly = it.value() == "1";
}
if ((it = tags.find("subs-only")) != tags.end()) {
roomModes.submode = it.value() == "1";
}
if ((it = tags.find("slow")) != tags.end()) {
roomModes.slowMode = it.value().toInt();
}
if ((it = tags.find("r9k")) != tags.end()) {
roomModes.r9k = it.value() == "1";
}
if ((it = tags.find("broadcaster-lang")) != tags.end()) {
roomModes.broadcasterLang = it.value().toString();
}
twitchChannel->setRoomModes(roomModes);
} }
} }
void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message) void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
{ {
return; // check parameter count
// // check parameter count if (message->parameters().length() < 1) {
// if (message->parameters().length() < 1) { return;
// return; }
// }
// QString chanName; QString chanName;
// if (!TrimChannelName(message->parameter(0), chanName)) { if (!trimChannelName(message->parameter(0), chanName)) {
// return; return;
// } }
// auto app = getApp(); auto app = getApp();
// // get channel // get channel
// auto chan = app->twitch.server->getChannelOrEmpty(chanName); auto chan = app->twitch.server->getChannelOrEmpty(chanName);
// if (chan->isEmpty()) { if (chan->isEmpty()) {
// debug::Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not found", debug::Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not found",
// chanName); chanName);
// return; return;
// } }
// // check if the chat has been cleared by a moderator // check if the chat has been cleared by a moderator
// if (message->parameters().length() == 1) { if (message->parameters().length() == 1) {
// chan->addMessage(Message::createSystemMessage("Chat has been cleared by a chan->addMessage(Message::createSystemMessage("Chat has been cleared by a moderator."));
// moderator."));
// return; return;
// } }
// // get username, duration and message of the timed out user // get username, duration and message of the timed out user
// QString username = message->parameter(1); QString username = message->parameter(1);
// QString durationInSeconds, reason; QString durationInSeconds, reason;
// QVariant v = message->tag("ban-duration"); QVariant v = message->tag("ban-duration");
// if (v.isValid()) { if (v.isValid()) {
// durationInSeconds = v.toString(); durationInSeconds = v.toString();
// } }
// v = message->tag("ban-reason"); v = message->tag("ban-reason");
// if (v.isValid()) { if (v.isValid()) {
// reason = v.toString(); reason = v.toString();
// } }
// // add the notice that the user has been timed out auto timeoutMsg = Message::createTimeoutMessage(username, durationInSeconds, reason, false);
// LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot(); chan->addOrReplaceTimeout(timeoutMsg);
// bool addMessage = true;
// int snapshotLength = snapshot.getLength();
// for (int i = std::max(0, snapshotLength - 20); i < snapshotLength; i++) { // refresh all
// auto &s = snapshot[i]; app->windows->repaintVisibleChatWidgets(chan.get());
// if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == username) {
// MessagePtr replacement(
// Message::createTimeoutMessage(username, durationInSeconds, reason, true));
// chan->replaceMessage(s, replacement);
// addMessage = false;
// break;
// }
// }
// if (addMessage) {
// chan->addMessage(Message::createTimeoutMessage(username, durationInSeconds, reason,
// false));
// }
// // disable the messages from the user
// for (int i = 0; i < snapshotLength; i++) {
// auto &s = snapshot[i];
// if (!(s->flags & Message::Timeout) && s->loginName == username) {
// s->flags.EnableFlag(Message::Disabled);
// }
// }
// // refresh all
// app->windows->repaintVisibleChatWidgets(chan.get());
} }
void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message) void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
@ -144,7 +177,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
auto app = getApp(); auto app = getApp();
QString channelName; QString channelName;
if (!TrimChannelName(message->parameter(0), channelName)) { if (!trimChannelName(message->parameter(0), channelName)) {
return; return;
} }
@ -180,6 +213,8 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
app->twitch.server->mentionsChannel->addMessage(_message); app->twitch.server->mentionsChannel->addMessage(_message);
} }
app->twitch.server->lastUserThatWhisperedMe.set(builder.userName);
c->addMessage(_message); c->addMessage(_message);
if (app->settings->inlineWhispers) { if (app->settings->inlineWhispers) {
@ -190,9 +225,51 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
} }
} }
void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message) void IrcMessageHandler::handleUserNoticeMessage(Communi::IrcMessage *message, TwitchServer &server)
{ {
// do nothing auto data = message->toData();
auto tags = message->tags();
auto parameters = message->parameters();
auto target = parameters[0];
QString msgType = tags.value("msg-id", "").toString();
QString content;
if (parameters.size() >= 2) {
content = parameters[1];
}
if (msgType == "sub" || msgType == "resub" || msgType == "subgift") {
// Sub-specific message. I think it's only allowed for "resub" messages atm
if (!content.isEmpty()) {
this->addMessage(message, target, content, server, true);
}
}
auto it = tags.find("system-msg");
if (it != tags.end()) {
auto newMessage =
messages::Message::createSystemMessage(util::parseTagString(it.value().toString()));
newMessage->flags |= messages::Message::Subscription;
QString channelName;
if (message->parameters().size() < 1) {
return;
}
if (!trimChannelName(message->parameter(0), channelName)) {
return;
}
auto chan = server.getChannelOrEmpty(channelName);
if (!chan->isEmpty()) {
chan->addMessage(newMessage);
}
}
} }
void IrcMessageHandler::handleModeMessage(Communi::IrcMessage *message) void IrcMessageHandler::handleModeMessage(Communi::IrcMessage *message)
@ -231,7 +308,8 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
// auto channel = app->twitch.server->getChannelOrEmpty(channelName); // auto channel = app->twitch.server->getChannelOrEmpty(channelName);
// if (channel->isEmpty()) { // if (channel->isEmpty()) {
// debug::Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel manager", // debug::Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel
// manager",
// channelName); // channelName);
// return; // return;
// } // }
@ -257,6 +335,26 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(Communi::IrcNoticeMes
this->handleNoticeMessage(message); this->handleNoticeMessage(message);
} }
void IrcMessageHandler::handleJoinMessage(Communi::IrcMessage *message)
{
auto app = getApp();
auto channel = app->twitch.server->getChannelOrEmpty(message->parameter(0).remove(0, 1));
if (TwitchChannel *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get())) {
twitchChannel->addJoinedUser(message->nick());
}
}
void IrcMessageHandler::handlePartMessage(Communi::IrcMessage *message)
{
auto app = getApp();
auto channel = app->twitch.server->getChannelOrEmpty(message->parameter(0).remove(0, 1));
if (TwitchChannel *twitchChannel = dynamic_cast<TwitchChannel *>(channel.get())) {
twitchChannel->addPartedUser(message->nick());
}
}
} // namespace twitch } // namespace twitch
} // namespace providers } // namespace providers
} // namespace chatterino } // namespace chatterino

View file

@ -6,6 +6,8 @@ namespace chatterino {
namespace providers { namespace providers {
namespace twitch { namespace twitch {
class TwitchServer;
class IrcMessageHandler class IrcMessageHandler
{ {
IrcMessageHandler() = default; IrcMessageHandler() = default;
@ -13,14 +15,23 @@ class IrcMessageHandler
public: public:
static IrcMessageHandler &getInstance(); static IrcMessageHandler &getInstance();
void handlePrivMessage(Communi::IrcPrivateMessage *message, TwitchServer &server);
void handleRoomStateMessage(Communi::IrcMessage *message); void handleRoomStateMessage(Communi::IrcMessage *message);
void handleClearChatMessage(Communi::IrcMessage *message); void handleClearChatMessage(Communi::IrcMessage *message);
void handleUserStateMessage(Communi::IrcMessage *message); void handleUserStateMessage(Communi::IrcMessage *message);
void handleWhisperMessage(Communi::IrcMessage *message); void handleWhisperMessage(Communi::IrcMessage *message);
void handleUserNoticeMessage(Communi::IrcMessage *message); void handleUserNoticeMessage(Communi::IrcMessage *message, TwitchServer &server);
void handleModeMessage(Communi::IrcMessage *message); void handleModeMessage(Communi::IrcMessage *message);
void handleNoticeMessage(Communi::IrcNoticeMessage *message); void handleNoticeMessage(Communi::IrcNoticeMessage *message);
void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message); void handleWriteConnectionNoticeMessage(Communi::IrcNoticeMessage *message);
void handleJoinMessage(Communi::IrcMessage *message);
void handlePartMessage(Communi::IrcMessage *message);
private:
void addMessage(Communi::IrcMessage *message, const QString &target, const QString &content,
TwitchServer &server, bool isResub);
}; };
} // namespace twitch } // namespace twitch

View file

@ -3,7 +3,6 @@
#include "debug/log.hpp" #include "debug/log.hpp"
#include "providers/twitch/pubsubactions.hpp" #include "providers/twitch/pubsubactions.hpp"
#include "providers/twitch/pubsubhelpers.hpp" #include "providers/twitch/pubsubhelpers.hpp"
#include "singletons/accountmanager.hpp"
#include "util/rapidjson-helpers.hpp" #include "util/rapidjson-helpers.hpp"
#include <rapidjson/error/en.h> #include <rapidjson/error/en.h>

View file

@ -1,7 +1,6 @@
#include "providers/twitch/pubsubhelpers.hpp" #include "providers/twitch/pubsubhelpers.hpp"
#include "providers/twitch/pubsubactions.hpp" #include "providers/twitch/pubsubactions.hpp"
#include "singletons/accountmanager.hpp"
#include "util/rapidjson-helpers.hpp" #include "util/rapidjson-helpers.hpp"
namespace chatterino { namespace chatterino {

View file

@ -12,7 +12,7 @@ namespace twitch {
TwitchAccount::TwitchAccount(const QString &_username, const QString &_oauthToken, TwitchAccount::TwitchAccount(const QString &_username, const QString &_oauthToken,
const QString &_oauthClient, const QString &_userID) const QString &_oauthClient, const QString &_userID)
: controllers::accounts::Account("Twitch") : controllers::accounts::Account(ProviderId::Twitch)
, oauthClient(_oauthClient) , oauthClient(_oauthClient)
, oauthToken(_oauthToken) , oauthToken(_oauthToken)
, userName(_username) , userName(_username)

View file

@ -55,7 +55,6 @@ public:
bool isAnon() const; bool isAnon() const;
void loadIgnores(); void loadIgnores();
void ignore(const QString &targetName, void ignore(const QString &targetName,
std::function<void(IgnoreResult, const QString &)> onFinished); std::function<void(IgnoreResult, const QString &)> onFinished);
void ignoreByID(const QString &targetUserID, const QString &targetName, void ignoreByID(const QString &targetUserID, const QString &targetName,

View file

@ -15,6 +15,9 @@ TwitchAccountManager::TwitchAccountManager()
auto currentUser = this->getCurrent(); auto currentUser = this->getCurrent();
currentUser->loadIgnores(); currentUser->loadIgnores();
}); });
this->accounts.itemRemoved.connect(
[this](const auto &acc) { this->removeUser(acc.item.get()); });
} }
std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent() std::shared_ptr<TwitchAccount> TwitchAccountManager::getCurrent()
@ -32,7 +35,7 @@ std::vector<QString> TwitchAccountManager::getUsernames() const
std::lock_guard<std::mutex> lock(this->mutex); std::lock_guard<std::mutex> lock(this->mutex);
for (const auto &user : this->users) { for (const auto &user : this->accounts.getVector()) {
userNames.push_back(user->getUserName()); userNames.push_back(user->getUserName());
} }
@ -44,7 +47,7 @@ std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
{ {
std::lock_guard<std::mutex> lock(this->mutex); std::lock_guard<std::mutex> lock(this->mutex);
for (const auto &user : this->users) { for (const auto &user : this->accounts.getVector()) {
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0) { if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0) {
return user; return user;
} }
@ -134,25 +137,16 @@ void TwitchAccountManager::load()
}); });
} }
bool TwitchAccountManager::removeUser(const QString &username) bool TwitchAccountManager::removeUser(TwitchAccount *account)
{ {
if (!this->userExists(username)) { const auto &accs = this->accounts.getVector();
return false;
std::string userID(account->getUserId().toStdString());
if (!userID.empty()) {
pajlada::Settings::SettingManager::removeSetting("/accounts/uid" + userID);
} }
this->mutex.lock(); if (account->getUserName() == qS(this->currentUsername.getValue())) {
this->users.erase(std::remove_if(this->users.begin(), this->users.end(), [username](auto user) {
if (user->getUserName() == username) {
std::string userID(user->getUserId().toStdString());
assert(!userID.empty());
pajlada::Settings::SettingManager::removeSetting("/accounts/uid" + userID);
return true;
}
return false;
}));
this->mutex.unlock();
if (username == qS(this->currentUsername.getValue())) {
// The user that was removed is the current user, log into the anonymous user // The user that was removed is the current user, log into the anonymous user
this->currentUsername = ""; this->currentUsername = "";
} }
@ -187,9 +181,9 @@ TwitchAccountManager::AddUserResponse TwitchAccountManager::addUser(
auto newUser = std::make_shared<TwitchAccount>(userData.username, userData.oauthToken, auto newUser = std::make_shared<TwitchAccount>(userData.username, userData.oauthToken,
userData.clientID, userData.userID); userData.clientID, userData.userID);
std::lock_guard<std::mutex> lock(this->mutex); // std::lock_guard<std::mutex> lock(this->mutex);
this->users.push_back(newUser); this->accounts.insertItem(newUser);
return AddUserResponse::UserAdded; return AddUserResponse::UserAdded;
} }

View file

@ -15,10 +15,11 @@
// //
namespace chatterino { namespace chatterino {
namespace controllers {
namespace singletons { namespace accounts {
class AccountManager; class AccountController;
} // namespace singletons }
} // namespace controllers
namespace providers { namespace providers {
namespace twitch { namespace twitch {
@ -46,8 +47,6 @@ public:
void reloadUsers(); void reloadUsers();
void load(); void load();
bool removeUser(const QString &username);
pajlada::Settings::Setting<std::string> currentUsername = {"/accounts/current", ""}; pajlada::Settings::Setting<std::string> currentUsername = {"/accounts/current", ""};
pajlada::Signals::NoArgSignal currentUserChanged; pajlada::Signals::NoArgSignal currentUserChanged;
pajlada::Signals::NoArgSignal userListUpdated; pajlada::Signals::NoArgSignal userListUpdated;
@ -63,14 +62,14 @@ private:
UserAdded, UserAdded,
}; };
AddUserResponse addUser(const UserData &data); AddUserResponse addUser(const UserData &data);
bool removeUser(TwitchAccount *account);
std::shared_ptr<TwitchAccount> currentUser; std::shared_ptr<TwitchAccount> currentUser;
std::shared_ptr<TwitchAccount> anonymousUser; std::shared_ptr<TwitchAccount> anonymousUser;
std::vector<std::shared_ptr<TwitchAccount>> users;
mutable std::mutex mutex; mutable std::mutex mutex;
friend class chatterino::singletons::AccountManager; friend class chatterino::controllers::accounts::AccountController;
}; };
} // namespace twitch } // namespace twitch

View file

@ -5,7 +5,6 @@
#include "messages/message.hpp" #include "messages/message.hpp"
#include "providers/twitch/pubsub.hpp" #include "providers/twitch/pubsub.hpp"
#include "providers/twitch/twitchmessagebuilder.hpp" #include "providers/twitch/twitchmessagebuilder.hpp"
#include "singletons/accountmanager.hpp"
#include "singletons/emotemanager.hpp" #include "singletons/emotemanager.hpp"
#include "singletons/ircmanager.hpp" #include "singletons/ircmanager.hpp"
#include "singletons/settingsmanager.hpp" #include "singletons/settingsmanager.hpp"
@ -45,7 +44,8 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
this->refreshLiveStatus(); // this->refreshLiveStatus(); //
}); });
this->managedConnect(app->accounts->Twitch.currentUserChanged, [this]() { this->setMod(false); }); this->managedConnect(app->accounts->twitch.currentUserChanged,
[this]() { this->setMod(false); });
auto refreshPubSubState = [=]() { auto refreshPubSubState = [=]() {
if (!this->hasModRights()) { if (!this->hasModRights()) {
@ -56,7 +56,7 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
return; return;
} }
auto account = app->accounts->Twitch.getCurrent(); auto account = app->accounts->twitch.getCurrent();
if (account && !account->getUserId().isEmpty()) { if (account && !account->getUserId().isEmpty()) {
app->twitch.pubsub->listenToChannelModerationActions(this->roomID, account); app->twitch.pubsub->listenToChannelModerationActions(this->roomID, account);
} }
@ -64,7 +64,7 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
this->userStateChanged.connect(refreshPubSubState); this->userStateChanged.connect(refreshPubSubState);
this->roomIDchanged.connect(refreshPubSubState); this->roomIDchanged.connect(refreshPubSubState);
this->managedConnect(app->accounts->Twitch.currentUserChanged, refreshPubSubState); this->managedConnect(app->accounts->twitch.currentUserChanged, refreshPubSubState);
refreshPubSubState(); refreshPubSubState();
this->fetchMessages.connect([this] { this->fetchMessages.connect([this] {
@ -85,7 +85,7 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
}; };
auto doRefreshChatters = [=]() { auto doRefreshChatters = [=]() {
const auto streamStatus = this->GetStreamStatus(); const auto streamStatus = this->getStreamStatus();
if (app->settings->onlyFetchChattersForSmallerStreamers) { if (app->settings->onlyFetchChattersForSmallerStreamers) {
if (streamStatus.live && streamStatus.viewerCount > app->settings->smallStreamerLimit) { if (streamStatus.live && streamStatus.viewerCount > app->settings->smallStreamerLimit) {
@ -102,6 +102,10 @@ TwitchChannel::TwitchChannel(const QString &channelName, Communi::IrcConnection
this->chattersListTimer = new QTimer; this->chattersListTimer = new QTimer;
QObject::connect(this->chattersListTimer, &QTimer::timeout, doRefreshChatters); QObject::connect(this->chattersListTimer, &QTimer::timeout, doRefreshChatters);
this->chattersListTimer->start(5 * 60 * 1000); this->chattersListTimer->start(5 * 60 * 1000);
// for (int i = 0; i < 1000; i++) {
// this->addMessage(messages::Message::createSystemMessage("asdf"));
// }
} }
TwitchChannel::~TwitchChannel() TwitchChannel::~TwitchChannel()
@ -186,7 +190,7 @@ bool TwitchChannel::isBroadcaster()
{ {
auto app = getApp(); auto app = getApp();
return this->name == app->accounts->Twitch.getCurrent()->getUserName(); return this->name == app->accounts->twitch.getCurrent()->getUserName();
} }
bool TwitchChannel::hasModRights() bool TwitchChannel::hasModRights()
@ -206,6 +210,93 @@ void TwitchChannel::addRecentChatter(const std::shared_ptr<messages::Message> &m
this->completionModel.addUser(message->displayName); this->completionModel.addUser(message->displayName);
} }
void TwitchChannel::addJoinedUser(const QString &user)
{
auto *app = getApp();
if (user == app->accounts->twitch.getCurrent()->getUserName() ||
!app->settings->showJoins.getValue()) {
return;
}
std::lock_guard<std::mutex> guard(this->joinedUserMutex);
joinedUsers << user;
if (!this->joinedUsersMergeQueued) {
this->joinedUsersMergeQueued = true;
QTimer::singleShot(500, &this->object, [this] {
std::lock_guard<std::mutex> guard(this->joinedUserMutex);
auto message = messages::Message::createSystemMessage("Users joined: " +
this->joinedUsers.join(", "));
message->flags |= messages::Message::Collapsed;
this->addMessage(message);
this->joinedUsers.clear();
this->joinedUsersMergeQueued = false;
});
}
}
void TwitchChannel::addPartedUser(const QString &user)
{
auto *app = getApp();
if (user == app->accounts->twitch.getCurrent()->getUserName() ||
!app->settings->showJoins.getValue()) {
return;
}
std::lock_guard<std::mutex> guard(this->partedUserMutex);
partedUsers << user;
if (!this->partedUsersMergeQueued) {
this->partedUsersMergeQueued = true;
QTimer::singleShot(500, &this->object, [this] {
std::lock_guard<std::mutex> guard(this->partedUserMutex);
auto message = messages::Message::createSystemMessage("Users parted: " +
this->partedUsers.join(", "));
message->flags |= messages::Message::Collapsed;
this->addMessage(message);
this->partedUsers.clear();
this->partedUsersMergeQueued = false;
});
}
}
TwitchChannel::RoomModes TwitchChannel::getRoomModes()
{
std::lock_guard<std::mutex> lock(this->roomModeMutex);
return this->roomModes;
}
void TwitchChannel::setRoomModes(const RoomModes &_roomModes)
{
{
std::lock_guard<std::mutex> lock(this->roomModeMutex);
this->roomModes = _roomModes;
}
this->roomModesChanged.invoke();
}
bool TwitchChannel::isLive() const
{
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
return this->streamStatus.live;
}
TwitchChannel::StreamStatus TwitchChannel::getStreamStatus() const
{
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
return this->streamStatus;
}
void TwitchChannel::setLive(bool newLiveStatus) void TwitchChannel::setLive(bool newLiveStatus)
{ {
bool gotNewLiveStatus = false; bool gotNewLiveStatus = false;
@ -289,6 +380,11 @@ void TwitchChannel::refreshLiveStatus()
QString::number(diff / 3600) + "h " + QString::number(diff % 3600 / 60) + "m"; QString::number(diff / 3600) + "h " + QString::number(diff % 3600 / 60) + "m";
channel->streamStatus.rerun = false; channel->streamStatus.rerun = false;
if (stream.HasMember("stream_type")) {
channel->streamStatus.streamType = stream["stream_type"].GetString();
} else {
channel->streamStatus.streamType = QString();
}
if (stream.HasMember("broadcast_platform")) { if (stream.HasMember("broadcast_platform")) {
const auto &broadcastPlatformValue = stream["broadcast_platform"]; const auto &broadcastPlatformValue = stream["broadcast_platform"];

View file

@ -7,6 +7,7 @@
#include "singletons/emotemanager.hpp" #include "singletons/emotemanager.hpp"
#include "singletons/ircmanager.hpp" #include "singletons/ircmanager.hpp"
#include "util/concurrentmap.hpp" #include "util/concurrentmap.hpp"
#include "util/mutexvalue.hpp"
#include <pajlada/signals/signalholder.hpp> #include <pajlada/signals/signalholder.hpp>
@ -31,6 +32,7 @@ public:
QString title; QString title;
QString game; QString game;
QString uptime; QString uptime;
QString streamType;
}; };
struct UserState { struct UserState {
@ -38,6 +40,15 @@ public:
bool broadcaster; bool broadcaster;
}; };
struct RoomModes {
bool submode = false;
bool r9k = false;
bool emoteOnly = false;
// int folowerOnly = 0;
int slowMode = 0;
QString broadcasterLang;
};
~TwitchChannel() final; ~TwitchChannel() final;
void reloadChannelEmotes(); void reloadChannelEmotes();
@ -52,6 +63,8 @@ public:
bool hasModRights(); bool hasModRights();
void addRecentChatter(const std::shared_ptr<messages::Message> &message) final; void addRecentChatter(const std::shared_ptr<messages::Message> &message) final;
void addJoinedUser(const QString &user);
void addPartedUser(const QString &user);
const std::shared_ptr<chatterino::util::EmoteMap> bttvChannelEmotes; const std::shared_ptr<chatterino::util::EmoteMap> bttvChannelEmotes;
const std::shared_ptr<chatterino::util::EmoteMap> ffzChannelEmotes; const std::shared_ptr<chatterino::util::EmoteMap> ffzChannelEmotes;
@ -66,25 +79,21 @@ public:
pajlada::Signals::NoArgBoltSignal fetchMessages; pajlada::Signals::NoArgBoltSignal fetchMessages;
pajlada::Signals::NoArgSignal userStateChanged; pajlada::Signals::NoArgSignal userStateChanged;
pajlada::Signals::NoArgSignal roomModesChanged;
QString roomID; QString roomID;
StreamStatus GetStreamStatus() const RoomModes getRoomModes();
{ void setRoomModes(const RoomModes &roomModes);
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
return this->streamStatus; StreamStatus getStreamStatus() const;
}
struct NameOptions { struct NameOptions {
QString displayName; QString displayName;
QString localizedName; QString localizedName;
}; };
bool IsLive() const bool isLive() const;
{
std::lock_guard<std::mutex> lock(this->streamStatusMutex);
return this->streamStatus.live;
}
private: private:
explicit TwitchChannel(const QString &channelName, Communi::IrcConnection *readConnection); explicit TwitchChannel(const QString &channelName, Communi::IrcConnection *readConnection);
@ -103,6 +112,16 @@ private:
bool mod; bool mod;
QByteArray messageSuffix; QByteArray messageSuffix;
QString lastSentMessage; QString lastSentMessage;
RoomModes roomModes;
std::mutex roomModeMutex;
QObject object;
std::mutex joinedUserMutex;
QStringList joinedUsers;
bool joinedUsersMergeQueued = false;
std::mutex partedUserMutex;
QStringList partedUsers;
bool partedUsersMergeQueued = false;
Communi::IrcConnection *readConnection; Communi::IrcConnection *readConnection;

View file

@ -5,7 +5,7 @@ namespace chatterino {
namespace providers { namespace providers {
namespace twitch { namespace twitch {
bool TrimChannelName(const QString &channelName, QString &outChannelName) bool trimChannelName(const QString &channelName, QString &outChannelName)
{ {
if (channelName.length() < 3) { if (channelName.length() < 3) {
debug::Log("channel name length below 3"); debug::Log("channel name length below 3");

View file

@ -6,7 +6,7 @@ namespace chatterino {
namespace providers { namespace providers {
namespace twitch { namespace twitch {
bool TrimChannelName(const QString &channelName, QString &outChannelName); bool trimChannelName(const QString &channelName, QString &outChannelName);
} // namespace twitch } // namespace twitch
} // namespace providers } // namespace providers

View file

@ -1,11 +1,11 @@
#include "providers/twitch/twitchmessagebuilder.hpp" #include "providers/twitch/twitchmessagebuilder.hpp"
#include "application.hpp" #include "application.hpp"
#include "controllers/accounts/accountcontroller.hpp"
#include "controllers/highlights/highlightcontroller.hpp" #include "controllers/highlights/highlightcontroller.hpp"
#include "controllers/ignores/ignorecontroller.hpp" #include "controllers/ignores/ignorecontroller.hpp"
#include "debug/log.hpp" #include "debug/log.hpp"
#include "providers/twitch/twitchchannel.hpp" #include "providers/twitch/twitchchannel.hpp"
#include "singletons/accountmanager.hpp"
#include "singletons/emotemanager.hpp" #include "singletons/emotemanager.hpp"
#include "singletons/ircmanager.hpp" #include "singletons/ircmanager.hpp"
#include "singletons/resourcemanager.hpp" #include "singletons/resourcemanager.hpp"
@ -68,7 +68,18 @@ bool TwitchMessageBuilder::isIgnored() const
if (app->settings->enableTwitchIgnoredUsers && this->tags.contains("user-id")) { if (app->settings->enableTwitchIgnoredUsers && this->tags.contains("user-id")) {
auto sourceUserID = this->tags.value("user-id").toString(); auto sourceUserID = this->tags.value("user-id").toString();
for (const auto &user : app->accounts->Twitch.getCurrent()->getIgnores()) { for (const auto &user : app->accounts->twitch.getCurrent()->getIgnores()) {
if (sourceUserID == user.id) {
debug::Log("Blocking message because it's from blocked user {}", user.name);
return true;
}
}
}
if (app->settings->enableTwitchIgnoredUsers && this->tags.contains("user-id")) {
auto sourceUserID = this->tags.value("user-id").toString();
for (const auto &user : app->accounts->twitch.getCurrent()->getIgnores()) {
if (sourceUserID == user.id) { if (sourceUserID == user.id) {
debug::Log("Blocking message because it's from blocked user {}", user.name); debug::Log("Blocking message because it's from blocked user {}", user.name);
return true; return true;
@ -86,12 +97,14 @@ MessagePtr TwitchMessageBuilder::build()
// PARSING // PARSING
this->parseUsername(); this->parseUsername();
#ifdef XD //#ifdef XD
if (this->originalMessage.length() > 100) { // if (this->originalMessage.length() > 100) {
this->message->flags |= Message::Collapsed; // this->message->flags |= Message::Collapsed;
this->emplace<EmoteElement>(getApp()->resources->badgeCollapsed, MessageElement::Collapsed); // this->emplace<EmoteElement>(getApp()->resources->badgeCollapsed,
} // MessageElement::Collapsed);
#endif // }
//#endif
this->message->flags |= Message::Collapsed;
// PARSING // PARSING
this->parseMessageID(); this->parseMessageID();
@ -141,15 +154,8 @@ MessagePtr TwitchMessageBuilder::build()
this->appendTwitchEmote(ircMessage, emote, twitchEmotes); this->appendTwitchEmote(ircMessage, emote, twitchEmotes);
} }
struct { std::sort(twitchEmotes.begin(), twitchEmotes.end(),
bool operator()(const std::pair<long, util::EmoteData> &lhs, [](const auto &a, const auto &b) { return a.first < b.first; });
const std::pair<long, util::EmoteData> &rhs)
{
return lhs.first < rhs.first;
}
} customLess;
std::sort(twitchEmotes.begin(), twitchEmotes.end(), customLess);
} }
auto currentTwitchEmote = twitchEmotes.begin(); auto currentTwitchEmote = twitchEmotes.begin();
@ -282,10 +288,16 @@ void TwitchMessageBuilder::parseUsername()
// username // username
this->userName = this->ircMessage->nick(); this->userName = this->ircMessage->nick();
if (this->userName.isEmpty()) { if (this->userName.isEmpty() || this->args.trimSubscriberUsername) {
this->userName = this->tags.value(QLatin1String("login")).toString(); this->userName = this->tags.value(QLatin1String("login")).toString();
} }
// display name
// auto displayNameVariant = this->tags.value("display-name");
// if (displayNameVariant.isValid()) {
// this->userName = displayNameVariant.toString() + " (" + this->userName + ")";
// }
this->message->loginName = this->userName; this->message->loginName = this->userName;
} }
@ -350,14 +362,14 @@ void TwitchMessageBuilder::appendUsername()
} else if (this->args.isReceivedWhisper) { } else if (this->args.isReceivedWhisper) {
// Sender username // Sender username
this->emplace<TextElement>(usernameText, MessageElement::Text, this->usernameColor, this->emplace<TextElement>(usernameText, MessageElement::Text, this->usernameColor,
FontStyle::MediumBold) FontStyle::ChatMediumBold)
->setLink({Link::UserInfo, this->userName}); ->setLink({Link::UserInfo, this->userName});
auto currentUser = app->accounts->Twitch.getCurrent(); auto currentUser = app->accounts->twitch.getCurrent();
// Separator // Separator
this->emplace<TextElement>("->", MessageElement::Text, this->emplace<TextElement>("->", MessageElement::Text,
app->themes->messages.textColors.system, FontStyle::Medium); app->themes->messages.textColors.system, FontStyle::ChatMedium);
QColor selfColor = currentUser->color; QColor selfColor = currentUser->color;
if (!selfColor.isValid()) { if (!selfColor.isValid()) {
@ -366,14 +378,14 @@ void TwitchMessageBuilder::appendUsername()
// Your own username // Your own username
this->emplace<TextElement>(currentUser->getUserName() + ":", MessageElement::Text, this->emplace<TextElement>(currentUser->getUserName() + ":", MessageElement::Text,
selfColor, FontStyle::MediumBold); selfColor, FontStyle::ChatMediumBold);
} else { } else {
if (!this->action) { if (!this->action) {
usernameText += ":"; usernameText += ":";
} }
this->emplace<TextElement>(usernameText, MessageElement::Text, this->usernameColor, this->emplace<TextElement>(usernameText, MessageElement::Text, this->usernameColor,
FontStyle::MediumBold) FontStyle::ChatMediumBold)
->setLink({Link::UserInfo, this->userName}); ->setLink({Link::UserInfo, this->userName});
} }
} }
@ -385,7 +397,7 @@ void TwitchMessageBuilder::parseHighlights()
auto app = getApp(); auto app = getApp();
auto currentUser = app->accounts->Twitch.getCurrent(); auto currentUser = app->accounts->twitch.getCurrent();
QString currentUsername = currentUser->getUserName(); QString currentUsername = currentUser->getUserName();
@ -571,11 +583,13 @@ void TwitchMessageBuilder::appendTwitchBadges()
QString cheerAmountQS = badge.mid(5); QString cheerAmountQS = badge.mid(5);
std::string versionKey = cheerAmountQS.toStdString(); std::string versionKey = cheerAmountQS.toStdString();
QString tooltip = QString("Twitch cheer ") + cheerAmountQS;
// Try to fetch channel-specific bit badge // Try to fetch channel-specific bit badge
try { try {
const auto &badge = channelResources.badgeSets.at("bits").versions.at(versionKey); const auto &badge = channelResources.badgeSets.at("bits").versions.at(versionKey);
this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity); this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity)
->setTooltip(tooltip);
continue; continue;
} catch (const std::out_of_range &) { } catch (const std::out_of_range &) {
// Channel does not contain a special bit badge for this version // Channel does not contain a special bit badge for this version
@ -584,7 +598,8 @@ void TwitchMessageBuilder::appendTwitchBadges()
// Use default bit badge // Use default bit badge
try { try {
const auto &badge = app->resources->badgeSets.at("bits").versions.at(versionKey); const auto &badge = app->resources->badgeSets.at("bits").versions.at(versionKey);
this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity); this->emplace<ImageElement>(badge.badgeImage1x, MessageElement::BadgeVanity)
->setTooltip(tooltip);
} catch (const std::out_of_range &) { } catch (const std::out_of_range &) {
debug::Log("No default bit badge for version {} found", versionKey); debug::Log("No default bit badge for version {} found", versionKey);
continue; continue;

View file

@ -1,16 +1,18 @@
#include "twitchserver.hpp" #include "twitchserver.hpp"
#include "application.hpp" #include "application.hpp"
#include "controllers/accounts/accountcontroller.hpp"
#include "controllers/highlights/highlightcontroller.hpp"
#include "providers/twitch/ircmessagehandler.hpp" #include "providers/twitch/ircmessagehandler.hpp"
#include "providers/twitch/twitchaccount.hpp" #include "providers/twitch/twitchaccount.hpp"
#include "providers/twitch/twitchhelpers.hpp" #include "providers/twitch/twitchhelpers.hpp"
#include "providers/twitch/twitchmessagebuilder.hpp" #include "providers/twitch/twitchmessagebuilder.hpp"
#include "singletons/accountmanager.hpp"
#include "util/posttothread.hpp" #include "util/posttothread.hpp"
#include <IrcCommand>
#include <cassert> #include <cassert>
using namespace Communi; // using namespace Communi;
using namespace chatterino::singletons; using namespace chatterino::singletons;
namespace chatterino { namespace chatterino {
@ -27,13 +29,14 @@ TwitchServer::TwitchServer()
void TwitchServer::initialize() void TwitchServer::initialize()
{ {
getApp()->accounts->Twitch.currentUserChanged.connect( getApp()->accounts->twitch.currentUserChanged.connect(
[this]() { util::postToThread([this] { this->connect(); }); }); [this]() { util::postToThread([this] { this->connect(); }); });
} }
void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead, bool isWrite) void TwitchServer::initializeConnection(providers::irc::IrcConnection *connection, bool isRead,
bool isWrite)
{ {
std::shared_ptr<TwitchAccount> account = getApp()->accounts->Twitch.getCurrent(); std::shared_ptr<TwitchAccount> account = getApp()->accounts->twitch.getCurrent();
qDebug() << "logging in as" << account->getUserName(); qDebug() << "logging in as" << account->getUserName();
@ -56,9 +59,9 @@ void TwitchServer::initializeConnection(IrcConnection *connection, bool isRead,
// this->refreshIgnoredUsers(username, oauthClient, oauthToken); // this->refreshIgnoredUsers(username, oauthClient, oauthToken);
} }
connection->sendCommand(IrcCommand::createCapability("REQ", "twitch.tv/membership")); connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/membership"));
connection->sendCommand(IrcCommand::createCapability("REQ", "twitch.tv/commands")); connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/commands"));
connection->sendCommand(IrcCommand::createCapability("REQ", "twitch.tv/tags")); connection->sendCommand(Communi::IrcCommand::createCapability("REQ", "twitch.tv/tags"));
connection->setHost("irc.chat.twitch.tv"); connection->setHost("irc.chat.twitch.tv");
connection->setPort(6667); connection->setPort(6667);
@ -74,69 +77,53 @@ std::shared_ptr<Channel> TwitchServer::createChannel(const QString &channelName)
return std::shared_ptr<Channel>(channel); return std::shared_ptr<Channel>(channel);
} }
void TwitchServer::privateMessageReceived(IrcPrivateMessage *message) void TwitchServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
{ {
QString channelName; IrcMessageHandler::getInstance().handlePrivMessage(message, *this);
if (!TrimChannelName(message->target(), channelName)) {
return;
}
this->onPrivateMessage.invoke(message);
auto chan = this->getChannelOrEmpty(channelName);
if (chan->isEmpty()) {
return;
}
messages::MessageParseArgs args;
TwitchMessageBuilder builder(chan.get(), message, args);
if (!builder.isIgnored()) {
messages::MessagePtr _message = builder.build();
if (_message->flags & messages::Message::Highlighted) {
this->mentionsChannel->addMessage(_message);
}
chan->addMessage(_message);
}
} }
void TwitchServer::messageReceived(IrcMessage *message) void TwitchServer::messageReceived(Communi::IrcMessage *message)
{ {
// this->readConnection // this->readConnection
if (message->type() == IrcMessage::Type::Private) { if (message->type() == Communi::IrcMessage::Type::Private) {
// We already have a handler for private messages // We already have a handler for private messages
return; return;
} }
const QString &command = message->command(); const QString &command = message->command();
auto &handler = IrcMessageHandler::getInstance();
if (command == "ROOMSTATE") { if (command == "ROOMSTATE") {
IrcMessageHandler::getInstance().handleRoomStateMessage(message); handler.handleRoomStateMessage(message);
} else if (command == "CLEARCHAT") { } else if (command == "CLEARCHAT") {
IrcMessageHandler::getInstance().handleClearChatMessage(message); handler.handleClearChatMessage(message);
} else if (command == "USERSTATE") { } else if (command == "USERSTATE") {
IrcMessageHandler::getInstance().handleUserStateMessage(message); handler.handleUserStateMessage(message);
} else if (command == "WHISPER") { } else if (command == "WHISPER") {
IrcMessageHandler::getInstance().handleWhisperMessage(message); handler.handleWhisperMessage(message);
} else if (command == "USERNOTICE") { } else if (command == "USERNOTICE") {
IrcMessageHandler::getInstance().handleUserNoticeMessage(message); handler.handleUserNoticeMessage(message, *this);
} else if (command == "MODE") { } else if (command == "MODE") {
IrcMessageHandler::getInstance().handleModeMessage(message); handler.handleModeMessage(message);
} else if (command == "NOTICE") { } else if (command == "NOTICE") {
IrcMessageHandler::getInstance().handleNoticeMessage( handler.handleNoticeMessage(static_cast<Communi::IrcNoticeMessage *>(message));
static_cast<IrcNoticeMessage *>(message)); } else if (command == "JOIN") {
handler.handleJoinMessage(message);
} else if (command == "PART") {
handler.handlePartMessage(message);
} }
} }
void TwitchServer::writeConnectionMessageReceived(IrcMessage *message) void TwitchServer::writeConnectionMessageReceived(Communi::IrcMessage *message)
{ {
switch (message->type()) { switch (message->type()) {
case IrcMessage::Type::Notice: { case Communi::IrcMessage::Type::Notice: {
IrcMessageHandler::getInstance().handleWriteConnectionNoticeMessage( IrcMessageHandler::getInstance().handleWriteConnectionNoticeMessage(
static_cast<IrcNoticeMessage *>(message)); static_cast<Communi::IrcNoticeMessage *>(message));
} break; } break;
default:;
} }
} }

View file

@ -3,6 +3,7 @@
#include "providers/irc/abstractircserver.hpp" #include "providers/irc/abstractircserver.hpp"
#include "providers/twitch/twitchaccount.hpp" #include "providers/twitch/twitchaccount.hpp"
#include "providers/twitch/twitchchannel.hpp" #include "providers/twitch/twitchchannel.hpp"
#include "util/mutexvalue.hpp"
#include <memory> #include <memory>
@ -22,12 +23,17 @@ public:
std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID); std::shared_ptr<Channel> getChannelOrEmptyByID(const QString &channelID);
util::MutexValue<QString> lastUserThatWhisperedMe;
// QString getLastWhisperedPerson() const;
// void setLastWhisperedPerson(const QString &person);
const ChannelPtr whispersChannel; const ChannelPtr whispersChannel;
const ChannelPtr mentionsChannel; const ChannelPtr mentionsChannel;
IndirectChannel watchingChannel; IndirectChannel watchingChannel;
protected: protected:
void initializeConnection(Communi::IrcConnection *connection, bool isRead, void initializeConnection(providers::irc::IrcConnection *connection, bool isRead,
bool isWrite) override; bool isWrite) override;
std::shared_ptr<Channel> createChannel(const QString &channelName) override; std::shared_ptr<Channel> createChannel(const QString &channelName) override;
@ -38,6 +44,10 @@ protected:
std::shared_ptr<Channel> getCustomChannel(const QString &channelname) override; std::shared_ptr<Channel> getCustomChannel(const QString &channelname) override;
QString cleanChannelName(const QString &dirtyChannelName) override; QString cleanChannelName(const QString &dirtyChannelName) override;
private:
// mutable std::mutex lastWhisperedPersonMutex;
// QString lastWhisperedPerson;
}; };
} // namespace twitch } // namespace twitch

View file

@ -1,12 +0,0 @@
#include "singletons/accountmanager.hpp"
namespace chatterino {
namespace singletons {
void AccountManager::load()
{
this->Twitch.load();
}
} // namespace singletons
} // namespace chatterino

View file

@ -1,21 +0,0 @@
#pragma once
#include "providers/twitch/twitchaccountmanager.hpp"
namespace chatterino {
namespace singletons {
class AccountManager
{
public:
AccountManager() = default;
~AccountManager() = delete;
void load();
providers::twitch::TwitchAccountManager Twitch;
};
} // namespace singletons
} // namespace chatterino

View file

@ -86,8 +86,8 @@ EmoteManager::EmoteManager()
void EmoteManager::initialize() void EmoteManager::initialize()
{ {
getApp()->accounts->Twitch.currentUserChanged.connect([this] { getApp()->accounts->twitch.currentUserChanged.connect([this] {
auto currentUser = getApp()->accounts->Twitch.getCurrent(); auto currentUser = getApp()->accounts->twitch.getCurrent();
assert(currentUser); assert(currentUser);
this->refreshTwitchEmotes(currentUser); this->refreshTwitchEmotes(currentUser);
}); });
@ -130,13 +130,23 @@ void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName,
QString code = emoteObject.value("code").toString(); QString code = emoteObject.value("code").toString();
// emoteObject.value("imageType").toString(); // emoteObject.value("imageType").toString();
QString link = linkTemplate; auto emote = this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [&] {
link.detach(); util::EmoteData emoteData;
QString link = linkTemplate;
link.detach();
emoteData.image1x = new Image(link.replace("{{id}}", id).replace("{{image}}", "1x"),
1, code, code + "<br />Channel BTTV Emote");
link = linkTemplate;
link.detach();
emoteData.image2x = new Image(link.replace("{{id}}", id).replace("{{image}}", "2x"),
0.5, code, code + "<br />Channel BTTV Emote");
link = linkTemplate;
link.detach();
emoteData.image3x = new Image(link.replace("{{id}}", id).replace("{{image}}", "3x"),
0.25, code, code + "<br />Channel BTTV Emote");
emoteData.pageLink = "https://manage.betterttv.net/emotes/" + id;
link = link.replace("{{id}}", id).replace("{{image}}", "1x"); return emoteData;
auto emote = this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [&code, &link] {
return util::EmoteData(new Image(link, 1, code, code + "<br/>Channel BTTV Emote"));
}); });
this->bttvChannelEmotes.insert(code, emote); this->bttvChannelEmotes.insert(code, emote);
@ -182,9 +192,11 @@ void EmoteManager::reloadFFZChannelEmotes(const QString &channelName,
QJsonObject urls = emoteObject.value("urls").toObject(); QJsonObject urls = emoteObject.value("urls").toObject();
auto emote = this->getFFZChannelEmoteFromCaches().getOrAdd(id, [&code, &urls] { auto emote = this->getFFZChannelEmoteFromCaches().getOrAdd(id, [id, &code, &urls] {
util::EmoteData emoteData; util::EmoteData emoteData;
FillInFFZEmoteData(urls, code, code + "<br/>Channel FFZ Emote", emoteData); FillInFFZEmoteData(urls, code, code + "<br/>Channel FFZ Emote", emoteData);
emoteData.pageLink =
QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
return emoteData; return emoteData;
}); });
@ -436,6 +448,7 @@ void EmoteManager::refreshTwitchEmotes(const std::shared_ptr<TwitchAccount> &use
[=, &emoteData](const QJsonObject &root) { [=, &emoteData](const QJsonObject &root) {
emoteData.emoteSets.clear(); emoteData.emoteSets.clear();
emoteData.emoteCodes.clear(); emoteData.emoteCodes.clear();
auto emoticonSets = root.value("emoticon_sets").toObject(); auto emoticonSets = root.value("emoticon_sets").toObject();
for (QJsonObject::iterator it = emoticonSets.begin(); it != emoticonSets.end(); ++it) { for (QJsonObject::iterator it = emoticonSets.begin(); it != emoticonSets.end(); ++it) {
std::string emoteSetString = it.key().toStdString(); std::string emoteSetString = it.key().toStdString();
@ -443,7 +456,7 @@ void EmoteManager::refreshTwitchEmotes(const std::shared_ptr<TwitchAccount> &use
for (QJsonValue emoteValue : emoteSetList) { for (QJsonValue emoteValue : emoteSetList) {
QJsonObject emoticon = emoteValue.toObject(); QJsonObject emoticon = emoteValue.toObject();
std::string id = emoticon["id"].toString().toStdString(); std::string id = QString::number(emoticon["id"].toInt()).toStdString();
std::string code = emoticon["code"].toString().toStdString(); std::string code = emoticon["code"].toString().toStdString();
emoteData.emoteSets[emoteSetString].push_back({id, code}); emoteData.emoteSets[emoteSetString].push_back({id, code});
emoteData.emoteCodes.push_back(code); emoteData.emoteCodes.push_back(code);
@ -483,6 +496,7 @@ void EmoteManager::loadBTTVEmotes()
code + "<br />Global BTTV Emote"); code + "<br />Global BTTV Emote");
emoteData.image3x = new Image(GetBTTVEmoteLink(urlTemplate, id, "3x"), 0.25, code, emoteData.image3x = new Image(GetBTTVEmoteLink(urlTemplate, id, "3x"), 0.25, code,
code + "<br />Global BTTV Emote"); code + "<br />Global BTTV Emote");
emoteData.pageLink = "https://manage.betterttv.net/emotes/" + id;
this->bttvGlobalEmotes.insert(code, emoteData); this->bttvGlobalEmotes.insert(code, emoteData);
codes.push_back(code.toStdString()); codes.push_back(code.toStdString());
@ -510,10 +524,13 @@ void EmoteManager::loadFFZEmotes()
QJsonObject object = emote.toObject(); QJsonObject object = emote.toObject();
QString code = object.value("name").toString(); QString code = object.value("name").toString();
int id = object.value("id").toInt();
QJsonObject urls = object.value("urls").toObject(); QJsonObject urls = object.value("urls").toObject();
util::EmoteData emoteData; util::EmoteData emoteData;
FillInFFZEmoteData(urls, code, code + "<br/>Global FFZ Emote", emoteData); FillInFFZEmoteData(urls, code, code + "<br/>Global FFZ Emote", emoteData);
emoteData.pageLink =
QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
this->ffzGlobalEmotes.insert(code, emoteData); this->ffzGlobalEmotes.insert(code, emoteData);
codes.push_back(code.toStdString()); codes.push_back(code.toStdString());
@ -530,6 +547,20 @@ util::EmoteData EmoteManager::getTwitchEmoteById(long id, const QString &emoteNa
{ {
QString _emoteName = emoteName; QString _emoteName = emoteName;
_emoteName.replace("<", "&lt;"); _emoteName.replace("<", "&lt;");
_emoteName.replace(">", "&gt;");
static QMap<QString, QString> emoteNameReplacements{
{"[oO](_|\\.)[oO]", "O_o"}, {"\\&gt\\;\\(", "&gt;("}, {"\\&lt\\;3", "&lt;3"},
{"\\:-?(o|O)", ":O"}, {"\\:-?(p|P)", ":P"}, {"\\:-?[\\\\/]", ":/"},
{"\\:-?[z|Z|\\|]", ":Z"}, {"\\:-?\\(", ":("}, {"\\:-?\\)", ":)"},
{"\\:-?D", ":D"}, {"\\;-?(p|P)", ";P"}, {"\\;-?\\)", ";)"},
{"R-?\\)", "R)"}, {"B-?\\)", "B)"},
};
auto it = emoteNameReplacements.find(_emoteName);
if (it != emoteNameReplacements.end()) {
_emoteName = it.value();
}
return _twitchEmoteFromCache.getOrAdd(id, [&emoteName, &_emoteName, &id] { return _twitchEmoteFromCache.getOrAdd(id, [&emoteName, &_emoteName, &id] {
util::EmoteData newEmoteData; util::EmoteData newEmoteData;

View file

@ -46,16 +46,6 @@ public:
util::EmoteData getTwitchEmoteById(long int id, const QString &emoteName); util::EmoteData getTwitchEmoteById(long int id, const QString &emoteName);
int getGeneration()
{
return _generation;
}
void incGeneration()
{
_generation++;
}
pajlada::Signals::NoArgSignal &getGifUpdateSignal(); pajlada::Signals::NoArgSignal &getGifUpdateSignal();
// Bit badge/emotes? // Bit badge/emotes?
@ -145,8 +135,6 @@ private:
pajlada::Signals::NoArgSignal gifUpdateTimerSignal; pajlada::Signals::NoArgSignal gifUpdateTimerSignal;
QTimer gifUpdateTimer; QTimer gifUpdateTimer;
bool gifUpdateTimerInitiated = false; bool gifUpdateTimerInitiated = false;
int _generation = 0;
}; };
} // namespace singletons } // namespace singletons

View file

@ -3,6 +3,10 @@
#include <QDebug> #include <QDebug>
#include <QtGlobal> #include <QtGlobal>
#include "application.hpp"
#include "util/assertinguithread.hpp"
#include "windowmanager.hpp"
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
#define DEFAULT_FONT_FAMILY "Segoe UI" #define DEFAULT_FONT_FAMILY "Segoe UI"
#define DEFAULT_FONT_SIZE 10 #define DEFAULT_FONT_SIZE 10
@ -20,86 +24,111 @@ namespace chatterino {
namespace singletons { namespace singletons {
FontManager::FontManager() FontManager::FontManager()
: currentFontFamily("/appearance/currentFontFamily", DEFAULT_FONT_FAMILY) : chatFontFamily("/appearance/currentFontFamily", DEFAULT_FONT_FAMILY)
, currentFontSize("/appearance/currentFontSize", DEFAULT_FONT_SIZE) , chatFontSize("/appearance/currentFontSize", DEFAULT_FONT_SIZE)
// , currentFont(this->currentFontFamily.getValue().c_str(), currentFontSize.getValue())
{ {
qDebug() << "init FontManager"; qDebug() << "init FontManager";
this->currentFontFamily.connect([this](const std::string &newValue, auto) { this->chatFontFamily.connect([this](const std::string &, auto) {
this->incGeneration(); util::assertInGuiThread();
// this->currentFont.setFamily(newValue.c_str());
this->currentFontByScale.clear();
this->fontChanged.invoke();
});
this->currentFontSize.connect([this](const int &newValue, auto) { if (getApp()->windows) {
this->incGeneration(); getApp()->windows->incGeneration();
// this->currentFont.setSize(newValue);
this->currentFontByScale.clear();
this->fontChanged.invoke();
});
}
QFont &FontManager::getFont(FontManager::Type type, float scale)
{
// return this->currentFont.getFont(type);
return this->getCurrentFont(scale).getFont(type);
}
QFontMetrics &FontManager::getFontMetrics(FontManager::Type type, float scale)
{
// return this->currentFont.getFontMetrics(type);
return this->getCurrentFont(scale).getFontMetrics(type);
}
FontManager::FontData &FontManager::Font::getFontData(FontManager::Type type)
{
switch (type) {
case Tiny:
return this->tiny;
case Small:
return this->small;
case MediumSmall:
return this->mediumSmall;
case Medium:
return this->medium;
case MediumBold:
return this->mediumBold;
case MediumItalic:
return this->mediumItalic;
case Large:
return this->large;
case VeryLarge:
return this->veryLarge;
default:
qDebug() << "Unknown font type:" << type << ", defaulting to medium";
return this->medium;
}
}
QFont &FontManager::Font::getFont(Type type)
{
return this->getFontData(type).font;
}
QFontMetrics &FontManager::Font::getFontMetrics(Type type)
{
return this->getFontData(type).metrics;
}
FontManager::Font &FontManager::getCurrentFont(float scale)
{
for (auto it = this->currentFontByScale.begin(); it != this->currentFontByScale.end(); it++) {
if (it->first == scale) {
return it->second;
} }
}
this->currentFontByScale.push_back(
std::make_pair(scale, Font(this->currentFontFamily.getValue().c_str(),
this->currentFontSize.getValue() * scale)));
return this->currentFontByScale.back().second; for (auto &map : this->fontsByType) {
map.clear();
}
this->fontChanged.invoke();
});
this->chatFontSize.connect([this](const int &, auto) {
util::assertInGuiThread();
if (getApp()->windows) {
getApp()->windows->incGeneration();
}
for (auto &map : this->fontsByType) {
map.clear();
}
this->fontChanged.invoke();
});
this->fontsByType.resize(size_t(EndType));
}
QFont FontManager::getFont(FontManager::Type type, float scale)
{
return this->getOrCreateFontData(type, scale).font;
}
QFontMetrics FontManager::getFontMetrics(FontManager::Type type, float scale)
{
return this->getOrCreateFontData(type, scale).metrics;
}
FontManager::FontData &FontManager::getOrCreateFontData(Type type, float scale)
{
util::assertInGuiThread();
assert(type >= 0 && type < EndType);
auto &map = this->fontsByType[size_t(type)];
// find element
auto it = map.find(scale);
if (it != map.end()) {
// return if found
return it->second;
}
// emplace new element
auto result = map.emplace(scale, this->createFontData(type, scale));
assert(result.second);
return result.first->second;
}
FontManager::FontData FontManager::createFontData(Type type, float scale)
{
// check if it's a chat (scale the setting)
if (type >= ChatStart && type <= ChatEnd) {
static std::unordered_map<Type, ChatFontData> sizeScale{
{ChatSmall, {0.6f, false, QFont::Normal}},
{ChatMediumSmall, {0.8f, false, QFont::Normal}},
{ChatMedium, {1, false, QFont::Normal}},
{ChatMediumBold, {1, false, QFont::Medium}},
{ChatMediumItalic, {1, true, QFont::Normal}},
{ChatLarge, {1.2f, false, QFont::Normal}},
{ChatVeryLarge, {1.4f, false, QFont::Normal}},
};
auto data = sizeScale[type];
return FontData(QFont(QString::fromStdString(this->chatFontFamily.getValue()),
int(this->chatFontSize.getValue() * data.scale * scale), data.weight,
data.italic));
}
// normal Ui font (use pt size)
{
#ifdef Q_OS_MAC
constexpr float multiplier = 0.8f;
#else
constexpr float multiplier = 1.f;
#endif
static std::unordered_map<Type, UiFontData> defaultSize{
{Tiny, {8, "Monospace", false, QFont::Normal}},
{UiMedium, {int(12 * multiplier), DEFAULT_FONT_FAMILY, false, QFont::Normal}},
{UiTabs, {int(9 * multiplier), DEFAULT_FONT_FAMILY, false, QFont::Normal}},
};
UiFontData &data = defaultSize[type];
QFont font(data.name, int(data.size * scale), data.weight, data.italic);
return FontData(font);
}
} }
} // namespace singletons } // namespace singletons

View file

@ -3,138 +3,79 @@
#include <QFont> #include <QFont>
#include <QFontDatabase> #include <QFontDatabase>
#include <QFontMetrics> #include <QFontMetrics>
#include <array>
#include <boost/noncopyable.hpp>
#include <pajlada/settings/setting.hpp> #include <pajlada/settings/setting.hpp>
#include <pajlada/signals/signal.hpp> #include <pajlada/signals/signal.hpp>
#include <unordered_map>
namespace chatterino { namespace chatterino {
namespace singletons { namespace singletons {
class FontManager class FontManager : boost::noncopyable
{ {
public: public:
FontManager(); FontManager();
FontManager(const FontManager &) = delete; // font data gets set in createFontData(...)
FontManager(FontManager &&) = delete;
~FontManager() = delete;
enum Type : uint8_t { enum Type : uint8_t {
Tiny, Tiny,
Small, ChatSmall,
MediumSmall, ChatMediumSmall,
Medium, ChatMedium,
MediumBold, ChatMediumBold,
MediumItalic, ChatMediumItalic,
Large, ChatLarge,
VeryLarge, ChatVeryLarge,
UiMedium,
UiTabs,
// don't remove this value
EndType,
// make sure to update these values accordingly!
ChatStart = ChatSmall,
ChatEnd = ChatVeryLarge,
}; };
QFont &getFont(Type type, float scale); QFont getFont(Type type, float scale);
QFontMetrics &getFontMetrics(Type type, float scale); QFontMetrics getFontMetrics(Type type, float scale);
int getGeneration() const pajlada::Settings::Setting<std::string> chatFontFamily;
{ pajlada::Settings::Setting<int> chatFontSize;
return this->generation;
}
void incGeneration()
{
this->generation++;
}
pajlada::Settings::Setting<std::string> currentFontFamily;
pajlada::Settings::Setting<int> currentFontSize;
pajlada::Signals::NoArgSignal fontChanged; pajlada::Signals::NoArgSignal fontChanged;
private: private:
struct FontData { struct FontData {
FontData(QFont &&_font) FontData(const QFont &_font)
: font(_font) : font(_font)
, metrics(this->font) , metrics(_font)
{ {
} }
QFont font; const QFont font;
QFontMetrics metrics; const QFontMetrics metrics;
}; };
struct Font { struct ChatFontData {
Font() = delete; float scale;
bool italic;
Font(const char *fontFamilyName, int mediumSize) QFont::Weight weight;
: tiny(QFont("Monospace", 8))
, small(QFont(fontFamilyName, mediumSize - 4))
, mediumSmall(QFont(fontFamilyName, mediumSize - 2))
, medium(QFont(fontFamilyName, mediumSize))
, mediumBold(QFont(fontFamilyName, mediumSize, QFont::DemiBold))
, mediumItalic(QFont(fontFamilyName, mediumSize, -1, true))
, large(QFont(fontFamilyName, mediumSize))
, veryLarge(QFont(fontFamilyName, mediumSize))
{
tiny.font.setStyleHint(QFont::TypeWriter);
}
void setFamily(const char *newFamily)
{
this->small.font.setFamily(newFamily);
this->mediumSmall.font.setFamily(newFamily);
this->medium.font.setFamily(newFamily);
this->mediumBold.font.setFamily(newFamily);
this->mediumItalic.font.setFamily(newFamily);
this->large.font.setFamily(newFamily);
this->veryLarge.font.setFamily(newFamily);
this->updateMetrics();
}
void setSize(int newMediumSize)
{
this->small.font.setPointSize(newMediumSize - 4);
this->mediumSmall.font.setPointSize(newMediumSize - 2);
this->medium.font.setPointSize(newMediumSize);
this->mediumBold.font.setPointSize(newMediumSize);
this->mediumItalic.font.setPointSize(newMediumSize);
this->large.font.setPointSize(newMediumSize + 2);
this->veryLarge.font.setPointSize(newMediumSize + 4);
this->updateMetrics();
}
void updateMetrics()
{
this->small.metrics = QFontMetrics(this->small.font);
this->mediumSmall.metrics = QFontMetrics(this->mediumSmall.font);
this->medium.metrics = QFontMetrics(this->medium.font);
this->mediumBold.metrics = QFontMetrics(this->mediumBold.font);
this->mediumItalic.metrics = QFontMetrics(this->mediumItalic.font);
this->large.metrics = QFontMetrics(this->large.font);
this->veryLarge.metrics = QFontMetrics(this->veryLarge.font);
}
FontData &getFontData(Type type);
QFont &getFont(Type type);
QFontMetrics &getFontMetrics(Type type);
FontData tiny;
FontData small;
FontData mediumSmall;
FontData medium;
FontData mediumBold;
FontData mediumItalic;
FontData large;
FontData veryLarge;
}; };
Font &getCurrentFont(float scale); struct UiFontData {
float size;
const char *name;
bool italic;
QFont::Weight weight;
};
// Future plans: FontData &getOrCreateFontData(Type type, float scale);
// Could have multiple fonts in here, such as "Menu font", "Application font", "Chat font" FontData createFontData(Type type, float scale);
std::list<std::pair<float, Font>> currentFontByScale; std::vector<std::unordered_map<float, FontData>> fontsByType;
int generation = 0;
}; };
} // namespace singletons } // namespace singletons

View file

@ -31,6 +31,7 @@ namespace ipc = boost::interprocess;
namespace chatterino { namespace chatterino {
namespace singletons { namespace singletons {
// fourtf: don't add this class to the application class
NativeMessagingManager::NativeMessagingManager() NativeMessagingManager::NativeMessagingManager()
{ {
qDebug() << "init NativeMessagingManager"; qDebug() << "init NativeMessagingManager";
@ -50,6 +51,10 @@ void NativeMessagingManager::registerHost()
{ {
auto app = getApp(); auto app = getApp();
if (app->paths->isPortable()) {
return;
}
// create manifest // create manifest
QJsonDocument document; QJsonDocument document;
QJsonObject root_obj; QJsonObject root_obj;
@ -113,13 +118,14 @@ void NativeMessagingManager::ReceiverThread::run()
while (true) { while (true) {
try { try {
char *buf = (char *)malloc(MESSAGE_SIZE); std::unique_ptr<char> buf(static_cast<char *>(malloc(MESSAGE_SIZE)));
ipc::message_queue::size_type retSize; ipc::message_queue::size_type retSize;
unsigned int priority; unsigned int priority;
messageQueue.receive(buf, MESSAGE_SIZE, retSize, priority); messageQueue.receive(buf.get(), MESSAGE_SIZE, retSize, priority);
QJsonDocument document = QJsonDocument::fromJson(QByteArray(buf, retSize)); QJsonDocument document =
QJsonDocument::fromJson(QByteArray::fromRawData(buf.get(), retSize));
this->handleMessage(document.object()); this->handleMessage(document.object());
} catch (ipc::interprocess_exception &ex) { } catch (ipc::interprocess_exception &ex) {
@ -143,26 +149,37 @@ void NativeMessagingManager::ReceiverThread::handleMessage(const QJsonObject &ro
QString _type = root.value("type").toString(); QString _type = root.value("type").toString();
bool attach = root.value("attach").toBool(); bool attach = root.value("attach").toBool();
QString name = root.value("name").toString(); QString name = root.value("name").toString();
QString winId = root.value("winId").toString();
int yOffset = root.value("yOffset").toInt(-1);
if (_type.isNull() || name.isNull() || winId.isNull()) { #ifdef USEWINSDK
widgets::AttachedWindow::GetArgs args;
args.winId = root.value("winId").toString();
args.yOffset = root.value("yOffset").toInt(-1);
args.width = root.value("size").toObject().value("width").toInt(-1);
args.height = root.value("size").toObject().value("height").toInt(-1);
if (_type.isNull() || args.winId.isNull()) {
qDebug() << "NM type, name or winId missing"; qDebug() << "NM type, name or winId missing";
attach = false; attach = false;
return; return;
} }
#endif
if (_type == "twitch") { if (_type == "twitch") {
util::postToThread([name, attach, winId, yOffset, app] { util::postToThread([=] {
app->twitch.server->watchingChannel.update( if (!name.isEmpty()) {
app->twitch.server->getOrAddChannel(name)); app->twitch.server->watchingChannel.update(
app->twitch.server->getOrAddChannel(name));
}
if (attach) { if (attach) {
#ifdef USEWINSDK #ifdef USEWINSDK
auto *window = if (args.height != -1) {
widgets::AttachedWindow::get(::GetForegroundWindow(), winId, yOffset); auto *window = widgets::AttachedWindow::get(::GetForegroundWindow(), args);
window->setChannel(app->twitch.server->getOrAddChannel(name)); if (!name.isEmpty()) {
window->show(); window->setChannel(app->twitch.server->getOrAddChannel(name));
}
}
// window->show();
#endif #endif
} }
}); });
@ -179,12 +196,15 @@ void NativeMessagingManager::ReceiverThread::handleMessage(const QJsonObject &ro
} }
#ifdef USEWINSDK #ifdef USEWINSDK
util::postToThread([winId] { widgets::AttachedWindow::detach(winId); }); util::postToThread([winId] {
qDebug() << "NW detach";
widgets::AttachedWindow::detach(winId);
});
#endif #endif
} else { } else {
qDebug() << "NM unknown action " + action; qDebug() << "NM unknown action " + action;
} }
} } // namespace singletons
} // namespace singletons } // namespace singletons
} // namespace chatterino } // namespace chatterino

View file

@ -8,6 +8,7 @@ namespace singletons {
class NativeMessagingManager class NativeMessagingManager
{ {
public: public:
// fourtf: don't add this class to the application class
NativeMessagingManager(); NativeMessagingManager();
~NativeMessagingManager() = delete; ~NativeMessagingManager() = delete;

View file

@ -19,21 +19,21 @@ PathManager::PathManager(int argc, char **argv)
.replace("/", "x"); .replace("/", "x");
// Options // Options
bool portable = false; this->portable = false;
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "portable") == 0) { if (strcmp(argv[i], "portable") == 0) {
portable = true; this->portable = true;
} }
} }
if (QFileInfo::exists(QCoreApplication::applicationDirPath() + "/portable")) { if (QFileInfo::exists(QCoreApplication::applicationDirPath() + "/this->portable")) {
portable = true; this->portable = true;
} }
// Root path = %APPDATA%/Chatterino or the folder that the executable resides in // Root path = %APPDATA%/Chatterino or the folder that the executable resides in
QString rootPath; QString rootPath;
if (portable) { if (this->portable) {
rootPath.append(QCoreApplication::applicationDirPath()); rootPath.append(QCoreApplication::applicationDirPath());
} else { } else {
// Get settings path // Get settings path
@ -91,5 +91,10 @@ bool PathManager::createFolder(const QString &folderPath)
return QDir().mkpath(folderPath); return QDir().mkpath(folderPath);
} }
bool PathManager::isPortable()
{
return this->portable;
}
} // namespace singletons } // namespace singletons
} // namespace chatterino } // namespace chatterino

View file

@ -28,6 +28,10 @@ public:
QString appPathHash; QString appPathHash;
bool createFolder(const QString &folderPath); bool createFolder(const QString &folderPath);
bool isPortable();
private:
bool portable;
}; };
} // namespace singletons } // namespace singletons

View file

@ -2,6 +2,7 @@
#include "util/emotemap.hpp" #include "util/emotemap.hpp"
#include <QIcon>
#include <QRegularExpression> #include <QRegularExpression>
#include <map> #include <map>

View file

@ -39,8 +39,18 @@ void SettingManager::initialize()
this->timestampFormat.connect([](auto, auto) { this->timestampFormat.connect([](auto, auto) {
auto app = getApp(); auto app = getApp();
app->windows->layoutVisibleChatWidgets(); app->windows->layoutChannelViews();
}); });
this->emoteScale.connect([](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
this->timestampFormat.connect([](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
this->alternateMessageBackground.connect(
[](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
this->seperateMessages.connect(
[](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
this->collpseMessagesMinLines.connect(
[](auto, auto) { getApp()->windows->forceLayoutChannelViews(); });
} }
MessageElement::Flags SettingManager::getWordFlags() MessageElement::Flags SettingManager::getWordFlags()

View file

@ -40,6 +40,8 @@ public:
BoolSetting hideEmptyInput = {"/appearance/hideEmptyInputBox", false}; BoolSetting hideEmptyInput = {"/appearance/hideEmptyInputBox", false};
BoolSetting showMessageLength = {"/appearance/messages/showMessageLength", false}; BoolSetting showMessageLength = {"/appearance/messages/showMessageLength", false};
BoolSetting seperateMessages = {"/appearance/messages/separateMessages", false}; BoolSetting seperateMessages = {"/appearance/messages/separateMessages", false};
// BoolSetting collapseLongMessages = {"/appearance/messages/collapseLongMessages", false};
IntSetting collpseMessagesMinLines = {"/appearance/messages/collapseMessagesMinLines", 0};
BoolSetting alternateMessageBackground = {"/appearance/messages/alternateMessageBackground", BoolSetting alternateMessageBackground = {"/appearance/messages/alternateMessageBackground",
false}; false};
BoolSetting windowTopMost = {"/appearance/windowAlwaysOnTop", false}; BoolSetting windowTopMost = {"/appearance/windowAlwaysOnTop", false};
@ -54,6 +56,8 @@ public:
/// Behaviour /// Behaviour
BoolSetting allowDuplicateMessages = {"/behaviour/allowDuplicateMessages", true}; BoolSetting allowDuplicateMessages = {"/behaviour/allowDuplicateMessages", true};
BoolSetting mentionUsersWithAt = {"/behaviour/mentionUsersWithAt", false}; BoolSetting mentionUsersWithAt = {"/behaviour/mentionUsersWithAt", false};
BoolSetting showJoins = {"/behaviour/showJoins", false};
BoolSetting showParts = {"/behaviour/showParts", false};
FloatSetting mouseScrollMultiplier = {"/behaviour/mouseScrollMultiplier", 1.0}; FloatSetting mouseScrollMultiplier = {"/behaviour/mouseScrollMultiplier", 1.0};
// Auto-completion // Auto-completion
@ -75,10 +79,10 @@ public:
BoolSetting enableGifAnimations = {"/emotes/enableGifAnimations", true}; BoolSetting enableGifAnimations = {"/emotes/enableGifAnimations", true};
FloatSetting emoteScale = {"/emotes/scale", 1.f}; FloatSetting emoteScale = {"/emotes/scale", 1.f};
// 0 = Smallest size // 0 = No preference
// 1 = One size above 0 (usually size of 0 * 2) // 1 = 1x
// 2 = One size above 1 (usually size of 1 * 2) // 2 = 2x
// etc... // 3 = 3x
IntSetting preferredEmoteQuality = {"/emotes/preferredEmoteQuality", 0}; IntSetting preferredEmoteQuality = {"/emotes/preferredEmoteQuality", 0};
/// Links /// Links

View file

@ -52,7 +52,6 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
isLight = multiplier > 0; isLight = multiplier > 0;
bool lightWin = isLight; bool lightWin = isLight;
QColor none(0, 0, 0, 0);
QColor themeColor = QColor::fromHslF(hue, 0.43, 0.5); QColor themeColor = QColor::fromHslF(hue, 0.43, 0.5);
QColor themeColorNoSat = QColor::fromHslF(hue, 0, 0.5); QColor themeColorNoSat = QColor::fromHslF(hue, 0, 0.5);
@ -69,7 +68,7 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
this->window.background = lightWin ? "#fff" : QColor(61, 60, 56); this->window.background = lightWin ? "#fff" : QColor(61, 60, 56);
#else #else
this->window.background = lightWin ? "#fff" : "#444"; this->window.background = lightWin ? "#fff" : "#111";
#endif #endif
QColor fg = this->window.text = lightWin ? "#000" : "#eee"; QColor fg = this->window.text = lightWin ? "#000" : "#eee";
@ -89,27 +88,52 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
/// TABS /// TABS
if (lightWin) { if (lightWin) {
this->tabs.regular = {fg, {bg, QColor("#ccc"), bg}}; this->tabs.regular = {QColor("#444"),
{QColor("#fff"), QColor("#fff"), QColor("#fff")},
{QColor("#fff"), QColor("#fff"), QColor("#fff")}};
this->tabs.newMessage = { this->tabs.newMessage = {
fg, fg, {bg, QColor("#ccc"), bg}, {QColor("#aaa"), QColor("#aaa"), QColor("#aaa")}};
{QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern), this->tabs.highlighted = {fg,
QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern), {bg, QColor("#ccc"), bg},
QBrush(blendColors(themeColorNoSat, "#ccc", 0.9), Qt::FDiagPattern)}}; {QColor("#b60505"), QColor("#b60505"), QColor("#b60505")}};
this->tabs.highlighted = {fg, {QColor("#ccc"), QColor("#ccc"), QColor("#bbb")}};
this->tabs.selected = {QColor("#fff"),
{QColor("#777"), QColor("#777"), QColor("#888")}};
} else {
this->tabs.regular = {fg, {bg, QColor("#555"), bg}};
this->tabs.newMessage = {
fg,
{QBrush(blendColors(themeColor, "#666", 0.7), Qt::FDiagPattern),
QBrush(blendColors(themeColor, "#666", 0.5), Qt::FDiagPattern),
QBrush(blendColors(themeColorNoSat, "#666", 0.7), Qt::FDiagPattern)}};
this->tabs.highlighted = {fg, {QColor("#777"), QColor("#777"), QColor("#666")}};
this->tabs.selected = {QColor("#000"), this->tabs.selected = {QColor("#000"),
{QColor("#999"), QColor("#999"), QColor("#888")}}; {QColor("#b4d7ff"), QColor("#b4d7ff"), QColor("#b4d7ff")},
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
} else {
this->tabs.regular = {QColor("#aaa"),
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
{QColor("#444"), QColor("#444"), QColor("#444")}};
this->tabs.newMessage = {fg,
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
{QColor("#888"), QColor("#888"), QColor("#888")}};
this->tabs.highlighted = {fg,
{QColor("#252525"), QColor("#252525"), QColor("#252525")},
{QColor("#ee6166"), QColor("#ee6166"), QColor("#ee6166")}};
this->tabs.selected = {QColor("#fff"),
{QColor("#555555"), QColor("#555555"), QColor("#555555")},
{QColor("#00aeef"), QColor("#00aeef"), QColor("#00aeef")}};
} }
// scrollbar
this->scrollbars.highlights.highlight = QColor("#ee6166");
this->scrollbars.highlights.subscription = QColor("#C466FF");
// this->tabs.newMessage = {
// fg,
// {QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
// QBrush(blendColors(themeColor, "#ccc", 0.9), Qt::FDiagPattern),
// QBrush(blendColors(themeColorNoSat, "#ccc", 0.9), Qt::FDiagPattern)}};
// this->tabs.newMessage = {
// fg,
// {QBrush(blendColors(themeColor, "#666", 0.7), Qt::FDiagPattern),
// QBrush(blendColors(themeColor, "#666", 0.5), Qt::FDiagPattern),
// QBrush(blendColors(themeColorNoSat, "#666", 0.7),
// Qt::FDiagPattern)}};
// this->tabs.highlighted = {fg, {QColor("#777"), QColor("#777"),
// QColor("#666")}};
this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color(); this->tabs.bottomLine = this->tabs.selected.backgrounds.regular.color();
} }
@ -119,7 +143,12 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
this->splits.messageSeperator = isLight ? QColor(127, 127, 127) : QColor(60, 60, 60); this->splits.messageSeperator = isLight ? QColor(127, 127, 127) : QColor(60, 60, 60);
this->splits.background = getColor(0, sat, 1); this->splits.background = getColor(0, sat, 1);
this->splits.dropPreview = QColor(0, 148, 255, 0x30); this->splits.dropPreview = QColor(0, 148, 255, 0x30);
this->splits.dropPreviewBorder = QColor(0, 148, 255, 0x70); this->splits.dropPreviewBorder = QColor(0, 148, 255, 0xff);
this->splits.dropTargetRect = QColor(0, 148, 255, 0x00);
this->splits.dropTargetRectBorder = QColor(0, 148, 255, 0x00);
this->splits.resizeHandle = QColor(0, 148, 255, 0x70);
this->splits.resizeHandleBackground = QColor(0, 148, 255, 0x20);
// this->splits.border // this->splits.border
// this->splits.borderFocused // this->splits.borderFocused
@ -143,7 +172,10 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
this->messages.backgrounds.regular = splits.background; this->messages.backgrounds.regular = splits.background;
this->messages.backgrounds.alternate = getColor(0, sat, 0.93); this->messages.backgrounds.alternate = getColor(0, sat, 0.93);
this->messages.backgrounds.highlighted = this->messages.backgrounds.highlighted =
blendColors(themeColor, this->messages.backgrounds.regular, 0.8); blendColors(themeColor, this->messages.backgrounds.regular, 0.6);
this->messages.backgrounds.subscription =
blendColors(QColor("#C466FF"), this->messages.backgrounds.regular, 0.7);
// this->messages.backgrounds.resub // this->messages.backgrounds.resub
// this->messages.backgrounds.whisper // this->messages.backgrounds.whisper
this->messages.disabled = getColor(0, sat, 1, 0.6); this->messages.disabled = getColor(0, sat, 1, 0.6);
@ -151,7 +183,9 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
// this->messages.seperatorInner = // this->messages.seperatorInner =
// Scrollbar // Scrollbar
this->scrollbars.background = getColor(0, sat, 0.94); this->scrollbars.background = QColor(0, 0, 0, 0);
// this->scrollbars.background = splits.background;
// this->scrollbars.background.setAlphaF(qreal(0.2));
this->scrollbars.thumb = getColor(0, sat, 0.80); this->scrollbars.thumb = getColor(0, sat, 0.80);
this->scrollbars.thumbSelected = getColor(0, sat, 0.7); this->scrollbars.thumbSelected = getColor(0, sat, 0.7);
@ -163,13 +197,13 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier)
this->messages.selection = isLightTheme() ? QColor(0, 0, 0, 64) : QColor(255, 255, 255, 64); this->messages.selection = isLightTheme() ? QColor(0, 0, 0, 64) : QColor(255, 255, 255, 64);
this->updated.invoke(); this->updated.invoke();
} } // namespace singletons
QColor ThemeManager::blendColors(const QColor &color1, const QColor &color2, qreal ratio) QColor ThemeManager::blendColors(const QColor &color1, const QColor &color2, qreal ratio)
{ {
int r = color1.red() * (1 - ratio) + color2.red() * ratio; int r = int(color1.red() * (1 - ratio) + color2.red() * ratio);
int g = color1.green() * (1 - ratio) + color2.green() * ratio; int g = int(color1.green() * (1 - ratio) + color2.green() * ratio);
int b = color1.blue() * (1 - ratio) + color2.blue() * ratio; int b = int(color1.blue() * (1 - ratio) + color2.blue() * ratio);
return QColor(r, g, b, 255); return QColor(r, g, b, 255);
} }
@ -177,22 +211,22 @@ QColor ThemeManager::blendColors(const QColor &color1, const QColor &color2, qre
void ThemeManager::normalizeColor(QColor &color) void ThemeManager::normalizeColor(QColor &color)
{ {
if (this->isLight) { if (this->isLight) {
if (color.lightnessF() > 0.5f) { if (color.lightnessF() > 0.5) {
color.setHslF(color.hueF(), color.saturationF(), 0.5f); color.setHslF(color.hueF(), color.saturationF(), 0.5);
} }
if (color.lightnessF() > 0.4f && color.hueF() > 0.1 && color.hueF() < 0.33333) { if (color.lightnessF() > 0.4 && color.hueF() > 0.1 && color.hueF() < 0.33333) {
color.setHslF( color.setHslF(
color.hueF(), color.saturationF(), color.hueF(), color.saturationF(),
color.lightnessF() - sin((color.hueF() - 0.1) / (0.3333 - 0.1) * 3.14159) * color.lightnessF() - sin((color.hueF() - 0.1) / (0.3333 - 0.1) * 3.14159) *
color.saturationF() * 0.2); color.saturationF() * 0.2);
} }
} else { } else {
if (color.lightnessF() < 0.5f) { if (color.lightnessF() < 0.5) {
color.setHslF(color.hueF(), color.saturationF(), 0.5f); color.setHslF(color.hueF(), color.saturationF(), 0.5);
} }
if (color.lightnessF() < 0.6f && color.hueF() > 0.54444 && color.hueF() < 0.83333) { if (color.lightnessF() < 0.6 && color.hueF() > 0.54444 && color.hueF() < 0.83333) {
color.setHslF( color.setHslF(
color.hueF(), color.saturationF(), color.hueF(), color.saturationF(),
color.lightnessF() + sin((color.hueF() - 0.54444) / (0.8333 - 0.54444) * 3.14159) * color.lightnessF() + sin((color.hueF() - 0.54444) / (0.8333 - 0.54444) * 3.14159) *

View file

@ -25,11 +25,16 @@ public:
struct TabColors { struct TabColors {
QColor text; QColor text;
struct Backgrounds { struct {
QBrush regular; QBrush regular;
QBrush hover; QBrush hover;
QBrush unfocused; QBrush unfocused;
} backgrounds; } backgrounds;
struct {
QColor regular;
QColor hover;
QColor unfocused;
} line;
}; };
/// WINDOW /// WINDOW
@ -43,9 +48,9 @@ public:
/// TABS /// TABS
struct { struct {
TabColors regular; TabColors regular;
TabColors selected;
TabColors highlighted;
TabColors newMessage; TabColors newMessage;
TabColors highlighted;
TabColors selected;
QColor border; QColor border;
QColor bottomLine; QColor bottomLine;
} tabs; } tabs;
@ -58,6 +63,10 @@ public:
QColor borderFocused; QColor borderFocused;
QColor dropPreview; QColor dropPreview;
QColor dropPreviewBorder; QColor dropPreviewBorder;
QColor dropTargetRect;
QColor dropTargetRectBorder;
QColor resizeHandle;
QColor resizeHandleBackground;
struct { struct {
QColor border; QColor border;
@ -89,7 +98,7 @@ public:
QColor regular; QColor regular;
QColor alternate; QColor alternate;
QColor highlighted; QColor highlighted;
// QColor resub; QColor subscription;
// QColor whisper; // QColor whisper;
} backgrounds; } backgrounds;
@ -104,8 +113,10 @@ public:
QColor background; QColor background;
QColor thumb; QColor thumb;
QColor thumbSelected; QColor thumbSelected;
// const int highlightsCount = 3; struct {
// QColor highlights[3]; QColor highlight;
QColor subscription;
} highlights;
} scrollbars; } scrollbars;
/// TOOLTIP /// TOOLTIP

View file

@ -7,25 +7,31 @@ namespace chatterino {
namespace singletons { namespace singletons {
UpdateManager::UpdateManager() UpdateManager::UpdateManager()
: currentVersion(CHATTERINO_VERSION) : currentVersion_(CHATTERINO_VERSION)
{ {
qDebug() << "init UpdateManager"; qDebug() << "init UpdateManager";
} }
UpdateManager &UpdateManager::getInstance() UpdateManager &UpdateManager::getInstance()
{ {
// fourtf: don't add this class to the application class
static UpdateManager instance; static UpdateManager instance;
return instance; return instance;
} }
const QString &UpdateManager::getCurrentVersion() const const QString &UpdateManager::getCurrentVersion() const
{ {
return this->getCurrentVersion(); return currentVersion_;
} }
const QString &UpdateManager::getOnlineVersion() const const QString &UpdateManager::getOnlineVersion() const
{ {
return this->getOnlineVersion(); return onlineVersion_;
}
void UpdateManager::installUpdates()
{
} }
void UpdateManager::checkForUpdates() void UpdateManager::checkForUpdates()
@ -33,17 +39,38 @@ void UpdateManager::checkForUpdates()
QString url = "https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS "/stable"; QString url = "https://notitia.chatterino.com/version/chatterino/" CHATTERINO_OS "/stable";
util::NetworkRequest req(url); util::NetworkRequest req(url);
req.setTimeout(20000); req.setTimeout(30000);
req.getJSON([this](QJsonObject &object) { req.getJSON([this](QJsonObject &object) {
QJsonValue version_val = object.value("version"); QJsonValue version_val = object.value("version");
if (!version_val.isString()) { if (!version_val.isString()) {
this->setStatus_(Error);
qDebug() << "error updating"; qDebug() << "error updating";
return; return;
} }
this->onlineVersion = version_val.toString(); this->onlineVersion_ = version_val.toString();
this->onlineVersionUpdated.invoke();
if (this->currentVersion_ != this->onlineVersion_) {
this->setStatus_(UpdateAvailable);
} else {
this->setStatus_(NoUpdateAvailable);
}
}); });
this->setStatus_(Searching);
req.execute();
}
UpdateManager::UpdateStatus UpdateManager::getStatus() const
{
return this->status_;
}
void UpdateManager::setStatus_(UpdateStatus status)
{
if (this->status_ != status) {
this->status_ = status;
this->statusUpdated.invoke(status);
}
} }
} // namespace singletons } // namespace singletons

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