Merge branch 'master' of https://github.com/redsky17/nheko
|
@ -2,10 +2,10 @@
|
|||
|
||||
set -ex
|
||||
|
||||
if [ $TRAVIS_OS_NAME == osx ]; then
|
||||
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||
brew update
|
||||
brew install qt5 lmdb clang-format ninja libsodium cmark
|
||||
brew upgrade boost cmake || true
|
||||
brew upgrade boost cmake icu4c || true
|
||||
|
||||
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
|
||||
sudo python get-pip.py
|
||||
|
@ -17,7 +17,7 @@ if [ $TRAVIS_OS_NAME == osx ]; then
|
|||
fi
|
||||
|
||||
|
||||
if [ $TRAVIS_OS_NAME == linux ]; then
|
||||
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
|
||||
|
||||
if [ -z "$QT_VERSION" ]; then
|
||||
QT_VERSION="592"
|
||||
|
|
|
@ -36,8 +36,10 @@ export LD_LIBRARY_PATH=.deps/usr/lib/:$LD_LIBRARY_PATH
|
|||
./linuxdeployqt*.AppImage ${DIR}/usr/share/applications/*.desktop -bundle-non-qt-libs
|
||||
./linuxdeployqt*.AppImage ${DIR}/usr/share/applications/*.desktop -appimage
|
||||
|
||||
chmod +x nheko-x86_64.AppImage
|
||||
chmod +x nheko-*x86_64.AppImage
|
||||
|
||||
if [ ! -z $TRAVIS_TAG ]; then
|
||||
mv nheko-x86_64.AppImage nheko-${TRAVIS_TAG}-x86_64.AppImage
|
||||
if [ ! -z $VERSION ]; then
|
||||
# commented out for now, as AppImage file appears to already contain the version.
|
||||
#mv nheko-*x86_64.AppImage nheko-${VERSION}-x86_64.AppImage
|
||||
echo "nheko-${VERSION}-x86_64.AppImage"
|
||||
fi
|
|
@ -8,7 +8,15 @@ TAG=`git tag -l --points-at HEAD`
|
|||
PATH=/usr/local/opt/qt/bin/:${PATH}
|
||||
|
||||
pushd build
|
||||
sudo macdeployqt nheko.app -dmg
|
||||
|
||||
# macdeployqt does not copy symlinks over.
|
||||
# this specifically addresses icu4c issues but nothing else.
|
||||
export ICU_LIB="$(brew --prefix icu4c)/lib"
|
||||
mkdir -p nheko.app/Contents/Frameworks
|
||||
find ${ICU_LIB} -type l -name "*.dylib" -exec cp -a -n {} nheko.app/Contents/Frameworks/ \; || true
|
||||
|
||||
sudo macdeployqt nheko.app -dmg -always-overwrite
|
||||
|
||||
user=$(id -nu)
|
||||
sudo chown ${user} nheko.dmg
|
||||
mv nheko.dmg ..
|
||||
|
@ -16,6 +24,6 @@ popd
|
|||
|
||||
dmgbuild -s ./.ci/macos/settings.json "Nheko" nheko.dmg
|
||||
|
||||
if [ ! -z $TRAVIS_TAG ]; then
|
||||
mv nheko.dmg nheko-${TRAVIS_TAG}.dmg
|
||||
if [ ! -z $VERSION ]; then
|
||||
mv nheko.dmg nheko-${VERSION}.dmg
|
||||
fi
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
set -ex
|
||||
|
||||
if [ $TRAVIS_OS_NAME == linux ]; then
|
||||
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
|
||||
export CC=${C_COMPILER}
|
||||
export CXX=${CXX_COMPILER}
|
||||
|
||||
|
@ -13,11 +13,11 @@ if [ $TRAVIS_OS_NAME == linux ]; then
|
|||
sudo update-alternatives --set g++ "/usr/bin/${CXX_COMPILER}"
|
||||
fi
|
||||
|
||||
if [ $TRAVIS_OS_NAME == linux ]; then
|
||||
if [ "$TRAVIS_OS_NAME" == "linux" ]; then
|
||||
source /opt/qt${QT_PKG}/bin/qt${QT_PKG}-env.sh || true;
|
||||
fi
|
||||
|
||||
if [ $TRAVIS_OS_NAME == osx ]; then
|
||||
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||
export CMAKE_PREFIX_PATH=/usr/local/opt/qt5
|
||||
fi
|
||||
|
||||
|
@ -33,14 +33,14 @@ cmake -GNinja -H. -Bbuild \
|
|||
-DCMAKE_INSTALL_PREFIX=.deps/usr
|
||||
cmake --build build
|
||||
|
||||
if [ $TRAVIS_OS_NAME == osx ]; then
|
||||
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||
make lint;
|
||||
|
||||
if [ $DEPLOYMENT == 1 ] && [ ! -z $TRAVIS_TAG ]; then
|
||||
if [ $DEPLOYMENT == 1 ] && [ ! -z $VERSION ] ; then
|
||||
make macos-deploy;
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $TRAVIS_OS_NAME == linux ] && [ $DEPLOYMENT == 1 ] && [ ! -z $TRAVIS_TAG ]; then
|
||||
if [ "$TRAVIS_OS_NAME" == "linux" ] && [ $DEPLOYMENT == 1 ] && [ ! -z $VERSION ]; then
|
||||
make linux-deploy;
|
||||
fi
|
||||
|
|
56
.travis.yml
|
@ -1,19 +1,21 @@
|
|||
language: cpp
|
||||
sudo: required
|
||||
dist: trusty
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
webhooks:
|
||||
urls:
|
||||
- https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MHJlZF9za3klM0FvY2Vhbi5qb2Vkb25vZnJ5LmNvbS8lMjFldkFxa1BIWnVQSElHZWVuaGklM0FvY2Vhbi5qb2Vkb25vZnJ5LmNvbQ
|
||||
on_success: always
|
||||
on_failure: always
|
||||
on_start: never
|
||||
email: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode9
|
||||
compiler: clang
|
||||
osx_image: xcode9
|
||||
env:
|
||||
- DEPLOYMENT=1
|
||||
- USE_BUNDLED_BOOST=0
|
||||
|
@ -30,11 +32,8 @@ matrix:
|
|||
- USE_BUNDLED_CMARK=1
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-5
|
||||
- ninja-build
|
||||
sources: ["ubuntu-toolchain-r-test"]
|
||||
packages: ["g++-5", "ninja-build"]
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
env:
|
||||
|
@ -46,11 +45,8 @@ matrix:
|
|||
- USE_BUNDLED_CMARK=1
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-8
|
||||
- ninja-build
|
||||
sources: ["ubuntu-toolchain-r-test"]
|
||||
packages: ["g++-8", "ninja-build"]
|
||||
- os: linux
|
||||
compiler: clang
|
||||
env:
|
||||
|
@ -62,44 +58,44 @@ matrix:
|
|||
- USE_BUNDLED_CMARK=1
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-trusty-5.0
|
||||
packages:
|
||||
- clang-5.0
|
||||
- g++-7
|
||||
- ninja-build
|
||||
sources: ["ubuntu-toolchain-r-test", "llvm-toolchain-trusty-5.0"]
|
||||
packages: ["clang-5.0", "g++-7", "ninja-build"]
|
||||
|
||||
before_install:
|
||||
- export CXX=${CXX_COMPILER}
|
||||
- export CC=${C_COMPILER}
|
||||
# Use TRAVIS_TAG if defined, or the short commit SHA otherwise
|
||||
- export VERSION=${TRAVIS_TAG:-$(git rev-parse --short HEAD)}
|
||||
install:
|
||||
- "./.ci/install.sh"
|
||||
- ./.ci/install.sh
|
||||
- export PATH=/usr/local/bin:${PATH}
|
||||
|
||||
script:
|
||||
- "./.ci/script.sh"
|
||||
- sed -i -e "s/VERSION_NAME_VALUE/${TRAVIS_TAG}/g" ./.ci/bintray-release.json || true
|
||||
- ./.ci/script.sh
|
||||
- sed -i -e "s/VERSION_NAME_VALUE/${VERSION}/g" ./.ci/bintray-release.json || true
|
||||
- cp ./.ci/bintray-release.json .
|
||||
deploy:
|
||||
- provider: bintray
|
||||
user: redsky17
|
||||
user: "redsky17"
|
||||
key:
|
||||
secure: CAVzWZPxYSOTollo9bpD4tvEbfxXjqelc32aApV48GKyJrMQljQ+mvSe25BuUtnDehxnw8affgGX23AYXmvG8P7w4hM2d7//8Lgan1zCmusV8JE432jknev6X641B4cvrywqSe0Dj3l0kS9Xgirq4BGavlI0y2vUjeJfQEv0y8GYoI72LwgyH0i82v/1Qi92Fh8429IJIb0eKmC1wGWXCmo2kd8StZRL5mSlc4TmyWI0SHpA5GrLMiQwLAuD7DjDl5mpaK2yQx+H4vBcI2SUMvmlHGgVjXikJG5gURlHbnIaaBFvO67INc1/65KtMokWuMP12zxqJiaMPtsAskOpQv4FLAYDfnigH3NxufyOIGp2cxS5RhJDQhbNsxHEDnUo1kHcO23ZYNWCuC1yUdn0RXzKhWcUsz8mKF8KJs22Ty4VjfUMZ+vqK/AbHyq4rkl8DizVRZqKF1KjSWrSv/2sT4itnHk9pmcgxAYfGuALcjrJJveI4MTwDhzXB62CKnMOqLq3sAMqvE0+BdA0BykQr7qrKtptuyP2/OFx6RDbfHQl5Klkb6cSOjxm0oUzh/8iaxgsVdCrhfE67eqkhFZ+a8lJkB/rZ4zSK1Q2Cp4nLtnxenUCW+Ptk2l7zZN6kXM1/+tcgqVROChYJ6asMUpsjFOOAVQ8SZ4TcxX1rq+pxlA=
|
||||
secure: "2C+ESOClZdNCDzfUfE8Rcdn9+BM3/5x1lebGS0qRxICMAuylx50C8RAxlqKIFNI1J+IMnj5xhK+1oWsLg6wUdo8B3JocvM6P1NbC/WwfY5UJVwtSmzcqvEC+IjM90Bng8OoM5f6ED3IAUyYPBgo8J14+2l5Oruvr99t0mrpniLXDf66TJrI9u/+05JYa2pm0EWlQkDVaziI+96zAt1pMIX9Z/ToTmUbq2OAlHaN8H4hR5aBRmYuF11FQVyARK64eqRsBV4RXKJ9DjW7BR+pQV008hQUoXrCxK8xIf/Qph9h1fz63961NJxfru62LeuZ4Z8MUcV9L+p0HYZzkhNdn5Z3QFf3SXbrVCYs6LK6+uKHpmYS7vB5XwjDx9SWc2szsd5OVKJg3YEdBcC4KUSL3VEbBHDSQ3GFsVKoBsZvopwHkyCmPotY47HtEiPBLdEcsOvcVyeU5Gfq9EOp9dB+nJbNrujBgav1l13CorkBG2rgD+NRivV1otclXnPsIj85ySrc6xk0VDN3zogLAdLw0f+fNOaK17N8GmhPbCu/iX2Vjem/fdjh4NPC4pSNDei2MoWUfqGBmoTxuQEsZeywigwV2c51W9RZuwo8X810N3ehVcBmmNyLjbKmYT+8Kp+dJbaf/ErFnvdZl06mX2bgyxFakv5iQY3dwX4WYHvl77c4="
|
||||
skip_cleanup: true
|
||||
overwrite: true
|
||||
file: bintray-release.json
|
||||
on:
|
||||
condition: "$DEPLOYMENT == 1"
|
||||
repo: Nheko-Reborn/nheko
|
||||
tags: true
|
||||
tags: false
|
||||
all_branches: true
|
||||
deploy:
|
||||
- skip_cleanup: true
|
||||
overwrite: true
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: JROFCxI1Dj0j8GKftCk1M16PovGmbCQb/i6JKm+YKWIhoYoMJBFl3TzhN0D0KlT8VeClZ0WV4MOom6elAkxlYTGR1kcoJ5ESt/AS0B1ULxq2exbmqzqJgJJBb65JVo4nglLHZPnUHOY5s/QGtg05nPeexcK8b3lFvhMRI+Y5jqX9i4FGsEBk6tG2OLfXB0odU8f6rhEeIGWgJw1LVyiTk3VoQcJtBi7Vsg3p4othMaLDlkVHsepNY+xSO14NbNpUjXSzYWZJEM9HqCOaOlAjZR5Q0Ad365TqN9zj6NOVwxEdN4Zl3/Ux838Or6TobhdhGjqqO2JmWt6C+xV4XJ9wX+8LPb+hYYVBrItp32g3grtW/e4nNsp4j3nm1P87kzKPxC4oAaskyn0dlwC4Vo3LH67beQiceAIuM9ywej4Zwr94+MeKjIVtqI6Qz7Tjlt1pFGI1lmfkKQOXiFlkwPbyCPV3smpJ1WSOC4Npkht6tFPBlLV2DFySYUMRAdH2RwBxWhjzwsSJlx/dEKUUL5yffKtg2tANM6aCCyXMEqEXXVkFe9e9ymPbGmmQuf56xo3rYQj5BcQWA9JHAancqLkxoR0rbRBBmai5qDQP7rBss/HR7Uec5xSnYkS6YYI9zpZ+FTfPa7lnVI3c8hj+ukua1EnsYytB8F8l95jrO8fnTxU=
|
||||
secure: "JROFCxI1Dj0j8GKftCk1M16PovGmbCQb/i6JKm+YKWIhoYoMJBFl3TzhN0D0KlT8VeClZ0WV4MOom6elAkxlYTGR1kcoJ5ESt/AS0B1ULxq2exbmqzqJgJJBb65JVo4nglLHZPnUHOY5s/QGtg05nPeexcK8b3lFvhMRI+Y5jqX9i4FGsEBk6tG2OLfXB0odU8f6rhEeIGWgJw1LVyiTk3VoQcJtBi7Vsg3p4othMaLDlkVHsepNY+xSO14NbNpUjXSzYWZJEM9HqCOaOlAjZR5Q0Ad365TqN9zj6NOVwxEdN4Zl3/Ux838Or6TobhdhGjqqO2JmWt6C+xV4XJ9wX+8LPb+hYYVBrItp32g3grtW/e4nNsp4j3nm1P87kzKPxC4oAaskyn0dlwC4Vo3LH67beQiceAIuM9ywej4Zwr94+MeKjIVtqI6Qz7Tjlt1pFGI1lmfkKQOXiFlkwPbyCPV3smpJ1WSOC4Npkht6tFPBlLV2DFySYUMRAdH2RwBxWhjzwsSJlx/dEKUUL5yffKtg2tANM6aCCyXMEqEXXVkFe9e9ymPbGmmQuf56xo3rYQj5BcQWA9JHAancqLkxoR0rbRBBmai5qDQP7rBss/HR7Uec5xSnYkS6YYI9zpZ+FTfPa7lnVI3c8hj+ukua1EnsYytB8F8l95jrO8fnTxU="
|
||||
file_glob: true
|
||||
file:
|
||||
- nheko-${TRAVIS_TAG}-x86_64.AppImage
|
||||
- nheko-${VERSION}-x86_64.AppImage
|
||||
on:
|
||||
condition: "$TRAVIS_OS_NAME == linux && $DEPLOYMENT == 1"
|
||||
repo: Nheko-Reborn/nheko
|
||||
|
@ -108,11 +104,9 @@ deploy:
|
|||
overwrite: true
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: JROFCxI1Dj0j8GKftCk1M16PovGmbCQb/i6JKm+YKWIhoYoMJBFl3TzhN0D0KlT8VeClZ0WV4MOom6elAkxlYTGR1kcoJ5ESt/AS0B1ULxq2exbmqzqJgJJBb65JVo4nglLHZPnUHOY5s/QGtg05nPeexcK8b3lFvhMRI+Y5jqX9i4FGsEBk6tG2OLfXB0odU8f6rhEeIGWgJw1LVyiTk3VoQcJtBi7Vsg3p4othMaLDlkVHsepNY+xSO14NbNpUjXSzYWZJEM9HqCOaOlAjZR5Q0Ad365TqN9zj6NOVwxEdN4Zl3/Ux838Or6TobhdhGjqqO2JmWt6C+xV4XJ9wX+8LPb+hYYVBrItp32g3grtW/e4nNsp4j3nm1P87kzKPxC4oAaskyn0dlwC4Vo3LH67beQiceAIuM9ywej4Zwr94+MeKjIVtqI6Qz7Tjlt1pFGI1lmfkKQOXiFlkwPbyCPV3smpJ1WSOC4Npkht6tFPBlLV2DFySYUMRAdH2RwBxWhjzwsSJlx/dEKUUL5yffKtg2tANM6aCCyXMEqEXXVkFe9e9ymPbGmmQuf56xo3rYQj5BcQWA9JHAancqLkxoR0rbRBBmai5qDQP7rBss/HR7Uec5xSnYkS6YYI9zpZ+FTfPa7lnVI3c8hj+ukua1EnsYytB8F8l95jrO8fnTxU=
|
||||
file: nheko-${TRAVIS_TAG}.dmg
|
||||
secure: "JROFCxI1Dj0j8GKftCk1M16PovGmbCQb/i6JKm+YKWIhoYoMJBFl3TzhN0D0KlT8VeClZ0WV4MOom6elAkxlYTGR1kcoJ5ESt/AS0B1ULxq2exbmqzqJgJJBb65JVo4nglLHZPnUHOY5s/QGtg05nPeexcK8b3lFvhMRI+Y5jqX9i4FGsEBk6tG2OLfXB0odU8f6rhEeIGWgJw1LVyiTk3VoQcJtBi7Vsg3p4othMaLDlkVHsepNY+xSO14NbNpUjXSzYWZJEM9HqCOaOlAjZR5Q0Ad365TqN9zj6NOVwxEdN4Zl3/Ux838Or6TobhdhGjqqO2JmWt6C+xV4XJ9wX+8LPb+hYYVBrItp32g3grtW/e4nNsp4j3nm1P87kzKPxC4oAaskyn0dlwC4Vo3LH67beQiceAIuM9ywej4Zwr94+MeKjIVtqI6Qz7Tjlt1pFGI1lmfkKQOXiFlkwPbyCPV3smpJ1WSOC4Npkht6tFPBlLV2DFySYUMRAdH2RwBxWhjzwsSJlx/dEKUUL5yffKtg2tANM6aCCyXMEqEXXVkFe9e9ymPbGmmQuf56xo3rYQj5BcQWA9JHAancqLkxoR0rbRBBmai5qDQP7rBss/HR7Uec5xSnYkS6YYI9zpZ+FTfPa7lnVI3c8hj+ukua1EnsYytB8F8l95jrO8fnTxU="
|
||||
file: nheko-${VERSION}.dmg
|
||||
on:
|
||||
condition: "$TRAVIS_OS_NAME == osx && $DEPLOYMENT == 1"
|
||||
repo: Nheko-Reborn/nheko
|
||||
tags: true
|
||||
key:
|
||||
secure: q4V4k6mAEDBgA/13NiCw+5/Jh7/xmtRBybFSr/0I6JTatkaLs2pj4zIRyHIrBVZtOd1oFVmq6aDHXXR+fbSo1Euj3s5Eo+7TTAqKAlyRMcme/+0S0bfZfisA+6LskZcmacq1FzEkAXd5OjMXB6rIakDC1sgkgo5bpM9w5r/9ZFXXgCfDzW07LJOypDyph0Gg4rlU22o5fAcKmuglTgbJWceznv46HcHvW1s2JzkJQugpAh8LkpiEkuNnH1wZ0WDI1wQQFI+ti5GSBkHicS2kgkOL3IlCvfzS0ym85XF1FTncqDEClxudwWOhVm3qpSOm28I+lB4i0ha1LNzsl4S8ClVTxJRJMJBHmLmkh3lOasAn6v8Vc2WASygfnTC2VGMaRWYMfphLm7e1CcT8OPfoNcEJLvR6YTxgm7AadomOV4f8q9FUwvrkyJkbR+sV+DkJ5yQ/uF1pDOMnUUzjDYpCfYXEECqh8gH8iUXhWabrJyaFlzZaOsai/ujyepLOkUtJaGcOrnCHlOQlfXgBhmOCUFau8ByJhSrHGGlBPb9JhC/jzWq++dmN/5zn1coc4kNqKB55h1AFVtTTW7t14RzNKER2/opl7LFoywvyMyERusmxHfGzNihFHO4GoBY+WtEpphCAdqCjLaJM95w9spQ0sgR0/qy4883MhTWBctT9K6s=
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
|
||||
### Other changes
|
||||
- Removed room re-ordering option.
|
||||
- Removed built-in emoji picker.
|
||||
- Removed bundled fonts.
|
||||
|
||||
## [0.6.1] - 2018-09-26
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.1)
|
||||
cmake_minimum_required(VERSION 3.11)
|
||||
|
||||
option(APPVEYOR_BUILD "Build on appveyor" OFF)
|
||||
option(ASAN "Compile with address sanitizers" OFF)
|
||||
|
@ -183,6 +183,10 @@ set(SRC_FILES
|
|||
src/dialogs/RoomSettings.cpp
|
||||
|
||||
# Emoji
|
||||
src/emoji/Category.cpp
|
||||
src/emoji/ItemDelegate.cpp
|
||||
src/emoji/Panel.cpp
|
||||
src/emoji/PickButton.cpp
|
||||
src/emoji/Provider.cpp
|
||||
|
||||
# Timeline
|
||||
|
@ -289,6 +293,9 @@ include_directories(SYSTEM ${TWEENY_INCLUDE_DIR})
|
|||
include_directories(${CMAKE_SOURCE_DIR}/src)
|
||||
include_directories(${Boost_INCLUDE_DIRS})
|
||||
|
||||
# local inclue directory
|
||||
include_directories(includes)
|
||||
|
||||
qt5_wrap_cpp(MOC_HEADERS
|
||||
# Dialogs
|
||||
src/dialogs/CreateRoom.h
|
||||
|
@ -305,6 +312,12 @@ qt5_wrap_cpp(MOC_HEADERS
|
|||
src/dialogs/ReCaptcha.h
|
||||
src/dialogs/RoomSettings.h
|
||||
|
||||
# Emoji
|
||||
src/emoji/Category.h
|
||||
src/emoji/ItemDelegate.h
|
||||
src/emoji/Panel.h
|
||||
src/emoji/PickButton.h
|
||||
|
||||
# Timeline
|
||||
src/timeline/TimelineItem.h
|
||||
src/timeline/TimelineView.h
|
||||
|
|
|
@ -264,5 +264,11 @@ Here is a screen shot to get a feel for the UI, but things will probably change.
|
|||
|
||||
![nheko](https://dl.dropboxusercontent.com/s/3zjcezdtk8kqe4i/nheko-v0.4.0.png)
|
||||
|
||||
### Third party
|
||||
|
||||
- [Emoji One](http://emojione.com)
|
||||
- [Font Awesome](http://fontawesome.io/)
|
||||
- [Open Sans](https://fonts.google.com/specimen/Open+Sans)
|
||||
|
||||
[Matrix]:https://matrix.org
|
||||
[Riot]:https://riot.im
|
||||
|
|
7
deps/CMakeLists.txt
vendored
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.1)
|
||||
cmake_minimum_required(VERSION 3.11)
|
||||
project(NHEKO_DEPS)
|
||||
|
||||
# Point CMake at any custom modules we may ship
|
||||
|
@ -30,6 +30,11 @@ option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdbxx." ${USE_BUNDLED})
|
|||
option(USE_BUNDLED_MATRIX_CLIENT "Use the bundled version of mtxclient."
|
||||
${USE_BUNDLED})
|
||||
|
||||
if(USE_BUNDLED_BOOST)
|
||||
# bundled boost is 1.68, which requires CMake 3.12 or greater.
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
endif()
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
set(BOOST_URL
|
||||
|
|
17
includes/jdenticoninterface.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#ifndef JDENTICONINTERFACE_H
|
||||
#define JDENTICONINTERFACE_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class JdenticonInterface
|
||||
{
|
||||
public:
|
||||
virtual ~JdenticonInterface() {}
|
||||
virtual QString generate(const QString &message, uint16_t size) = 0;
|
||||
};
|
||||
|
||||
#define JdenticonInterface_iid "redsky17.Qt.JdenticonInterface"
|
||||
|
||||
Q_DECLARE_INTERFACE(JdenticonInterface, JdenticonInterface_iid)
|
||||
|
||||
#endif // JDENTICONINTERFACE_H
|
BIN
resources/fonts/EmojiOne/emojione-android.ttf
Normal file
202
resources/fonts/OpenSans/LICENSE.txt
Normal file
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
BIN
resources/fonts/OpenSans/OpenSans-Bold.ttf
Normal file
BIN
resources/fonts/OpenSans/OpenSans-BoldItalic.ttf
Normal file
BIN
resources/fonts/OpenSans/OpenSans-ExtraBold.ttf
Normal file
BIN
resources/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf
Normal file
BIN
resources/fonts/OpenSans/OpenSans-Italic.ttf
Normal file
BIN
resources/fonts/OpenSans/OpenSans-Light.ttf
Normal file
BIN
resources/fonts/OpenSans/OpenSans-LightItalic.ttf
Normal file
BIN
resources/fonts/OpenSans/OpenSans-Regular.ttf
Normal file
BIN
resources/fonts/OpenSans/OpenSans-Semibold.ttf
Normal file
BIN
resources/fonts/OpenSans/OpenSans-SemiboldItalic.ttf
Normal file
BIN
resources/icons/emoji-categories/activity.png
Normal file
After Width: | Height: | Size: 603 B |
BIN
resources/icons/emoji-categories/activity@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
resources/icons/emoji-categories/flags.png
Normal file
After Width: | Height: | Size: 416 B |
BIN
resources/icons/emoji-categories/flags@2x.png
Normal file
After Width: | Height: | Size: 824 B |
BIN
resources/icons/emoji-categories/foods.png
Normal file
After Width: | Height: | Size: 537 B |
BIN
resources/icons/emoji-categories/foods@2x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
resources/icons/emoji-categories/nature.png
Normal file
After Width: | Height: | Size: 667 B |
BIN
resources/icons/emoji-categories/nature@2x.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
resources/icons/emoji-categories/objects.png
Normal file
After Width: | Height: | Size: 606 B |
BIN
resources/icons/emoji-categories/objects@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
resources/icons/emoji-categories/people.png
Normal file
After Width: | Height: | Size: 581 B |
BIN
resources/icons/emoji-categories/people@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
resources/icons/emoji-categories/symbols.png
Normal file
After Width: | Height: | Size: 504 B |
BIN
resources/icons/emoji-categories/symbols@2x.png
Normal file
After Width: | Height: | Size: 1,001 B |
BIN
resources/icons/emoji-categories/travel.png
Normal file
After Width: | Height: | Size: 439 B |
BIN
resources/icons/emoji-categories/travel@2x.png
Normal file
After Width: | Height: | Size: 840 B |
|
@ -63,6 +63,22 @@
|
|||
<file>icons/ui/edit.png</file>
|
||||
<file>icons/ui/edit@2x.png</file>
|
||||
|
||||
<file>icons/emoji-categories/people.png</file>
|
||||
<file>icons/emoji-categories/people@2x.png</file>
|
||||
<file>icons/emoji-categories/nature.png</file>
|
||||
<file>icons/emoji-categories/nature@2x.png</file>
|
||||
<file>icons/emoji-categories/foods.png</file>
|
||||
<file>icons/emoji-categories/foods@2x.png</file>
|
||||
<file>icons/emoji-categories/activity.png</file>
|
||||
<file>icons/emoji-categories/activity@2x.png</file>
|
||||
<file>icons/emoji-categories/travel.png</file>
|
||||
<file>icons/emoji-categories/travel@2x.png</file>
|
||||
<file>icons/emoji-categories/objects.png</file>
|
||||
<file>icons/emoji-categories/objects@2x.png</file>
|
||||
<file>icons/emoji-categories/symbols.png</file>
|
||||
<file>icons/emoji-categories/symbols@2x.png</file>
|
||||
<file>icons/emoji-categories/flags.png</file>
|
||||
<file>icons/emoji-categories/flags@2x.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="/logos">
|
||||
<file>nheko.png</file>
|
||||
|
@ -83,6 +99,13 @@
|
|||
<file>nheko-32.png</file>
|
||||
<file>nheko-16.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="/fonts">
|
||||
<file>fonts/OpenSans/OpenSans-Regular.ttf</file>
|
||||
<file>fonts/OpenSans/OpenSans-Italic.ttf</file>
|
||||
<file>fonts/OpenSans/OpenSans-Bold.ttf</file>
|
||||
<file>fonts/OpenSans/OpenSans-Semibold.ttf</file>
|
||||
<file>fonts/EmojiOne/emojione-android.ttf</file>
|
||||
</qresource>
|
||||
<qresource prefix="/styles">
|
||||
<file>styles/system.qss</file>
|
||||
<file>styles/nheko.qss</file>
|
||||
|
|
|
@ -3,6 +3,10 @@ QLabel {
|
|||
color: #caccd1;
|
||||
}
|
||||
|
||||
TimelineItem {
|
||||
qproperty-backgroundColor: #202228;
|
||||
}
|
||||
|
||||
#chatPage,
|
||||
#chatPage > * {
|
||||
background-color: #202228;
|
||||
|
@ -86,6 +90,7 @@ RaisedButton {
|
|||
}
|
||||
|
||||
RoomInfoListItem {
|
||||
qproperty-mentionedColor: #a82353;
|
||||
qproperty-highlightedBackgroundColor: #4d84c7;
|
||||
qproperty-hoverBackgroundColor: rgba(230, 230, 230, 30);
|
||||
qproperty-backgroundColor: #2d3139;
|
||||
|
@ -188,6 +193,18 @@ RegisterPage {
|
|||
color: #caccd1;
|
||||
}
|
||||
|
||||
emoji--Panel,
|
||||
emoji--Panel > * {
|
||||
background-color: #202228;
|
||||
color: #caccd1;
|
||||
}
|
||||
|
||||
emoji--Category,
|
||||
emoji--Category > * {
|
||||
background-color: #2d3139;
|
||||
color: #caccd1;
|
||||
}
|
||||
|
||||
FloatingButton {
|
||||
qproperty-backgroundColor: #2d3139;
|
||||
qproperty-foregroundColor: white;
|
||||
|
|
|
@ -3,6 +3,10 @@ QLabel {
|
|||
color: #333;
|
||||
}
|
||||
|
||||
TimelineItem {
|
||||
qproperty-backgroundColor: white;
|
||||
}
|
||||
|
||||
#chatPage,
|
||||
#chatPage > * {
|
||||
background-color: white;
|
||||
|
@ -83,6 +87,7 @@ RaisedButton {
|
|||
}
|
||||
|
||||
RoomInfoListItem {
|
||||
qproperty-mentionedColor: #a82353;
|
||||
qproperty-highlightedBackgroundColor: #38A3D8;
|
||||
qproperty-hoverBackgroundColor: rgba(200, 200, 200, 70);
|
||||
qproperty-hoverTitleColor: #f2f5f8;
|
||||
|
@ -185,6 +190,18 @@ RegisterPage {
|
|||
color: #333;
|
||||
}
|
||||
|
||||
emoji--Panel,
|
||||
emoji--Panel > * {
|
||||
background-color: #eee;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
emoji--Category,
|
||||
emoji--Category > * {
|
||||
background-color: white;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
FloatingButton {
|
||||
qproperty-backgroundColor: #efefef;
|
||||
qproperty-foregroundColor: black;
|
||||
|
|
|
@ -3,6 +3,10 @@ TypingDisplay {
|
|||
qproperty-backgroundColor: palette(window);
|
||||
}
|
||||
|
||||
TimelineItem {
|
||||
qproperty-backgroundColor: palette(window);
|
||||
}
|
||||
|
||||
TimelineView,
|
||||
TimelineView > * {
|
||||
border: none;
|
||||
|
@ -82,6 +86,7 @@ QListWidget {
|
|||
}
|
||||
|
||||
RoomInfoListItem {
|
||||
qproperty-mentionedColor: palette(alternate-base);
|
||||
qproperty-highlightedBackgroundColor: palette(highlight);
|
||||
qproperty-hoverBackgroundColor: palette(base);
|
||||
qproperty-backgroundColor: palette(window);
|
||||
|
|
|
@ -2059,6 +2059,7 @@ Cache::roomMembers(const std::string &room_id)
|
|||
|
||||
QHash<QString, QString> Cache::DisplayNames;
|
||||
QHash<QString, QString> Cache::AvatarUrls;
|
||||
QHash<QString, QString> Cache::UserColors;
|
||||
|
||||
QString
|
||||
Cache::displayName(const QString &room_id, const QString &user_id)
|
||||
|
@ -2090,6 +2091,16 @@ Cache::avatarUrl(const QString &room_id, const QString &user_id)
|
|||
return QString();
|
||||
}
|
||||
|
||||
QString
|
||||
Cache::userColor(const QString &user_id)
|
||||
{
|
||||
if (UserColors.contains(user_id)) {
|
||||
return UserColors[user_id];
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
void
|
||||
Cache::insertDisplayName(const QString &room_id,
|
||||
const QString &user_id,
|
||||
|
@ -2119,3 +2130,21 @@ Cache::removeAvatarUrl(const QString &room_id, const QString &user_id)
|
|||
auto fmt = QString("%1 %2").arg(room_id).arg(user_id);
|
||||
AvatarUrls.remove(fmt);
|
||||
}
|
||||
|
||||
void
|
||||
Cache::insertUserColor(const QString &user_id, const QString &color_name)
|
||||
{
|
||||
UserColors.insert(user_id, color_name);
|
||||
}
|
||||
|
||||
void
|
||||
Cache::removeUserColor(const QString &user_id)
|
||||
{
|
||||
UserColors.remove(user_id);
|
||||
}
|
||||
|
||||
void
|
||||
Cache::clearUserColors()
|
||||
{
|
||||
UserColors.clear();
|
||||
}
|
|
@ -282,13 +282,16 @@ public:
|
|||
|
||||
static QHash<QString, QString> DisplayNames;
|
||||
static QHash<QString, QString> AvatarUrls;
|
||||
static QHash<QString, QString> UserColors;
|
||||
|
||||
static std::string displayName(const std::string &room_id, const std::string &user_id);
|
||||
static QString displayName(const QString &room_id, const QString &user_id);
|
||||
static QString avatarUrl(const QString &room_id, const QString &user_id);
|
||||
static QString userColor(const QString &user_id);
|
||||
|
||||
static void removeDisplayName(const QString &room_id, const QString &user_id);
|
||||
static void removeAvatarUrl(const QString &room_id, const QString &user_id);
|
||||
static void removeUserColor(const QString &user_id);
|
||||
|
||||
static void insertDisplayName(const QString &room_id,
|
||||
const QString &user_id,
|
||||
|
@ -296,6 +299,9 @@ public:
|
|||
static void insertAvatarUrl(const QString &room_id,
|
||||
const QString &user_id,
|
||||
const QString &avatar_url);
|
||||
static void insertUserColor(const QString &user_id, const QString &color_name);
|
||||
|
||||
static void clearUserColors();
|
||||
|
||||
//! Load saved data for the display names & avatars.
|
||||
void populateMembers();
|
||||
|
|
|
@ -546,7 +546,9 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||
|
||||
updateTypingUsers(room_id, room.second.ephemeral.typing);
|
||||
updateRoomNotificationCount(
|
||||
room_id, room.second.unread_notifications.notification_count);
|
||||
room_id,
|
||||
room.second.unread_notifications.notification_count,
|
||||
room.second.unread_notifications.highlight_count);
|
||||
|
||||
if (room.second.unread_notifications.notification_count > 0)
|
||||
hasNotifications = true;
|
||||
|
@ -908,9 +910,11 @@ ChatPage::setGroupViewState(bool isEnabled)
|
|||
}
|
||||
|
||||
void
|
||||
ChatPage::updateRoomNotificationCount(const QString &room_id, uint16_t notification_count)
|
||||
ChatPage::updateRoomNotificationCount(const QString &room_id,
|
||||
uint16_t notification_count,
|
||||
uint16_t highlight_count)
|
||||
{
|
||||
room_list_->updateUnreadMessageCount(room_id, notification_count);
|
||||
room_list_->updateUnreadMessageCount(room_id, notification_count, highlight_count);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -1098,7 +1102,8 @@ ChatPage::trySync()
|
|||
void
|
||||
ChatPage::joinRoom(const QString &room)
|
||||
{
|
||||
const auto room_id = room.toStdString();
|
||||
// Percent escape the room ID
|
||||
const auto room_id = QUrl::toPercentEncoding(room).toStdString();
|
||||
|
||||
http::client()->join_room(
|
||||
room_id, [this, room_id](const nlohmann::json &, mtx::http::RequestErr err) {
|
||||
|
|
|
@ -148,6 +148,7 @@ signals:
|
|||
const QImage &icon);
|
||||
|
||||
void updateGroupsInfo(const mtx::responses::JoinedGroups &groups);
|
||||
void themeChanged();
|
||||
|
||||
private slots:
|
||||
void showUnreadMessageNotification(int count);
|
||||
|
@ -196,7 +197,9 @@ private:
|
|||
Memberships getMemberships(const std::vector<Collection> &events) const;
|
||||
|
||||
//! Update the room with the new notification count.
|
||||
void updateRoomNotificationCount(const QString &room_id, uint16_t notification_count);
|
||||
void updateRoomNotificationCount(const QString &room_id,
|
||||
uint16_t notification_count,
|
||||
uint16_t highlight_count);
|
||||
//! Send desktop notification for the received messages.
|
||||
void sendDesktopNotifications(const mtx::responses::Notifications &);
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include <QApplication>
|
||||
#include <QLayout>
|
||||
#include <QPluginLoader>
|
||||
#include <QSettings>
|
||||
#include <QShortcut>
|
||||
|
||||
|
@ -112,7 +113,11 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
|
||||
connect(
|
||||
userSettingsPage_, SIGNAL(trayOptionChanged(bool)), trayIcon_, SLOT(setVisible(bool)));
|
||||
|
||||
connect(userSettingsPage_, &UserSettingsPage::themeChanged, this, []() {
|
||||
Cache::clearUserColors();
|
||||
});
|
||||
connect(
|
||||
userSettingsPage_, &UserSettingsPage::themeChanged, chat_page_, &ChatPage::themeChanged);
|
||||
connect(trayIcon_,
|
||||
SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
|
||||
this,
|
||||
|
@ -162,6 +167,10 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
|
||||
showChatPage();
|
||||
}
|
||||
|
||||
if (loadJdenticonPlugin()) {
|
||||
nhlog::ui()->info("loaded jdenticon.");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -475,3 +484,27 @@ MainWindow::showDialog(QWidget *dialog)
|
|||
dialog->raise();
|
||||
dialog->show();
|
||||
}
|
||||
|
||||
bool
|
||||
MainWindow::loadJdenticonPlugin()
|
||||
{
|
||||
QDir pluginsDir(qApp->applicationDirPath());
|
||||
|
||||
bool plugins = pluginsDir.cd("plugins");
|
||||
if (plugins) {
|
||||
foreach (QString fileName, pluginsDir.entryList(QDir::Files)) {
|
||||
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
|
||||
QObject *plugin = pluginLoader.instance();
|
||||
if (plugin) {
|
||||
jdenticonInteface_ = qobject_cast<JdenticonInterface *>(plugin);
|
||||
if (jdenticonInteface_) {
|
||||
nhlog::ui()->info("Found jdenticon plugin.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nhlog::ui()->info("jdenticon plugin not found.");
|
||||
return false;
|
||||
}
|
|
@ -31,6 +31,8 @@
|
|||
#include "dialogs/UserProfile.h"
|
||||
#include "ui/OverlayModal.h"
|
||||
|
||||
#include "jdenticoninterface.h"
|
||||
|
||||
class ChatPage;
|
||||
class LoadingIndicator;
|
||||
class OverlayModal;
|
||||
|
@ -129,6 +131,8 @@ private slots:
|
|||
void removeOverlayProgressBar();
|
||||
|
||||
private:
|
||||
bool loadJdenticonPlugin();
|
||||
|
||||
void showDialog(QWidget *dialog);
|
||||
bool hasActiveUser();
|
||||
void restoreWindowSize();
|
||||
|
@ -158,4 +162,6 @@ private:
|
|||
//! Overlay modal used to project other widgets.
|
||||
OverlayModal *modal_ = nullptr;
|
||||
LoadingIndicator *spinner_ = nullptr;
|
||||
|
||||
JdenticonInterface *jdenticonInteface_ = nullptr;
|
||||
};
|
||||
|
|
|
@ -101,6 +101,7 @@ RoomInfoListItem::RoomInfoListItem(QString room_id, RoomInfo info, QWidget *pare
|
|||
, roomName_{QString::fromStdString(std::move(info.name))}
|
||||
, isPressed_(false)
|
||||
, unreadMsgCount_(0)
|
||||
, unreadHighlightedMsgCount_(0)
|
||||
{
|
||||
init(parent);
|
||||
|
||||
|
@ -301,7 +302,11 @@ RoomInfoListItem::paintEvent(QPaintEvent *event)
|
|||
if (unreadMsgCount_ > 0) {
|
||||
QBrush brush;
|
||||
brush.setStyle(Qt::SolidPattern);
|
||||
if (unreadHighlightedMsgCount_ > 0) {
|
||||
brush.setColor(mentionedColor());
|
||||
} else {
|
||||
brush.setColor(bubbleBgColor());
|
||||
}
|
||||
|
||||
if (isPressed_)
|
||||
brush.setColor(bubbleFgColor());
|
||||
|
@ -354,9 +359,10 @@ RoomInfoListItem::paintEvent(QPaintEvent *event)
|
|||
}
|
||||
|
||||
void
|
||||
RoomInfoListItem::updateUnreadMessageCount(int count)
|
||||
RoomInfoListItem::updateUnreadMessageCount(int count, int highlightedCount)
|
||||
{
|
||||
unreadMsgCount_ = count;
|
||||
unreadHighlightedMsgCount_ = highlightedCount;
|
||||
update();
|
||||
}
|
||||
|
||||
|
|
|
@ -59,14 +59,15 @@ class RoomInfoListItem : public QWidget
|
|||
Q_PROPERTY(QColor hoverTitleColor READ hoverTitleColor WRITE setHoverTitleColor)
|
||||
Q_PROPERTY(QColor hoverSubtitleColor READ hoverSubtitleColor WRITE setHoverSubtitleColor)
|
||||
|
||||
Q_PROPERTY(QColor mentionedColor READ mentionedColor WRITE setMentionedColor)
|
||||
Q_PROPERTY(QColor btnColor READ btnColor WRITE setBtnColor)
|
||||
Q_PROPERTY(QColor btnTextColor READ btnTextColor WRITE setBtnTextColor)
|
||||
|
||||
public:
|
||||
RoomInfoListItem(QString room_id, RoomInfo info, QWidget *parent = 0);
|
||||
|
||||
void updateUnreadMessageCount(int count);
|
||||
void clearUnreadMessageCount() { updateUnreadMessageCount(0); };
|
||||
void updateUnreadMessageCount(int count, int highlightedCount);
|
||||
void clearUnreadMessageCount() { updateUnreadMessageCount(0, 0); };
|
||||
|
||||
QString roomId() { return roomId_; }
|
||||
bool isPressed() const { return isPressed_; }
|
||||
|
@ -97,6 +98,7 @@ public:
|
|||
|
||||
QColor bubbleFgColor() const { return bubbleFgColor_; }
|
||||
QColor bubbleBgColor() const { return bubbleBgColor_; }
|
||||
QColor mentionedColor() const { return mentionedFontColor_; }
|
||||
|
||||
void setHighlightedBackgroundColor(QColor &color) { highlightedBackgroundColor_ = color; }
|
||||
void setHoverBackgroundColor(QColor &color) { hoverBackgroundColor_ = color; }
|
||||
|
@ -120,6 +122,7 @@ public:
|
|||
|
||||
void setBubbleFgColor(QColor &color) { bubbleFgColor_ = color; }
|
||||
void setBubbleBgColor(QColor &color) { bubbleBgColor_ = color; }
|
||||
void setMentionedColor(QColor &color) { mentionedFontColor_ = color; }
|
||||
|
||||
void setRoomName(const QString &name) { roomName_ = name; }
|
||||
void setRoomType(bool isInvite)
|
||||
|
@ -185,6 +188,7 @@ private:
|
|||
bool hasUnreadMessages_ = true;
|
||||
|
||||
int unreadMsgCount_ = 0;
|
||||
int unreadHighlightedMsgCount_ = 0;
|
||||
|
||||
QColor highlightedBackgroundColor_;
|
||||
QColor hoverBackgroundColor_;
|
||||
|
@ -206,6 +210,7 @@ private:
|
|||
QRectF declineBtnRegion_;
|
||||
|
||||
// Fonts
|
||||
QColor mentionedFontColor_;
|
||||
QFont unreadCountFont_;
|
||||
int bubbleDiameter_;
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@ RoomList::removeRoom(const QString &room_id, bool reset)
|
|||
}
|
||||
|
||||
void
|
||||
RoomList::updateUnreadMessageCount(const QString &roomid, int count)
|
||||
RoomList::updateUnreadMessageCount(const QString &roomid, int count, int highlightedCount)
|
||||
{
|
||||
if (!roomExists(roomid)) {
|
||||
nhlog::ui()->warn("updateUnreadMessageCount: unknown room_id {}",
|
||||
|
@ -151,7 +151,7 @@ RoomList::updateUnreadMessageCount(const QString &roomid, int count)
|
|||
return;
|
||||
}
|
||||
|
||||
rooms_[roomid]->updateUnreadMessageCount(count);
|
||||
rooms_[roomid]->updateUnreadMessageCount(count, highlightedCount);
|
||||
|
||||
calculateUnreadMessageCount();
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ signals:
|
|||
public slots:
|
||||
void updateRoomAvatar(const QString &roomid, const QPixmap &img);
|
||||
void highlightSelectedRoom(const QString &room_id);
|
||||
void updateUnreadMessageCount(const QString &roomid, int count);
|
||||
void updateUnreadMessageCount(const QString &roomid, int count, int highlightedCount);
|
||||
void updateRoomDescription(const QString &roomid, const DescInfo &info);
|
||||
void closeJoinRoomDialog(bool isJoining, QString roomAlias);
|
||||
void updateReadStatus(const std::map<QString, bool> &status);
|
||||
|
|
|
@ -513,8 +513,22 @@ TextInputWidget::TextInputWidget(QWidget *parent)
|
|||
sendMessageBtn_->setIcon(send_message_icon);
|
||||
sendMessageBtn_->setIconSize(QSize(ButtonHeight, ButtonHeight));
|
||||
|
||||
emojiBtn_ = new emoji::PickButton(this);
|
||||
emojiBtn_->setToolTip(tr("Emoji"));
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
// macOS has a native emoji picker.
|
||||
emojiBtn_->hide();
|
||||
#endif
|
||||
|
||||
QIcon emoji_icon;
|
||||
emoji_icon.addFile(":/icons/icons/ui/smile.png");
|
||||
emojiBtn_->setIcon(emoji_icon);
|
||||
emojiBtn_->setIconSize(QSize(ButtonHeight, ButtonHeight));
|
||||
|
||||
topLayout_->addWidget(sendFileBtn_);
|
||||
topLayout_->addWidget(input_);
|
||||
topLayout_->addWidget(emojiBtn_);
|
||||
topLayout_->addWidget(sendMessageBtn_);
|
||||
|
||||
setLayout(topLayout_);
|
||||
|
@ -527,6 +541,11 @@ TextInputWidget::TextInputWidget(QWidget *parent)
|
|||
connect(input_, &FilteredTextEdit::audio, this, &TextInputWidget::uploadAudio);
|
||||
connect(input_, &FilteredTextEdit::video, this, &TextInputWidget::uploadVideo);
|
||||
connect(input_, &FilteredTextEdit::file, this, &TextInputWidget::uploadFile);
|
||||
connect(emojiBtn_,
|
||||
SIGNAL(emojiSelected(const QString &)),
|
||||
this,
|
||||
SLOT(addSelectedEmoji(const QString &)));
|
||||
|
||||
connect(input_, &FilteredTextEdit::startedTyping, this, &TextInputWidget::startedTyping);
|
||||
|
||||
connect(input_, &FilteredTextEdit::stoppedTyping, this, &TextInputWidget::stoppedTyping);
|
||||
|
@ -535,6 +554,22 @@ TextInputWidget::TextInputWidget(QWidget *parent)
|
|||
input_, &FilteredTextEdit::startedUpload, this, &TextInputWidget::showUploadSpinner);
|
||||
}
|
||||
|
||||
void
|
||||
TextInputWidget::addSelectedEmoji(const QString &emoji)
|
||||
{
|
||||
QTextCursor cursor = input_->textCursor();
|
||||
|
||||
QTextCharFormat charfmt;
|
||||
input_->setCurrentCharFormat(charfmt);
|
||||
|
||||
input_->insertPlainText(emoji);
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
|
||||
input_->setCurrentCharFormat(charfmt);
|
||||
|
||||
input_->show();
|
||||
}
|
||||
|
||||
void
|
||||
TextInputWidget::command(QString command, QString args)
|
||||
{
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
#include "SuggestionsPopup.h"
|
||||
#include "dialogs/PreviewUploadOverlay.h"
|
||||
#include "emoji/PickButton.h"
|
||||
|
||||
namespace dialogs {
|
||||
class PreviewUploadOverlay;
|
||||
|
@ -159,6 +160,9 @@ public slots:
|
|||
void focusLineEdit() { input_->setFocus(); }
|
||||
void addReply(const QString &username, const QString &msg);
|
||||
|
||||
private slots:
|
||||
void addSelectedEmoji(const QString &emoji);
|
||||
|
||||
signals:
|
||||
void sendTextMessage(QString msg);
|
||||
void sendEmoteMessage(QString msg);
|
||||
|
@ -189,6 +193,7 @@ private:
|
|||
|
||||
FlatButton *sendFileBtn_;
|
||||
FlatButton *sendMessageBtn_;
|
||||
emoji::PickButton *emojiBtn_;
|
||||
|
||||
QColor borderColor_;
|
||||
};
|
||||
|
|
|
@ -49,7 +49,7 @@ UserSettings::load()
|
|||
isTypingNotificationsEnabled_ = settings.value("user/typing_notifications", true).toBool();
|
||||
isReadReceiptsEnabled_ = settings.value("user/read_receipts", true).toBool();
|
||||
theme_ = settings.value("user/theme", "light").toString();
|
||||
|
||||
font_ = settings.value("user/font_family", "default").toString();
|
||||
baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble();
|
||||
|
||||
applyTheme();
|
||||
|
@ -62,6 +62,13 @@ UserSettings::setFontSize(double size)
|
|||
save();
|
||||
}
|
||||
|
||||
void
|
||||
UserSettings::setFontFamily(QString family)
|
||||
{
|
||||
font_ = family;
|
||||
save();
|
||||
}
|
||||
|
||||
void
|
||||
UserSettings::setTheme(QString theme)
|
||||
{
|
||||
|
@ -106,6 +113,7 @@ UserSettings::save()
|
|||
settings.setValue("group_view", isGroupViewEnabled_);
|
||||
settings.setValue("desktop_notifications", hasDesktopNotifications_);
|
||||
settings.setValue("theme", theme());
|
||||
settings.setValue("font_family", font_);
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
|
@ -220,6 +228,23 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
|
|||
fontSizeOptionLayout->addWidget(fontSizeLabel);
|
||||
fontSizeOptionLayout->addWidget(fontSizeCombo_, 0, Qt::AlignRight);
|
||||
|
||||
auto fontFamilyOptionLayout = new QHBoxLayout;
|
||||
fontFamilyOptionLayout->setContentsMargins(0, OptionMargin, 0, OptionMargin);
|
||||
auto fontFamilyLabel = new QLabel(tr("Font Family"), this);
|
||||
fontFamilyLabel->setFont(font);
|
||||
fontSelectionCombo_ = new QComboBox(this);
|
||||
QFontDatabase fontDb;
|
||||
auto fontFamilies = fontDb.families();
|
||||
for (const auto &family : fontFamilies) {
|
||||
fontSelectionCombo_->addItem(family);
|
||||
}
|
||||
|
||||
int fontIndex = fontSelectionCombo_->findText(settings_->font());
|
||||
fontSelectionCombo_->setCurrentIndex(fontIndex);
|
||||
|
||||
fontFamilyOptionLayout->addWidget(fontFamilyLabel);
|
||||
fontFamilyOptionLayout->addWidget(fontSelectionCombo_, 0, Qt::AlignRight);
|
||||
|
||||
auto themeOptionLayout_ = new QHBoxLayout;
|
||||
themeOptionLayout_->setContentsMargins(0, OptionMargin, 0, OptionMargin);
|
||||
auto themeLabel_ = new QLabel(tr("Theme"), this);
|
||||
|
@ -229,6 +254,11 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
|
|||
themeCombo_->addItem("Dark");
|
||||
themeCombo_->addItem("System");
|
||||
|
||||
QString themeStr = settings_->theme();
|
||||
themeStr.replace(0, 1, themeStr[0].toUpper());
|
||||
int themeIndex = themeCombo_->findText(themeStr);
|
||||
themeCombo_->setCurrentIndex(themeIndex);
|
||||
|
||||
themeOptionLayout_->addWidget(themeLabel_);
|
||||
themeOptionLayout_->addWidget(themeCombo_, 0, Qt::AlignRight);
|
||||
|
||||
|
@ -319,6 +349,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
|
|||
|
||||
mainLayout_->addLayout(scaleFactorOptionLayout);
|
||||
mainLayout_->addLayout(fontSizeOptionLayout);
|
||||
mainLayout_->addLayout(fontFamilyOptionLayout);
|
||||
mainLayout_->addWidget(new HorizontalLine(this));
|
||||
mainLayout_->addLayout(themeOptionLayout_);
|
||||
mainLayout_->addWidget(new HorizontalLine(this));
|
||||
|
@ -348,14 +379,19 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
|
|||
|
||||
connect(themeCombo_,
|
||||
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated),
|
||||
[this](const QString &text) { settings_->setTheme(text.toLower()); });
|
||||
[this](const QString &text) {
|
||||
settings_->setTheme(text.toLower());
|
||||
emit themeChanged();
|
||||
});
|
||||
connect(scaleFactorCombo_,
|
||||
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated),
|
||||
[](const QString &factor) { utils::setScaleFactor(factor.toFloat()); });
|
||||
connect(fontSizeCombo_,
|
||||
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated),
|
||||
[this](const QString &size) { settings_->setFontSize(size.trimmed().toDouble()); });
|
||||
|
||||
connect(fontSelectionCombo_,
|
||||
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::activated),
|
||||
[this](const QString &family) { settings_->setFontFamily(family.trimmed()); });
|
||||
connect(trayToggle_, &Toggle::toggled, this, [this](bool isDisabled) {
|
||||
settings_->setTray(!isDisabled);
|
||||
if (isDisabled) {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QFontDatabase>
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
#include <QLayout>
|
||||
|
@ -54,6 +55,7 @@ public:
|
|||
}
|
||||
|
||||
void setFontSize(double size);
|
||||
void setFontFamily(QString family);
|
||||
|
||||
void setGroupView(bool state)
|
||||
{
|
||||
|
@ -90,6 +92,7 @@ public:
|
|||
bool isReadReceiptsEnabled() const { return isReadReceiptsEnabled_; }
|
||||
bool hasDesktopNotifications() const { return hasDesktopNotifications_; }
|
||||
double fontSize() const { return baseFontSize_; }
|
||||
QString font() const { return font_; }
|
||||
|
||||
signals:
|
||||
void groupViewStateChanged(bool state);
|
||||
|
@ -103,6 +106,7 @@ private:
|
|||
bool isReadReceiptsEnabled_;
|
||||
bool hasDesktopNotifications_;
|
||||
double baseFontSize_;
|
||||
QString font_;
|
||||
};
|
||||
|
||||
class HorizontalLine : public QFrame
|
||||
|
@ -128,6 +132,7 @@ protected:
|
|||
signals:
|
||||
void moveBack();
|
||||
void trayOptionChanged(bool value);
|
||||
void themeChanged();
|
||||
|
||||
private slots:
|
||||
void importSessionKeys();
|
||||
|
@ -154,6 +159,7 @@ private:
|
|||
QComboBox *themeCombo_;
|
||||
QComboBox *scaleFactorCombo_;
|
||||
QComboBox *fontSizeCombo_;
|
||||
QComboBox *fontSelectionCombo_;
|
||||
|
||||
int sideMargin_ = 0;
|
||||
};
|
||||
|
|
122
src/Utils.cpp
|
@ -15,6 +15,8 @@
|
|||
|
||||
using TimelineEvent = mtx::events::collections::TimelineEvents;
|
||||
|
||||
QHash<QString, QString> authorColors_;
|
||||
|
||||
QString
|
||||
utils::localUser()
|
||||
{
|
||||
|
@ -382,6 +384,126 @@ utils::linkColor()
|
|||
return QPalette().color(QPalette::Link).name();
|
||||
}
|
||||
|
||||
int
|
||||
utils::hashQString(const QString &input)
|
||||
{
|
||||
auto hash = 0;
|
||||
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
hash = input.at(i).digitValue() + ((hash << 5) - hash);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
QString
|
||||
utils::generateContrastingHexColor(const QString &input, const QString &background)
|
||||
{
|
||||
const QColor backgroundCol(background);
|
||||
const qreal backgroundLum = luminance(background);
|
||||
|
||||
// Create a color for the input
|
||||
auto hash = hashQString(input);
|
||||
// create a hue value based on the hash of the input.
|
||||
auto userHue = qAbs(hash % 360);
|
||||
// start with moderate saturation and lightness values.
|
||||
auto sat = 220;
|
||||
auto lightness = 125;
|
||||
|
||||
// converting to a QColor makes the luminance calc easier.
|
||||
QColor inputColor = QColor::fromHsl(userHue, sat, lightness);
|
||||
|
||||
// calculate the initial luminance and contrast of the
|
||||
// generated color. It's possible that no additional
|
||||
// work will be necessary.
|
||||
auto lum = luminance(inputColor);
|
||||
auto contrast = computeContrast(lum, backgroundLum);
|
||||
|
||||
// If the contrast doesn't meet our criteria,
|
||||
// try again and again until they do by modifying first
|
||||
// the lightness and then the saturation of the color.
|
||||
while (contrast < 5) {
|
||||
// if our lightness is at it's bounds, try changing
|
||||
// saturation instead.
|
||||
if (lightness == 242 || lightness == 13) {
|
||||
qreal newSat = qBound(26.0, sat * 1.25, 242.0);
|
||||
|
||||
inputColor.setHsl(userHue, qFloor(newSat), lightness);
|
||||
auto tmpLum = luminance(inputColor);
|
||||
auto higherContrast = computeContrast(tmpLum, backgroundLum);
|
||||
if (higherContrast > contrast) {
|
||||
contrast = higherContrast;
|
||||
sat = newSat;
|
||||
} else {
|
||||
newSat = qBound(26.0, sat / 1.25, 242.0);
|
||||
inputColor.setHsl(userHue, qFloor(newSat), lightness);
|
||||
tmpLum = luminance(inputColor);
|
||||
auto lowerContrast = computeContrast(tmpLum, backgroundLum);
|
||||
if (lowerContrast > contrast) {
|
||||
contrast = lowerContrast;
|
||||
sat = newSat;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qreal newLightness = qBound(13.0, lightness * 1.25, 242.0);
|
||||
|
||||
inputColor.setHsl(userHue, sat, qFloor(newLightness));
|
||||
|
||||
auto tmpLum = luminance(inputColor);
|
||||
auto higherContrast = computeContrast(tmpLum, backgroundLum);
|
||||
|
||||
// Check to make sure we have actually improved contrast
|
||||
if (higherContrast > contrast) {
|
||||
contrast = higherContrast;
|
||||
lightness = newLightness;
|
||||
// otherwise, try going the other way instead.
|
||||
} else {
|
||||
newLightness = qBound(13.0, lightness / 1.25, 242.0);
|
||||
inputColor.setHsl(userHue, sat, qFloor(newLightness));
|
||||
tmpLum = luminance(inputColor);
|
||||
auto lowerContrast = computeContrast(tmpLum, backgroundLum);
|
||||
if (lowerContrast > contrast) {
|
||||
contrast = lowerContrast;
|
||||
lightness = newLightness;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get the hex value of the generated color.
|
||||
auto colorHex = inputColor.name();
|
||||
|
||||
return colorHex;
|
||||
}
|
||||
|
||||
qreal
|
||||
utils::computeContrast(const qreal &one, const qreal &two)
|
||||
{
|
||||
auto ratio = (one + 0.05) / (two + 0.05);
|
||||
|
||||
if (two > one) {
|
||||
ratio = 1 / ratio;
|
||||
}
|
||||
|
||||
return ratio;
|
||||
}
|
||||
|
||||
qreal
|
||||
utils::luminance(const QColor &col)
|
||||
{
|
||||
int colRgb[3] = {col.red(), col.green(), col.blue()};
|
||||
qreal lumRgb[3];
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
qreal v = colRgb[i] / 255.0;
|
||||
v <= 0.03928 ? lumRgb[i] = v / 12.92 : lumRgb[i] = qPow((v + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
auto lum = lumRgb[0] * 0.2126 + lumRgb[1] * 0.7152 + lumRgb[2] * 0.0722;
|
||||
|
||||
return lum;
|
||||
}
|
||||
|
||||
void
|
||||
utils::centerWidget(QWidget *widget, QWidget *parent)
|
||||
{
|
||||
|
|
19
src/Utils.h
|
@ -14,6 +14,8 @@
|
|||
#include <mtx/events/collections.hpp>
|
||||
#include <mtx/events/common.hpp>
|
||||
|
||||
#include <qmath.h>
|
||||
|
||||
class QComboBox;
|
||||
|
||||
namespace utils {
|
||||
|
@ -227,6 +229,23 @@ markdownToHtml(const QString &text);
|
|||
QString
|
||||
linkColor();
|
||||
|
||||
//! Returns the hash code of the input QString
|
||||
int
|
||||
hashQString(const QString &input);
|
||||
|
||||
//! Generate a color (matching #RRGGBB) that has an acceptable contrast to background that is based
|
||||
//! on the input string.
|
||||
QString
|
||||
generateContrastingHexColor(const QString &input, const QString &background);
|
||||
|
||||
//! Given two luminance values, compute the contrast ratio between them.
|
||||
qreal
|
||||
computeContrast(const qreal &one, const qreal &two);
|
||||
|
||||
//! Compute the luminance of a single color. Based on https://stackoverflow.com/a/9733420
|
||||
qreal
|
||||
luminance(const QColor &col);
|
||||
|
||||
//! Center a widget in relation to another widget.
|
||||
void
|
||||
centerWidget(QWidget *widget, QWidget *parent);
|
||||
|
|
|
@ -76,6 +76,8 @@ ImageOverlay::paintEvent(QPaintEvent *event)
|
|||
content_ = QRect(outer_margin + diff_x / 2, diff_y / 2, image_.width(), image_.height());
|
||||
close_button_ =
|
||||
QRect(screen_.width() - margin - buttonSize, margin, buttonSize, buttonSize);
|
||||
save_button_ =
|
||||
QRect(screen_.width() - (2 * margin) - (2 * buttonSize), margin, buttonSize, buttonSize);
|
||||
|
||||
// Draw main content_.
|
||||
painter.drawPixmap(content_, image_);
|
||||
|
@ -91,6 +93,12 @@ ImageOverlay::paintEvent(QPaintEvent *event)
|
|||
painter.setPen(pen);
|
||||
painter.drawLine(center - QPointF(15, 15), center + QPointF(15, 15));
|
||||
painter.drawLine(center + QPointF(15, -15), center - QPointF(15, -15));
|
||||
|
||||
// Draw download button
|
||||
center = save_button_.center();
|
||||
painter.drawLine(center - QPointF(0, 15), center + QPointF(0, 15));
|
||||
painter.drawLine(center - QPointF(15, 0), center + QPointF(0, 15));
|
||||
painter.drawLine(center + QPointF(0, 15), center + QPointF(15, 0));
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -101,6 +109,8 @@ ImageOverlay::mousePressEvent(QMouseEvent *event)
|
|||
|
||||
if (close_button_.contains(event->pos()))
|
||||
emit closing();
|
||||
else if (save_button_.contains(event->pos()))
|
||||
emit saving();
|
||||
else if (!content_.contains(event->pos()))
|
||||
emit closing();
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ protected:
|
|||
|
||||
signals:
|
||||
void closing();
|
||||
void saving();
|
||||
|
||||
private:
|
||||
QPixmap originalImage_;
|
||||
|
@ -42,6 +43,7 @@ private:
|
|||
|
||||
QRect content_;
|
||||
QRect close_button_;
|
||||
QRect save_button_;
|
||||
QRect screen_;
|
||||
};
|
||||
} // dialogs
|
||||
|
|
90
src/emoji/Category.cpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <QStyleOption>
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#include "emoji/Category.h"
|
||||
|
||||
using namespace emoji;
|
||||
|
||||
Category::Category(QString category, std::vector<Emoji> emoji, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
mainLayout_ = new QVBoxLayout(this);
|
||||
mainLayout_->setMargin(0);
|
||||
mainLayout_->setSpacing(0);
|
||||
|
||||
emojiListView_ = new QListView();
|
||||
itemModel_ = new QStandardItemModel(this);
|
||||
|
||||
delegate_ = new ItemDelegate(this);
|
||||
data_ = new Emoji;
|
||||
|
||||
emojiListView_->setItemDelegate(delegate_);
|
||||
emojiListView_->setModel(itemModel_);
|
||||
emojiListView_->setViewMode(QListView::IconMode);
|
||||
emojiListView_->setFlow(QListView::LeftToRight);
|
||||
emojiListView_->setResizeMode(QListView::Adjust);
|
||||
emojiListView_->verticalScrollBar()->setEnabled(false);
|
||||
emojiListView_->horizontalScrollBar()->setEnabled(false);
|
||||
|
||||
const int cols = 7;
|
||||
const int rows = emoji.size() / 7;
|
||||
|
||||
// TODO: Be precise here. Take the parent into consideration.
|
||||
emojiListView_->setFixedSize(cols * 50 + 20, rows * 50 + 20);
|
||||
emojiListView_->setGridSize(QSize(50, 50));
|
||||
emojiListView_->setDragEnabled(false);
|
||||
emojiListView_->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
|
||||
for (const auto &e : emoji) {
|
||||
data_->unicode = e.unicode;
|
||||
|
||||
auto item = new QStandardItem;
|
||||
item->setSizeHint(QSize(24, 24));
|
||||
|
||||
QVariant unicode(data_->unicode);
|
||||
item->setData(unicode.toString(), Qt::UserRole);
|
||||
|
||||
itemModel_->appendRow(item);
|
||||
}
|
||||
|
||||
QFont font;
|
||||
font.setWeight(QFont::Medium);
|
||||
|
||||
category_ = new QLabel(category, this);
|
||||
category_->setFont(font);
|
||||
category_->setStyleSheet("margin: 20px 0 20px 8px;");
|
||||
|
||||
mainLayout_->addWidget(category_);
|
||||
mainLayout_->addWidget(emojiListView_);
|
||||
|
||||
connect(emojiListView_, &QListView::clicked, this, &Category::clickIndex);
|
||||
}
|
||||
|
||||
void
|
||||
Category::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
}
|
59
src/emoji/Category.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QLayout>
|
||||
#include <QListView>
|
||||
#include <QStandardItemModel>
|
||||
|
||||
#include "ItemDelegate.h"
|
||||
|
||||
namespace emoji {
|
||||
|
||||
class Category : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Category(QString category, std::vector<Emoji> emoji, QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void emojiSelected(const QString &emoji);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void clickIndex(const QModelIndex &index)
|
||||
{
|
||||
emit emojiSelected(index.data(Qt::UserRole).toString());
|
||||
};
|
||||
|
||||
private:
|
||||
QVBoxLayout *mainLayout_;
|
||||
|
||||
QStandardItemModel *itemModel_;
|
||||
QListView *emojiListView_;
|
||||
|
||||
emoji::Emoji *data_;
|
||||
emoji::ItemDelegate *delegate_;
|
||||
|
||||
QLabel *category_;
|
||||
};
|
||||
} // namespace emoji
|
48
src/emoji/ItemDelegate.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
|
||||
#include "emoji/ItemDelegate.h"
|
||||
|
||||
using namespace emoji;
|
||||
|
||||
ItemDelegate::ItemDelegate(QObject *parent)
|
||||
: QStyledItemDelegate(parent)
|
||||
{
|
||||
data_ = new Emoji;
|
||||
}
|
||||
|
||||
ItemDelegate::~ItemDelegate() { delete data_; }
|
||||
|
||||
void
|
||||
ItemDelegate::paint(QPainter *painter,
|
||||
const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
Q_UNUSED(index);
|
||||
|
||||
QStyleOptionViewItem viewOption(option);
|
||||
|
||||
auto emoji = index.data(Qt::UserRole).toString();
|
||||
|
||||
// QFont font("Emoji One");
|
||||
QFont font;
|
||||
painter->setFont(font);
|
||||
painter->drawText(viewOption.rect, Qt::AlignCenter, emoji);
|
||||
}
|
43
src/emoji/ItemDelegate.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QModelIndex>
|
||||
#include <QStandardItemModel>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
#include "Provider.h"
|
||||
|
||||
namespace emoji {
|
||||
|
||||
class ItemDelegate : public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ItemDelegate(QObject *parent = nullptr);
|
||||
~ItemDelegate();
|
||||
|
||||
void paint(QPainter *painter,
|
||||
const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
private:
|
||||
Emoji *data_;
|
||||
};
|
||||
} // namespace emoji
|
236
src/emoji/Panel.cpp
Normal file
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QScrollBar>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "ui/DropShadow.h"
|
||||
#include "ui/FlatButton.h"
|
||||
|
||||
#include "emoji/Category.h"
|
||||
#include "emoji/Panel.h"
|
||||
|
||||
using namespace emoji;
|
||||
|
||||
Panel::Panel(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, shadowMargin_{2}
|
||||
, width_{370}
|
||||
, height_{350}
|
||||
, categoryIconSize_{20}
|
||||
{
|
||||
setStyleSheet("QWidget {border: none;}"
|
||||
"QScrollBar:vertical { width: 0px; margin: 0px; }"
|
||||
"QScrollBar::handle:vertical { min-height: 30px; }");
|
||||
|
||||
setAttribute(Qt::WA_ShowWithoutActivating, true);
|
||||
setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint);
|
||||
|
||||
auto mainWidget = new QWidget(this);
|
||||
mainWidget->setMaximumSize(width_, height_);
|
||||
|
||||
auto topLayout = new QVBoxLayout(this);
|
||||
topLayout->addWidget(mainWidget);
|
||||
topLayout->setMargin(shadowMargin_);
|
||||
topLayout->setSpacing(0);
|
||||
|
||||
auto contentLayout = new QVBoxLayout(mainWidget);
|
||||
contentLayout->setMargin(0);
|
||||
contentLayout->setSpacing(0);
|
||||
|
||||
auto emojiCategories = new QFrame(mainWidget);
|
||||
|
||||
auto categoriesLayout = new QHBoxLayout(emojiCategories);
|
||||
categoriesLayout->setSpacing(0);
|
||||
categoriesLayout->setMargin(0);
|
||||
|
||||
QIcon icon;
|
||||
|
||||
auto peopleCategory = new FlatButton(emojiCategories);
|
||||
icon.addFile(":/icons/icons/emoji-categories/people.png");
|
||||
peopleCategory->setIcon(icon);
|
||||
peopleCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
|
||||
|
||||
auto natureCategory_ = new FlatButton(emojiCategories);
|
||||
icon.addFile(":/icons/icons/emoji-categories/nature.png");
|
||||
natureCategory_->setIcon(icon);
|
||||
natureCategory_->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
|
||||
|
||||
auto foodCategory_ = new FlatButton(emojiCategories);
|
||||
icon.addFile(":/icons/icons/emoji-categories/foods.png");
|
||||
foodCategory_->setIcon(icon);
|
||||
foodCategory_->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
|
||||
|
||||
auto activityCategory = new FlatButton(emojiCategories);
|
||||
icon.addFile(":/icons/icons/emoji-categories/activity.png");
|
||||
activityCategory->setIcon(icon);
|
||||
activityCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
|
||||
|
||||
auto travelCategory = new FlatButton(emojiCategories);
|
||||
icon.addFile(":/icons/icons/emoji-categories/travel.png");
|
||||
travelCategory->setIcon(icon);
|
||||
travelCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
|
||||
|
||||
auto objectsCategory = new FlatButton(emojiCategories);
|
||||
icon.addFile(":/icons/icons/emoji-categories/objects.png");
|
||||
objectsCategory->setIcon(icon);
|
||||
objectsCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
|
||||
|
||||
auto symbolsCategory = new FlatButton(emojiCategories);
|
||||
icon.addFile(":/icons/icons/emoji-categories/symbols.png");
|
||||
symbolsCategory->setIcon(icon);
|
||||
symbolsCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
|
||||
|
||||
auto flagsCategory = new FlatButton(emojiCategories);
|
||||
icon.addFile(":/icons/icons/emoji-categories/flags.png");
|
||||
flagsCategory->setIcon(icon);
|
||||
flagsCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
|
||||
|
||||
categoriesLayout->addWidget(peopleCategory);
|
||||
categoriesLayout->addWidget(natureCategory_);
|
||||
categoriesLayout->addWidget(foodCategory_);
|
||||
categoriesLayout->addWidget(activityCategory);
|
||||
categoriesLayout->addWidget(travelCategory);
|
||||
categoriesLayout->addWidget(objectsCategory);
|
||||
categoriesLayout->addWidget(symbolsCategory);
|
||||
categoriesLayout->addWidget(flagsCategory);
|
||||
|
||||
scrollArea_ = new QScrollArea(this);
|
||||
scrollArea_->setWidgetResizable(true);
|
||||
scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
|
||||
auto scrollWidget = new QWidget(this);
|
||||
auto scrollLayout = new QVBoxLayout(scrollWidget);
|
||||
|
||||
scrollLayout->setMargin(0);
|
||||
scrollLayout->setSpacing(0);
|
||||
scrollArea_->setWidget(scrollWidget);
|
||||
|
||||
auto peopleEmoji =
|
||||
new Category(tr("Smileys & People"), emoji_provider_.people, scrollWidget);
|
||||
scrollLayout->addWidget(peopleEmoji);
|
||||
|
||||
auto natureEmoji =
|
||||
new Category(tr("Animals & Nature"), emoji_provider_.nature, scrollWidget);
|
||||
scrollLayout->addWidget(natureEmoji);
|
||||
|
||||
auto foodEmoji = new Category(tr("Food & Drink"), emoji_provider_.food, scrollWidget);
|
||||
scrollLayout->addWidget(foodEmoji);
|
||||
|
||||
auto activityEmoji = new Category(tr("Activity"), emoji_provider_.activity, scrollWidget);
|
||||
scrollLayout->addWidget(activityEmoji);
|
||||
|
||||
auto travelEmoji =
|
||||
new Category(tr("Travel & Places"), emoji_provider_.travel, scrollWidget);
|
||||
scrollLayout->addWidget(travelEmoji);
|
||||
|
||||
auto objectsEmoji = new Category(tr("Objects"), emoji_provider_.objects, scrollWidget);
|
||||
scrollLayout->addWidget(objectsEmoji);
|
||||
|
||||
auto symbolsEmoji = new Category(tr("Symbols"), emoji_provider_.symbols, scrollWidget);
|
||||
scrollLayout->addWidget(symbolsEmoji);
|
||||
|
||||
auto flagsEmoji = new Category(tr("Flags"), emoji_provider_.flags, scrollWidget);
|
||||
scrollLayout->addWidget(flagsEmoji);
|
||||
|
||||
contentLayout->addWidget(scrollArea_);
|
||||
contentLayout->addWidget(emojiCategories);
|
||||
|
||||
connect(peopleEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
|
||||
connect(peopleCategory, &QPushButton::clicked, [this, peopleEmoji]() {
|
||||
this->showCategory(peopleEmoji);
|
||||
});
|
||||
|
||||
connect(natureEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
|
||||
connect(natureCategory_, &QPushButton::clicked, [this, natureEmoji]() {
|
||||
this->showCategory(natureEmoji);
|
||||
});
|
||||
|
||||
connect(foodEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
|
||||
connect(foodCategory_, &QPushButton::clicked, [this, foodEmoji]() {
|
||||
this->showCategory(foodEmoji);
|
||||
});
|
||||
|
||||
connect(activityEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
|
||||
connect(activityCategory, &QPushButton::clicked, [this, activityEmoji]() {
|
||||
this->showCategory(activityEmoji);
|
||||
});
|
||||
|
||||
connect(travelEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
|
||||
connect(travelCategory, &QPushButton::clicked, [this, travelEmoji]() {
|
||||
this->showCategory(travelEmoji);
|
||||
});
|
||||
|
||||
connect(objectsEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
|
||||
connect(objectsCategory, &QPushButton::clicked, [this, objectsEmoji]() {
|
||||
this->showCategory(objectsEmoji);
|
||||
});
|
||||
|
||||
connect(symbolsEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
|
||||
connect(symbolsCategory, &QPushButton::clicked, [this, symbolsEmoji]() {
|
||||
this->showCategory(symbolsEmoji);
|
||||
});
|
||||
|
||||
connect(flagsEmoji, &Category::emojiSelected, this, &Panel::emojiSelected);
|
||||
connect(flagsCategory, &QPushButton::clicked, [this, flagsEmoji]() {
|
||||
this->showCategory(flagsEmoji);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
Panel::showCategory(const Category *category)
|
||||
{
|
||||
auto posToGo = category->mapToParent(QPoint()).y();
|
||||
auto current = scrollArea_->verticalScrollBar()->value();
|
||||
|
||||
if (current == posToGo)
|
||||
return;
|
||||
|
||||
// HACK
|
||||
// If we want to go to a previous category and position the label at the top
|
||||
// the 6*50 offset won't work because not all the categories have the same
|
||||
// height. To ensure the category is at the top, we move to the top and go as
|
||||
// normal to the next category.
|
||||
if (current > posToGo)
|
||||
this->scrollArea_->ensureVisible(0, 0, 0, 0);
|
||||
|
||||
posToGo += 6 * 50;
|
||||
this->scrollArea_->ensureVisible(0, posToGo, 0, 0);
|
||||
}
|
||||
|
||||
void
|
||||
Panel::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
|
||||
DropShadow::draw(p,
|
||||
shadowMargin_,
|
||||
4.0,
|
||||
QColor(120, 120, 120, 92),
|
||||
QColor(255, 255, 255, 0),
|
||||
0.0,
|
||||
1.0,
|
||||
0.6,
|
||||
width(),
|
||||
height());
|
||||
}
|
66
src/emoji/Panel.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QScrollArea>
|
||||
|
||||
#include "Provider.h"
|
||||
|
||||
namespace emoji {
|
||||
|
||||
class Category;
|
||||
|
||||
class Panel : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Panel(QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void mouseLeft();
|
||||
void emojiSelected(const QString &emoji);
|
||||
|
||||
protected:
|
||||
void leaveEvent(QEvent *event) override
|
||||
{
|
||||
emit leaving();
|
||||
QWidget::leaveEvent(event);
|
||||
}
|
||||
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
signals:
|
||||
void leaving();
|
||||
|
||||
private:
|
||||
void showCategory(const Category *category);
|
||||
|
||||
Provider emoji_provider_;
|
||||
|
||||
QScrollArea *scrollArea_;
|
||||
|
||||
int shadowMargin_;
|
||||
|
||||
// Panel dimensions.
|
||||
int width_;
|
||||
int height_;
|
||||
|
||||
int categoryIconSize_;
|
||||
};
|
||||
} // namespace emoji
|
82
src/emoji/PickButton.cpp
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "emoji/Panel.h"
|
||||
#include "emoji/PickButton.h"
|
||||
|
||||
using namespace emoji;
|
||||
|
||||
// Number of milliseconds after which the panel will be hidden
|
||||
// if the mouse cursor is not on top of the widget.
|
||||
constexpr int HIDE_TIMEOUT = 300;
|
||||
|
||||
PickButton::PickButton(QWidget *parent)
|
||||
: FlatButton(parent)
|
||||
, panel_{nullptr}
|
||||
{
|
||||
connect(&hideTimer_, &QTimer::timeout, this, &PickButton::hidePanel);
|
||||
connect(this, &QPushButton::clicked, this, [this]() {
|
||||
if (panel_ && panel_->isVisible()) {
|
||||
hidePanel();
|
||||
return;
|
||||
}
|
||||
|
||||
showPanel();
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
PickButton::hidePanel()
|
||||
{
|
||||
if (panel_ && !panel_->underMouse()) {
|
||||
hideTimer_.stop();
|
||||
panel_->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PickButton::showPanel()
|
||||
{
|
||||
if (panel_.isNull()) {
|
||||
panel_ = QSharedPointer<Panel>(new Panel(this));
|
||||
connect(panel_.data(), &Panel::emojiSelected, this, &PickButton::emojiSelected);
|
||||
connect(panel_.data(), &Panel::leaving, this, [this]() { panel_->hide(); });
|
||||
}
|
||||
|
||||
if (panel_->isVisible())
|
||||
return;
|
||||
|
||||
QPoint pos(rect().x(), rect().y());
|
||||
pos = this->mapToGlobal(pos);
|
||||
|
||||
auto panel_size = panel_->sizeHint();
|
||||
|
||||
auto x = pos.x() - panel_size.width() + horizontal_distance_;
|
||||
auto y = pos.y() - panel_size.height() - vertical_distance_;
|
||||
|
||||
panel_->move(x, y);
|
||||
panel_->show();
|
||||
}
|
||||
|
||||
void
|
||||
PickButton::leaveEvent(QEvent *e)
|
||||
{
|
||||
hideTimer_.start(HIDE_TIMEOUT);
|
||||
FlatButton::leaveEvent(e);
|
||||
}
|
55
src/emoji/PickButton.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QEvent>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
#include "ui/FlatButton.h"
|
||||
|
||||
namespace emoji {
|
||||
|
||||
class Panel;
|
||||
|
||||
class PickButton : public FlatButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PickButton(QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void emojiSelected(const QString &emoji);
|
||||
|
||||
protected:
|
||||
void leaveEvent(QEvent *e) override;
|
||||
|
||||
private:
|
||||
void showPanel();
|
||||
void hidePanel();
|
||||
|
||||
// Vertical distance from panel's bottom.
|
||||
int vertical_distance_ = 10;
|
||||
|
||||
// Horizontal distance from panel's bottom right corner.
|
||||
int horizontal_distance_ = 70;
|
||||
|
||||
QSharedPointer<Panel> panel_;
|
||||
QTimer hideTimer_;
|
||||
};
|
||||
} // namespace emoji
|
10
src/main.cpp
|
@ -127,6 +127,12 @@ main(int argc, char *argv[])
|
|||
parser.addVersionOption();
|
||||
parser.process(app);
|
||||
|
||||
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Regular.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Italic.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Bold.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Semibold.ttf");
|
||||
QFontDatabase::addApplicationFont(":/fonts/fonts/EmojiOne/emojione-android.ttf");
|
||||
|
||||
app.setWindowIcon(QIcon(":/logos/nheko.png"));
|
||||
|
||||
http::init();
|
||||
|
@ -147,6 +153,10 @@ main(int argc, char *argv[])
|
|||
QSettings settings;
|
||||
|
||||
QFont font;
|
||||
QString userFontFamily = settings.value("user/font_family", "").toString();
|
||||
if (!userFontFamily.isEmpty()) {
|
||||
font.setFamily(userFontFamily);
|
||||
}
|
||||
font.setPointSizeF(settings.value("user/font_size", font.pointSizeF()).toDouble());
|
||||
|
||||
app.setFont(font);
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <functional>
|
||||
|
||||
#include <QContextMenuEvent>
|
||||
#include <QDesktopServices>
|
||||
|
@ -192,10 +193,17 @@ TimelineItem::init()
|
|||
emit eventRedacted(event_id_);
|
||||
});
|
||||
});
|
||||
|
||||
connect(
|
||||
ChatPage::instance(), &ChatPage::themeChanged, this, &TimelineItem::refreshAuthorColor);
|
||||
connect(markAsRead_, &QAction::triggered, this, &TimelineItem::sendReadReceipt);
|
||||
connect(viewRawMessage_, &QAction::triggered, this, &TimelineItem::openRawMessageViewer);
|
||||
|
||||
colorGenerating_ = new QFutureWatcher<QString>(this);
|
||||
connect(colorGenerating_,
|
||||
&QFutureWatcher<QString>::finished,
|
||||
this,
|
||||
&TimelineItem::finishedGeneratingColor);
|
||||
|
||||
topLayout_ = new QHBoxLayout(this);
|
||||
mainLayout_ = new QVBoxLayout;
|
||||
messageLayout_ = new QHBoxLayout;
|
||||
|
@ -556,6 +564,12 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text>
|
|||
adjustMessageLayout();
|
||||
}
|
||||
|
||||
TimelineItem::~TimelineItem()
|
||||
{
|
||||
colorGenerating_->cancel();
|
||||
colorGenerating_->waitForFinished();
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::markSent()
|
||||
{
|
||||
|
@ -594,7 +608,7 @@ TimelineItem::markReceived(bool isEncrypted)
|
|||
void
|
||||
TimelineItem::generateBody(const QString &body)
|
||||
{
|
||||
body_ = new TextLabel(body, this);
|
||||
body_ = new TextLabel(replaceEmoji(body), this);
|
||||
body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
|
||||
|
||||
connect(body_, &TextLabel::userProfileTriggered, this, [](const QString &user_id) {
|
||||
|
@ -603,6 +617,47 @@ TimelineItem::generateBody(const QString &body)
|
|||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::refreshAuthorColor()
|
||||
{
|
||||
// Cancel and wait if we are already generating the color.
|
||||
if (colorGenerating_->isRunning()) {
|
||||
colorGenerating_->cancel();
|
||||
colorGenerating_->waitForFinished();
|
||||
}
|
||||
if (userName_) {
|
||||
// generate user's unique color.
|
||||
std::function<QString()> generate = [this]() {
|
||||
QString userColor = utils::generateContrastingHexColor(
|
||||
userName_->toolTip(), backgroundColor().name());
|
||||
return userColor;
|
||||
};
|
||||
|
||||
QString userColor = Cache::userColor(userName_->toolTip());
|
||||
|
||||
// If the color is empty, then generate it asynchronously
|
||||
if (userColor.isEmpty()) {
|
||||
colorGenerating_->setFuture(QtConcurrent::run(generate));
|
||||
} else {
|
||||
userName_->setStyleSheet("QLabel { color : " + userColor + "; }");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::finishedGeneratingColor()
|
||||
{
|
||||
nhlog::ui()->debug("finishedGeneratingColor for: {}", userName_->toolTip().toStdString());
|
||||
QString userColor = colorGenerating_->result();
|
||||
|
||||
if (!userColor.isEmpty()) {
|
||||
// another TimelineItem might have inserted in the meantime.
|
||||
if (Cache::userColor(userName_->toolTip()).isEmpty()) {
|
||||
Cache::insertUserColor(userName_->toolTip(), userColor);
|
||||
}
|
||||
userName_->setStyleSheet("QLabel { color : " + userColor + "; }");
|
||||
}
|
||||
}
|
||||
// The username/timestamp is displayed along with the message body.
|
||||
void
|
||||
TimelineItem::generateBody(const QString &user_id, const QString &displayname, const QString &body)
|
||||
|
@ -623,7 +678,7 @@ TimelineItem::generateUserName(const QString &user_id, const QString &displaynam
|
|||
}
|
||||
|
||||
QFont usernameFont;
|
||||
usernameFont.setPointSizeF(usernameFont.pointSizeF());
|
||||
usernameFont.setPointSizeF(usernameFont.pointSizeF() * 1.1);
|
||||
usernameFont.setWeight(QFont::Medium);
|
||||
|
||||
QFontMetrics fm(usernameFont);
|
||||
|
@ -637,6 +692,10 @@ TimelineItem::generateUserName(const QString &user_id, const QString &displaynam
|
|||
userName_->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
userName_->setFixedWidth(QFontMetrics(userName_->font()).width(userName_->text()));
|
||||
|
||||
// Set the user color asynchronously if it hasn't been generated yet,
|
||||
// otherwise this will just set it.
|
||||
refreshAuthorColor();
|
||||
|
||||
auto filter = new UserProfileFilter(user_id, userName_);
|
||||
userName_->installEventFilter(filter);
|
||||
userName_->setCursor(Qt::PointingHandCursor);
|
||||
|
@ -667,6 +726,25 @@ TimelineItem::generateTimestamp(const QDateTime &time)
|
|||
QString("<span style=\"color: #999\"> %1 </span>").arg(time.toString("HH:mm")));
|
||||
}
|
||||
|
||||
QString
|
||||
TimelineItem::replaceEmoji(const QString &body)
|
||||
{
|
||||
QString fmtBody = "";
|
||||
|
||||
QVector<uint> utf32_string = body.toUcs4();
|
||||
|
||||
for (auto &code : utf32_string) {
|
||||
// TODO: Be more precise here.
|
||||
if (code > 9000)
|
||||
fmtBody += QString("<span style=\"font-family: emoji;\">") +
|
||||
QString::fromUcs4(&code, 1) + "</span>";
|
||||
else
|
||||
fmtBody += QString::fromUcs4(&code, 1);
|
||||
}
|
||||
|
||||
return fmtBody;
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::setupAvatarLayout(const QString &userName)
|
||||
{
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "AvatarProvider.h"
|
||||
#include "RoomInfoListItem.h"
|
||||
#include "Utils.h"
|
||||
|
@ -132,6 +134,8 @@ private:
|
|||
class TimelineItem : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor)
|
||||
|
||||
public:
|
||||
TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice> &e,
|
||||
bool with_sender,
|
||||
|
@ -202,6 +206,11 @@ public:
|
|||
const QString &room_id,
|
||||
QWidget *parent);
|
||||
|
||||
~TimelineItem();
|
||||
|
||||
void setBackgroundColor(const QColor &color) { backgroundColor_ = color; }
|
||||
QColor backgroundColor() const { return backgroundColor_; }
|
||||
|
||||
void setUserAvatar(const QImage &pixmap);
|
||||
DescInfo descriptionMessage() const { return descriptionMsg_; }
|
||||
QString eventId() const { return event_id_; }
|
||||
|
@ -222,6 +231,10 @@ signals:
|
|||
void eventRedacted(const QString &event_id);
|
||||
void redactionFailed(const QString &msg);
|
||||
|
||||
public slots:
|
||||
void refreshAuthorColor();
|
||||
void finishedGeneratingColor();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void contextMenuEvent(QContextMenuEvent *event) override;
|
||||
|
@ -256,6 +269,9 @@ private:
|
|||
//! has been acknowledged by the server.
|
||||
bool isReceived_ = false;
|
||||
|
||||
QFutureWatcher<QString> *colorGenerating_;
|
||||
|
||||
QString replaceEmoji(const QString &body);
|
||||
QString event_id_;
|
||||
QString room_id_;
|
||||
|
||||
|
@ -282,6 +298,8 @@ private:
|
|||
QLabel *timestamp_;
|
||||
QLabel *userName_;
|
||||
TextLabel *body_;
|
||||
|
||||
QColor backgroundColor_;
|
||||
};
|
||||
|
||||
template<class Widget>
|
||||
|
|
|
@ -158,6 +158,7 @@ ImageItem::mousePressEvent(QMouseEvent *event)
|
|||
} else {
|
||||
auto imgDialog = new dialogs::ImageOverlay(image_);
|
||||
imgDialog->show();
|
||||
connect(imgDialog, &dialogs::ImageOverlay::saving, this, &ImageItem::saveAs);
|
||||
}
|
||||
}
|
||||
|
||||
|
|