diff --git a/.ci/install.sh b/.ci/install.sh
index 59114baf..72c34127 100755
--- a/.ci/install.sh
+++ b/.ci/install.sh
@@ -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"
diff --git a/.ci/linux/deploy.sh b/.ci/linux/deploy.sh
index 4e716003..38317a4a 100755
--- a/.ci/linux/deploy.sh
+++ b/.ci/linux/deploy.sh
@@ -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
-fi
+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
\ No newline at end of file
diff --git a/.ci/macos/deploy.sh b/.ci/macos/deploy.sh
index 1de95a44..03d377bc 100755
--- a/.ci/macos/deploy.sh
+++ b/.ci/macos/deploy.sh
@@ -8,7 +8,15 @@ TAG=`git tag -l --points-at HEAD`
PATH=/usr/local/opt/qt/bin/:${PATH}
pushd build
+
+# 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
+
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
diff --git a/.ci/script.sh b/.ci/script.sh
index 622bda9b..ce65cb14 100755
--- a/.ci/script.sh
+++ b/.ci/script.sh
@@ -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
diff --git a/.travis.yml b/.travis.yml
index 77d0083a..afe6fb6a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,118 +1,112 @@
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
- env:
- - DEPLOYMENT=1
- - USE_BUNDLED_BOOST=0
- - USE_BUNDLED_CMARK=0
- - os: linux
- compiler: gcc
- env:
- - CXX_COMPILER=g++-5
- - C_COMPILER=gcc-5
- - QT_VERSION="-5.10.1"
- - QT_PKG=510
- - DEPLOYMENT=1
- - USE_BUNDLED_BOOST=1
- - USE_BUNDLED_CMARK=1
- addons:
- apt:
- sources:
- - ubuntu-toolchain-r-test
- packages:
- - g++-5
- - ninja-build
- - os: linux
- compiler: gcc
- env:
- - CXX_COMPILER=g++-8
- - C_COMPILER=gcc-8
- - QT_VERSION=571
- - QT_PKG=57
- - USE_BUNDLED_BOOST=1
- - USE_BUNDLED_CMARK=1
- addons:
- apt:
- sources:
- - ubuntu-toolchain-r-test
- packages:
- - g++-8
- - ninja-build
- - os: linux
- compiler: clang
- env:
- - CXX_COMPILER=clang++-5.0
- - C_COMPILER=clang-5.0
- - QT_VERSION=592
- - QT_PKG=59
- - USE_BUNDLED_BOOST=1
- - USE_BUNDLED_CMARK=1
- addons:
- apt:
- sources:
- - ubuntu-toolchain-r-test
- - llvm-toolchain-trusty-5.0
- packages:
- - clang-5.0
- - g++-7
- - ninja-build
+ include:
+ - os: osx
+ compiler: clang
+ osx_image: xcode9
+ env:
+ - DEPLOYMENT=1
+ - USE_BUNDLED_BOOST=0
+ - USE_BUNDLED_CMARK=0
+ - os: linux
+ compiler: gcc
+ env:
+ - CXX_COMPILER=g++-5
+ - C_COMPILER=gcc-5
+ - QT_VERSION="-5.10.1"
+ - QT_PKG=510
+ - DEPLOYMENT=1
+ - USE_BUNDLED_BOOST=1
+ - USE_BUNDLED_CMARK=1
+ addons:
+ apt:
+ sources: ["ubuntu-toolchain-r-test"]
+ packages: ["g++-5", "ninja-build"]
+ - os: linux
+ compiler: gcc
+ env:
+ - CXX_COMPILER=g++-8
+ - C_COMPILER=gcc-8
+ - QT_VERSION=571
+ - QT_PKG=57
+ - USE_BUNDLED_BOOST=1
+ - USE_BUNDLED_CMARK=1
+ addons:
+ apt:
+ sources: ["ubuntu-toolchain-r-test"]
+ packages: ["g++-8", "ninja-build"]
+ - os: linux
+ compiler: clang
+ env:
+ - CXX_COMPILER=clang++-5.0
+ - C_COMPILER=clang-5.0
+ - QT_VERSION=592
+ - QT_PKG=59
+ - USE_BUNDLED_BOOST=1
+ - USE_BUNDLED_CMARK=1
+ addons:
+ apt:
+ 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}
+ - 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"
-- export PATH=/usr/local/bin:${PATH}
+ - ./.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
-- cp ./.ci/bintray-release.json .
+ - ./.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
- key:
- secure: CAVzWZPxYSOTollo9bpD4tvEbfxXjqelc32aApV48GKyJrMQljQ+mvSe25BuUtnDehxnw8affgGX23AYXmvG8P7w4hM2d7//8Lgan1zCmusV8JE432jknev6X641B4cvrywqSe0Dj3l0kS9Xgirq4BGavlI0y2vUjeJfQEv0y8GYoI72LwgyH0i82v/1Qi92Fh8429IJIb0eKmC1wGWXCmo2kd8StZRL5mSlc4TmyWI0SHpA5GrLMiQwLAuD7DjDl5mpaK2yQx+H4vBcI2SUMvmlHGgVjXikJG5gURlHbnIaaBFvO67INc1/65KtMokWuMP12zxqJiaMPtsAskOpQv4FLAYDfnigH3NxufyOIGp2cxS5RhJDQhbNsxHEDnUo1kHcO23ZYNWCuC1yUdn0RXzKhWcUsz8mKF8KJs22Ty4VjfUMZ+vqK/AbHyq4rkl8DizVRZqKF1KjSWrSv/2sT4itnHk9pmcgxAYfGuALcjrJJveI4MTwDhzXB62CKnMOqLq3sAMqvE0+BdA0BykQr7qrKtptuyP2/OFx6RDbfHQl5Klkb6cSOjxm0oUzh/8iaxgsVdCrhfE67eqkhFZ+a8lJkB/rZ4zSK1Q2Cp4nLtnxenUCW+Ptk2l7zZN6kXM1/+tcgqVROChYJ6asMUpsjFOOAVQ8SZ4TcxX1rq+pxlA=
- skip_cleanup: true
- overwrite: true
- file: bintray-release.json
- on:
- condition: "$DEPLOYMENT == 1"
- repo: Nheko-Reborn/nheko
- tags: 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=
- file_glob: true
- file:
- - nheko-${TRAVIS_TAG}-x86_64.AppImage
- on:
- condition: "$TRAVIS_OS_NAME == linux && $DEPLOYMENT == 1"
- repo: Nheko-Reborn/nheko
- tags: true
- - 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=
- file: nheko-${TRAVIS_TAG}.dmg
- on:
- condition: "$TRAVIS_OS_NAME == osx && $DEPLOYMENT == 1"
- repo: Nheko-Reborn/nheko
- tags: true
+- provider: bintray
+ user: "redsky17"
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=
+ 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: 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="
+ file_glob: true
+ file:
+ - nheko-${VERSION}-x86_64.AppImage
+ on:
+ condition: "$TRAVIS_OS_NAME == linux && $DEPLOYMENT == 1"
+ repo: Nheko-Reborn/nheko
+ tags: true
+- 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="
+ file: nheko-${VERSION}.dmg
+ on:
+ condition: "$TRAVIS_OS_NAME == osx && $DEPLOYMENT == 1"
+ repo: Nheko-Reborn/nheko
+ tags: true
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6cb83ea5..8c492299 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 515e49d8..ae6edb87 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -176,6 +176,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
@@ -282,6 +286,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
@@ -298,6 +305,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
diff --git a/README.md b/README.md
index 66675d61..f3fad169 100644
--- a/README.md
+++ b/README.md
@@ -264,5 +264,11 @@ Here is a screen shot to get a feel for the UI, but things will probably change.

+### 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
diff --git a/includes/jdenticoninterface.h b/includes/jdenticoninterface.h
new file mode 100644
index 00000000..2108a726
--- /dev/null
+++ b/includes/jdenticoninterface.h
@@ -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
diff --git a/resources/fonts/EmojiOne/emojione-android.ttf b/resources/fonts/EmojiOne/emojione-android.ttf
new file mode 100644
index 00000000..4cd640d0
--- /dev/null
+++ b/resources/fonts/EmojiOne/emojione-android.ttf
Binary files differdiff --git a/resources/fonts/OpenSans/LICENSE.txt b/resources/fonts/OpenSans/LICENSE.txt
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/resources/fonts/OpenSans/LICENSE.txt
@@ -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.
diff --git a/resources/fonts/OpenSans/OpenSans-Bold.ttf b/resources/fonts/OpenSans/OpenSans-Bold.ttf
new file mode 100644
index 00000000..fd79d43b
--- /dev/null
+++ b/resources/fonts/OpenSans/OpenSans-Bold.ttf
Binary files differdiff --git a/resources/fonts/OpenSans/OpenSans-BoldItalic.ttf b/resources/fonts/OpenSans/OpenSans-BoldItalic.ttf
new file mode 100644
index 00000000..9bc80095
--- /dev/null
+++ b/resources/fonts/OpenSans/OpenSans-BoldItalic.ttf
Binary files differdiff --git a/resources/fonts/OpenSans/OpenSans-ExtraBold.ttf b/resources/fonts/OpenSans/OpenSans-ExtraBold.ttf
new file mode 100644
index 00000000..21f6f84a
--- /dev/null
+++ b/resources/fonts/OpenSans/OpenSans-ExtraBold.ttf
Binary files differdiff --git a/resources/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf b/resources/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf
new file mode 100644
index 00000000..31cb6883
--- /dev/null
+++ b/resources/fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf
Binary files differdiff --git a/resources/fonts/OpenSans/OpenSans-Italic.ttf b/resources/fonts/OpenSans/OpenSans-Italic.ttf
new file mode 100644
index 00000000..c90da48f
--- /dev/null
+++ b/resources/fonts/OpenSans/OpenSans-Italic.ttf
Binary files differdiff --git a/resources/fonts/OpenSans/OpenSans-Light.ttf b/resources/fonts/OpenSans/OpenSans-Light.ttf
new file mode 100644
index 00000000..0d381897
--- /dev/null
+++ b/resources/fonts/OpenSans/OpenSans-Light.ttf
Binary files differdiff --git a/resources/fonts/OpenSans/OpenSans-LightItalic.ttf b/resources/fonts/OpenSans/OpenSans-LightItalic.ttf
new file mode 100644
index 00000000..68299c4b
--- /dev/null
+++ b/resources/fonts/OpenSans/OpenSans-LightItalic.ttf
Binary files differdiff --git a/resources/fonts/OpenSans/OpenSans-Regular.ttf b/resources/fonts/OpenSans/OpenSans-Regular.ttf
new file mode 100644
index 00000000..db433349
--- /dev/null
+++ b/resources/fonts/OpenSans/OpenSans-Regular.ttf
Binary files differdiff --git a/resources/fonts/OpenSans/OpenSans-Semibold.ttf b/resources/fonts/OpenSans/OpenSans-Semibold.ttf
new file mode 100644
index 00000000..1a7679e3
--- /dev/null
+++ b/resources/fonts/OpenSans/OpenSans-Semibold.ttf
Binary files differdiff --git a/resources/fonts/OpenSans/OpenSans-SemiboldItalic.ttf b/resources/fonts/OpenSans/OpenSans-SemiboldItalic.ttf
new file mode 100644
index 00000000..59b6d16b
--- /dev/null
+++ b/resources/fonts/OpenSans/OpenSans-SemiboldItalic.ttf
Binary files differdiff --git a/resources/icons/emoji-categories/activity.png b/resources/icons/emoji-categories/activity.png
new file mode 100644
index 00000000..2d360762
--- /dev/null
+++ b/resources/icons/emoji-categories/activity.png
Binary files differdiff --git a/resources/icons/emoji-categories/activity@2x.png b/resources/icons/emoji-categories/activity@2x.png
new file mode 100644
index 00000000..d8f88711
--- /dev/null
+++ b/resources/icons/emoji-categories/activity@2x.png
Binary files differdiff --git a/resources/icons/emoji-categories/flags.png b/resources/icons/emoji-categories/flags.png
new file mode 100644
index 00000000..9a52000f
--- /dev/null
+++ b/resources/icons/emoji-categories/flags.png
Binary files differdiff --git a/resources/icons/emoji-categories/flags@2x.png b/resources/icons/emoji-categories/flags@2x.png
new file mode 100644
index 00000000..45350593
--- /dev/null
+++ b/resources/icons/emoji-categories/flags@2x.png
Binary files differdiff --git a/resources/icons/emoji-categories/foods.png b/resources/icons/emoji-categories/foods.png
new file mode 100644
index 00000000..15c31069
--- /dev/null
+++ b/resources/icons/emoji-categories/foods.png
Binary files differdiff --git a/resources/icons/emoji-categories/foods@2x.png b/resources/icons/emoji-categories/foods@2x.png
new file mode 100644
index 00000000..bbdd2a3c
--- /dev/null
+++ b/resources/icons/emoji-categories/foods@2x.png
Binary files differdiff --git a/resources/icons/emoji-categories/nature.png b/resources/icons/emoji-categories/nature.png
new file mode 100644
index 00000000..eb1786cf
--- /dev/null
+++ b/resources/icons/emoji-categories/nature.png
Binary files differdiff --git a/resources/icons/emoji-categories/nature@2x.png b/resources/icons/emoji-categories/nature@2x.png
new file mode 100644
index 00000000..81db5c08
--- /dev/null
+++ b/resources/icons/emoji-categories/nature@2x.png
Binary files differdiff --git a/resources/icons/emoji-categories/objects.png b/resources/icons/emoji-categories/objects.png
new file mode 100644
index 00000000..45c6eb37
--- /dev/null
+++ b/resources/icons/emoji-categories/objects.png
Binary files differdiff --git a/resources/icons/emoji-categories/objects@2x.png b/resources/icons/emoji-categories/objects@2x.png
new file mode 100644
index 00000000..01fd5cb4
--- /dev/null
+++ b/resources/icons/emoji-categories/objects@2x.png
Binary files differdiff --git a/resources/icons/emoji-categories/people.png b/resources/icons/emoji-categories/people.png
new file mode 100644
index 00000000..710e808a
--- /dev/null
+++ b/resources/icons/emoji-categories/people.png
Binary files differdiff --git a/resources/icons/emoji-categories/people@2x.png b/resources/icons/emoji-categories/people@2x.png
new file mode 100644
index 00000000..142ba09e
--- /dev/null
+++ b/resources/icons/emoji-categories/people@2x.png
Binary files differdiff --git a/resources/icons/emoji-categories/symbols.png b/resources/icons/emoji-categories/symbols.png
new file mode 100644
index 00000000..08184de1
--- /dev/null
+++ b/resources/icons/emoji-categories/symbols.png
Binary files differdiff --git a/resources/icons/emoji-categories/symbols@2x.png b/resources/icons/emoji-categories/symbols@2x.png
new file mode 100644
index 00000000..b5e7cc6c
--- /dev/null
+++ b/resources/icons/emoji-categories/symbols@2x.png
Binary files differdiff --git a/resources/icons/emoji-categories/travel.png b/resources/icons/emoji-categories/travel.png
new file mode 100644
index 00000000..93da773e
--- /dev/null
+++ b/resources/icons/emoji-categories/travel.png
Binary files differdiff --git a/resources/icons/emoji-categories/travel@2x.png b/resources/icons/emoji-categories/travel@2x.png
new file mode 100644
index 00000000..2f72a281
--- /dev/null
+++ b/resources/icons/emoji-categories/travel@2x.png
Binary files differdiff --git a/resources/res.qrc b/resources/res.qrc
index 559d6def..cef55773 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -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>
diff --git a/resources/styles/nheko-dark.qss b/resources/styles/nheko-dark.qss
index 86056bb2..5567f32c 100644
--- a/resources/styles/nheko-dark.qss
+++ b/resources/styles/nheko-dark.qss
@@ -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;
diff --git a/resources/styles/nheko.qss b/resources/styles/nheko.qss
index ca5a8f0d..58e83c22 100644
--- a/resources/styles/nheko.qss
+++ b/resources/styles/nheko.qss
@@ -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;
diff --git a/resources/styles/system.qss b/resources/styles/system.qss
index 45263e96..c1e8898a 100644
--- a/resources/styles/system.qss
+++ b/resources/styles/system.qss
@@ -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);
diff --git a/src/Cache.cpp b/src/Cache.cpp
index a9094e2d..d6a7b509 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -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();
+}
\ No newline at end of file
diff --git a/src/Cache.h b/src/Cache.h
index b730d6fc..1a133618 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -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();
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index aaaddf6d..dd23fb80 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -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
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 2c728c17..7d3b3273 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -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 &);
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 55dbba34..7d9a8902 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -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;
+}
\ No newline at end of file
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 2336a929..1aadbf4d 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -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;
};
diff --git a/src/RoomInfoListItem.cpp b/src/RoomInfoListItem.cpp
index fcf5bd72..f17b383c 100644
--- a/src/RoomInfoListItem.cpp
+++ b/src/RoomInfoListItem.cpp
@@ -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);
- brush.setColor(bubbleBgColor());
+ 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;
+ unreadMsgCount_ = count;
+ unreadHighlightedMsgCount_ = highlightedCount;
update();
}
diff --git a/src/RoomInfoListItem.h b/src/RoomInfoListItem.h
index 5fa89853..40c938c1 100644
--- a/src/RoomInfoListItem.h
+++ b/src/RoomInfoListItem.h
@@ -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)
@@ -184,7 +187,8 @@ private:
bool isPressed_ = false;
bool hasUnreadMessages_ = true;
- int unreadMsgCount_ = 0;
+ int unreadMsgCount_ = 0;
+ int unreadHighlightedMsgCount_ = 0;
QColor highlightedBackgroundColor_;
QColor hoverBackgroundColor_;
@@ -206,6 +210,7 @@ private:
QRectF declineBtnRegion_;
// Fonts
+ QColor mentionedFontColor_;
QFont unreadCountFont_;
int bubbleDiameter_;
diff --git a/src/RoomList.cpp b/src/RoomList.cpp
index c1b080c0..1abf3533 100644
--- a/src/RoomList.cpp
+++ b/src/RoomList.cpp
@@ -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();
}
diff --git a/src/RoomList.h b/src/RoomList.h
index 88e6d1ad..155a969c 100644
--- a/src/RoomList.h
+++ b/src/RoomList.h
@@ -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);
diff --git a/src/TextInputWidget.cpp b/src/TextInputWidget.cpp
index 89513037..5fcba7a9 100644
--- a/src/TextInputWidget.cpp
+++ b/src/TextInputWidget.cpp
@@ -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);
@@ -536,6 +555,22 @@ TextInputWidget::TextInputWidget(QWidget *parent)
}
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)
{
if (command == "me") {
diff --git a/src/TextInputWidget.h b/src/TextInputWidget.h
index 1fb6d7f2..8f634f6b 100644
--- a/src/TextInputWidget.h
+++ b/src/TextInputWidget.h
@@ -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_;
};
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 15ad72e1..e3c0d190 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -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();
@@ -63,6 +63,13 @@ UserSettings::setFontSize(double size)
}
void
+UserSettings::setFontFamily(QString family)
+{
+ font_ = family;
+ save();
+}
+
+void
UserSettings::setTheme(QString theme)
{
theme_ = 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) {
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index 373126ae..900f57e4 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -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;
};
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 8176cb43..1d1dbeb2 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -15,6 +15,8 @@
using TimelineEvent = mtx::events::collections::TimelineEvents;
+QHash<QString, QString> authorColors_;
+
QString
utils::localUser()
{
@@ -382,6 +384,135 @@ 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)
+{
+ nhlog::ui()->debug("Background hex {}", background.toStdString());
+ 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);
+ nhlog::ui()->debug(
+ "User Hue {} : {}", input.toStdString(), QString::number(userHue).toStdString());
+ // 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);
+ nhlog::ui()->info("newSat {}", QString::number(newSat).toStdString());
+
+ 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();
+
+ nhlog::ui()->debug("Hex Generated for {}: [hex: {}, contrast: {}, luminance: {}]",
+ input.toStdString(),
+ colorHex.toStdString(),
+ QString::number(contrast).toStdString(),
+ QString::number(lum).toStdString());
+ 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)
{
diff --git a/src/Utils.h b/src/Utils.h
index 2a029943..8672e7d4 100644
--- a/src/Utils.h
+++ b/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);
diff --git a/src/dialogs/ImageOverlay.cpp b/src/dialogs/ImageOverlay.cpp
index b40aa164..dbf5bbe4 100644
--- a/src/dialogs/ImageOverlay.cpp
+++ b/src/dialogs/ImageOverlay.cpp
@@ -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();
}
diff --git a/src/dialogs/ImageOverlay.h b/src/dialogs/ImageOverlay.h
index b4d42acb..26257fc1 100644
--- a/src/dialogs/ImageOverlay.h
+++ b/src/dialogs/ImageOverlay.h
@@ -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
diff --git a/src/emoji/Category.cpp b/src/emoji/Category.cpp
new file mode 100644
index 00000000..fbfbf4fc
--- /dev/null
+++ b/src/emoji/Category.cpp
@@ -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);
+}
diff --git a/src/emoji/Category.h b/src/emoji/Category.h
new file mode 100644
index 00000000..a14029c8
--- /dev/null
+++ b/src/emoji/Category.h
@@ -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
diff --git a/src/emoji/ItemDelegate.cpp b/src/emoji/ItemDelegate.cpp
new file mode 100644
index 00000000..b79ae0fc
--- /dev/null
+++ b/src/emoji/ItemDelegate.cpp
@@ -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);
+}
diff --git a/src/emoji/ItemDelegate.h b/src/emoji/ItemDelegate.h
new file mode 100644
index 00000000..e0456308
--- /dev/null
+++ b/src/emoji/ItemDelegate.h
@@ -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
diff --git a/src/emoji/Panel.cpp b/src/emoji/Panel.cpp
new file mode 100644
index 00000000..710b501e
--- /dev/null
+++ b/src/emoji/Panel.cpp
@@ -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());
+}
diff --git a/src/emoji/Panel.h b/src/emoji/Panel.h
new file mode 100644
index 00000000..ad233c27
--- /dev/null
+++ b/src/emoji/Panel.h
@@ -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
diff --git a/src/emoji/PickButton.cpp b/src/emoji/PickButton.cpp
new file mode 100644
index 00000000..608b4fa2
--- /dev/null
+++ b/src/emoji/PickButton.cpp
@@ -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);
+}
diff --git a/src/emoji/PickButton.h b/src/emoji/PickButton.h
new file mode 100644
index 00000000..97ed8c37
--- /dev/null
+++ b/src/emoji/PickButton.h
@@ -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
diff --git a/src/main.cpp b/src/main.cpp
index 591d348a..0c196a33 100644
--- a/src/main.cpp
+++ b/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);
diff --git a/src/timeline/TimelineItem.cpp b/src/timeline/TimelineItem.cpp
index e962d468..1c90eade 100644
--- a/src/timeline/TimelineItem.cpp
+++ b/src/timeline/TimelineItem.cpp
@@ -192,7 +192,8 @@ 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);
@@ -594,7 +595,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 +604,24 @@ TimelineItem::generateBody(const QString &body)
});
}
+void
+TimelineItem::refreshAuthorColor()
+{
+ if (userName_) {
+ QString userColor = Cache::userColor(userName_->toolTip());
+ if (userColor.isEmpty()) {
+ // This attempts to refresh this item since it's not drawn
+ // which allows us to get the background color accurately.
+ qApp->style()->polish(this);
+ // generate user's unique color.
+ auto backCol = backgroundColor().name();
+ userColor =
+ utils::generateContrastingHexColor(userName_->toolTip(), backCol);
+ 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 +642,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 +656,18 @@ TimelineItem::generateUserName(const QString &user_id, const QString &displaynam
userName_->setAlignment(Qt::AlignLeft | Qt::AlignTop);
userName_->setFixedWidth(QFontMetrics(userName_->font()).width(userName_->text()));
+ // TimelineItem isn't displayed. This forces the QSS to get
+ // loaded.
+ QString userColor = Cache::userColor(user_id);
+ if (userColor.isEmpty()) {
+ qApp->style()->polish(this);
+ // generate user's unique color.
+ auto backCol = backgroundColor().name();
+ userColor = utils::generateContrastingHexColor(user_id, backCol);
+ Cache::insertUserColor(user_id, userColor);
+ }
+ userName_->setStyleSheet("QLabel { color : " + userColor + "; }");
+
auto filter = new UserProfileFilter(user_id, userName_);
userName_->installEventFilter(filter);
userName_->setCursor(Qt::PointingHandCursor);
@@ -667,6 +698,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)
{
@@ -837,4 +887,4 @@ TimelineItem::openRawMessageViewer() const
"failed to serialize event ({}, {})", room_id, event_id);
}
});
-}
+}
\ No newline at end of file
diff --git a/src/timeline/TimelineItem.h b/src/timeline/TimelineItem.h
index 8159e370..f81aa658 100644
--- a/src/timeline/TimelineItem.h
+++ b/src/timeline/TimelineItem.h
@@ -132,6 +132,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 +204,9 @@ public:
const QString &room_id,
QWidget *parent);
+ 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 +227,9 @@ signals:
void eventRedacted(const QString &event_id);
void redactionFailed(const QString &msg);
+public slots:
+ void refreshAuthorColor();
+
protected:
void paintEvent(QPaintEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
@@ -256,6 +264,7 @@ private:
//! has been acknowledged by the server.
bool isReceived_ = false;
+ QString replaceEmoji(const QString &body);
QString event_id_;
QString room_id_;
@@ -282,6 +291,8 @@ private:
QLabel *timestamp_;
QLabel *userName_;
TextLabel *body_;
+
+ QColor backgroundColor_;
};
template<class Widget>
diff --git a/src/timeline/widgets/ImageItem.cpp b/src/timeline/widgets/ImageItem.cpp
index f06b9a5b..4ee9e42a 100644
--- a/src/timeline/widgets/ImageItem.cpp
+++ b/src/timeline/widgets/ImageItem.cpp
@@ -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);
}
}
|