mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-07-18 01:19:24 +00:00
Compare commits
1 Commits
emoji-majo
...
reduce-cli
| Author | SHA1 | Date | |
|---|---|---|---|
| 05ee0dbdb5 |
26
.github/workflows/appimage-build.yml
vendored
26
.github/workflows/appimage-build.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
libgtk-3-bin libglib2.0-bin shared-mime-info gsettings-desktop-schemas \
|
libgtk-3-bin libglib2.0-bin shared-mime-info gsettings-desktop-schemas \
|
||||||
liblua5.4-dev libpci-dev libperl-dev libssl-dev libayatana-appindicator3-dev \
|
liblua5.4-dev libpci-dev libperl-dev libssl-dev libayatana-appindicator3-dev \
|
||||||
perl python3 python3-minimal python3-dev python3-cffi mono-devel desktop-file-utils \
|
perl python3 python3-minimal python3-dev python3-cffi mono-devel desktop-file-utils \
|
||||||
breeze-gtk-theme \
|
fonts-noto-color-emoji breeze-gtk-theme \
|
||||||
patchelf file curl
|
patchelf file curl
|
||||||
|
|
||||||
- name: Configure
|
- name: Configure
|
||||||
@@ -119,9 +119,15 @@ jobs:
|
|||||||
cp -a /usr/lib/x86_64-linux-gnu/libpython3*.so* AppDir/usr/lib/x86_64-linux-gnu/
|
cp -a /usr/lib/x86_64-linux-gnu/libpython3*.so* AppDir/usr/lib/x86_64-linux-gnu/
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# The emoji font is not bundled on Linux; the AppImage uses the
|
if [ -d "/usr/share/gtk-3.0/emoji" ]; then
|
||||||
# host system's emoji font (e.g. fonts-noto-color-emoji).
|
install -d AppDir/usr/share/gtk-3.0
|
||||||
test ! -e AppDir/usr/share/fonts/zoitechat/NotoColorEmoji.ttf
|
cp -a /usr/share/gtk-3.0/emoji AppDir/usr/share/gtk-3.0/
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf" ]; then
|
||||||
|
install -d AppDir/usr/share/fonts/truetype/noto
|
||||||
|
cp -a /usr/share/fonts/truetype/noto/NotoColorEmoji.ttf AppDir/usr/share/fonts/truetype/noto/
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -d "/usr/lib/x86_64-linux-gnu/gtk-3.0/modules" ]; then
|
if [ -d "/usr/lib/x86_64-linux-gnu/gtk-3.0/modules" ]; then
|
||||||
install -d AppDir/usr/lib/x86_64-linux-gnu/gtk-3.0
|
install -d AppDir/usr/lib/x86_64-linux-gnu/gtk-3.0
|
||||||
@@ -137,6 +143,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -eux
|
set -eux
|
||||||
|
|
||||||
|
# Include every built-in plugin except the Windows-only update plugin.
|
||||||
find AppDir/usr/lib -maxdepth 4 -type f -name '*.so' -path '*/zoitechat/plugins/*' -print | sort
|
find AppDir/usr/lib -maxdepth 4 -type f -name '*.so' -path '*/zoitechat/plugins/*' -print | sort
|
||||||
|
|
||||||
for plugin in checksum fishlim lua perl python sysinfo; do
|
for plugin in checksum fishlim lua perl python sysinfo; do
|
||||||
@@ -185,6 +192,14 @@ jobs:
|
|||||||
export GTK_PATH="$gtk_path_entries${GTK_PATH:+:$GTK_PATH}"
|
export GTK_PATH="$gtk_path_entries${GTK_PATH:+:$GTK_PATH}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -d "$APPDIR/etc/fonts" ]; then
|
||||||
|
export FONTCONFIG_SYSROOT="$APPDIR"
|
||||||
|
export FONTCONFIG_PATH="$APPDIR/etc/fonts${FONTCONFIG_PATH:+:$FONTCONFIG_PATH}"
|
||||||
|
if [ -f "$APPDIR/etc/fonts/fonts.conf" ]; then
|
||||||
|
export FONTCONFIG_FILE="$APPDIR/etc/fonts/fonts.conf"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -d "$APPDIR/usr/lib/x86_64-linux-gnu/zoitechat/plugins" ]; then
|
if [ -d "$APPDIR/usr/lib/x86_64-linux-gnu/zoitechat/plugins" ]; then
|
||||||
export ZOITECHAT_LIBDIR="$APPDIR/usr/lib/x86_64-linux-gnu/zoitechat/plugins"
|
export ZOITECHAT_LIBDIR="$APPDIR/usr/lib/x86_64-linux-gnu/zoitechat/plugins"
|
||||||
elif [ -d "$APPDIR/usr/lib/zoitechat/plugins" ]; then
|
elif [ -d "$APPDIR/usr/lib/zoitechat/plugins" ]; then
|
||||||
@@ -243,10 +258,13 @@ jobs:
|
|||||||
export PYTHONPATH="$pythonpath_entries${PYTHONPATH:+:$PYTHONPATH}"
|
export PYTHONPATH="$pythonpath_entries${PYTHONPATH:+:$PYTHONPATH}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# OpenSSL trust store override
|
||||||
export SSL_CERT_FILE="${SSL_CERT_FILE:-$APPDIR/etc/ssl/certs/ca-certificates.crt}"
|
export SSL_CERT_FILE="${SSL_CERT_FILE:-$APPDIR/etc/ssl/certs/ca-certificates.crt}"
|
||||||
export SSL_CERT_DIR="${SSL_CERT_DIR:-$APPDIR/etc/ssl/certs}"
|
export SSL_CERT_DIR="${SSL_CERT_DIR:-$APPDIR/etc/ssl/certs}"
|
||||||
export CURL_CA_BUNDLE="${CURL_CA_BUNDLE:-$SSL_CERT_FILE}"
|
export CURL_CA_BUNDLE="${CURL_CA_BUNDLE:-$SSL_CERT_FILE}"
|
||||||
|
|
||||||
|
# Prefer Wayland if the session provides it, but keep X11 fallback.
|
||||||
|
# Don't override if the user already set GDK_BACKEND explicitly.
|
||||||
if [ -z "${GDK_BACKEND:-}" ]; then
|
if [ -z "${GDK_BACKEND:-}" ]; then
|
||||||
if [ -n "${WAYLAND_DISPLAY:-}" ] || [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then
|
if [ -n "${WAYLAND_DISPLAY:-}" ] || [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then
|
||||||
export GDK_BACKEND="wayland,x11"
|
export GDK_BACKEND="wayland,x11"
|
||||||
|
|||||||
23
COPYING
23
COPYING
@@ -336,26 +336,3 @@ proprietary programs. If your program is a subroutine library, you may
|
|||||||
consider it more useful to permit linking proprietary applications with the
|
consider it more useful to permit linking proprietary applications with the
|
||||||
library. If this is what you want to do, use the GNU Lesser General
|
library. If this is what you want to do, use the GNU Lesser General
|
||||||
Public License instead of this License.
|
Public License instead of this License.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
======================================================================
|
|
||||||
ADDITIONAL LICENSE NOTICE
|
|
||||||
======================================================================
|
|
||||||
|
|
||||||
ZoiteChat source code:
|
|
||||||
GPL version 2
|
|
||||||
See Above.
|
|
||||||
|
|
||||||
Unicode Emoji and CLDR data:
|
|
||||||
Unicode License v3
|
|
||||||
data/emoji/LICENSE-UNICODE.txt
|
|
||||||
|
|
||||||
gemoji shortcode data:
|
|
||||||
MIT License
|
|
||||||
Copyright (c) 2019 GitHub, Inc.
|
|
||||||
data/emoji/LICENSE-GEMOJI.txt
|
|
||||||
|
|
||||||
Noto Color Emoji:
|
|
||||||
SIL Open Font License 1.1
|
|
||||||
data/fonts/OFL.txt
|
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
UNICODE LICENSE V3
|
|
||||||
|
|
||||||
COPYRIGHT AND PERMISSION NOTICE
|
|
||||||
|
|
||||||
Copyright © 1991-2026 Unicode, Inc.
|
|
||||||
|
|
||||||
NOTICE TO USER: Carefully read the following legal agreement. BY
|
|
||||||
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
|
|
||||||
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
|
|
||||||
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
|
|
||||||
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
copy of data files and any associated documentation (the "Data Files") or
|
|
||||||
software and any associated documentation (the "Software") to deal in the
|
|
||||||
Data Files or Software without restriction, including without limitation
|
|
||||||
the rights to use, copy, modify, merge, publish, distribute, and/or sell
|
|
||||||
copies of the Data Files or Software, and to permit persons to whom the
|
|
||||||
Data Files or Software are furnished to do so, provided that either (a)
|
|
||||||
this copyright and permission notice appear with all copies of the Data
|
|
||||||
Files or Software, or (b) this copyright and permission notice appear in
|
|
||||||
associated Documentation.
|
|
||||||
|
|
||||||
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
||||||
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
|
||||||
THIRD PARTY RIGHTS.
|
|
||||||
|
|
||||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
|
|
||||||
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
|
|
||||||
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
|
||||||
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
|
||||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
|
|
||||||
FILES OR SOFTWARE.
|
|
||||||
|
|
||||||
Except as contained in this notice, the name of a copyright holder shall
|
|
||||||
not be used in advertising or otherwise to promote the sale, use or other
|
|
||||||
dealings in these Data Files or Software without prior written
|
|
||||||
authorization of the copyright holder.
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Bundled emoji data
|
|
||||||
|
|
||||||
These are pinned copies of the upstream data files that
|
|
||||||
`tools/gen-emoji-data.py` compiles into ZoiteChat's emoji picker catalog
|
|
||||||
at build time. Bundling them means every ZoiteChat package exposes the
|
|
||||||
same emoji, names, categories and search keywords, independent of the
|
|
||||||
GTK runtime version or the emoji data a distribution installs under
|
|
||||||
`/usr/share/gtk-3.0/emoji`.
|
|
||||||
|
|
||||||
Nothing here affects what is sent over IRC: the picker inserts plain
|
|
||||||
Unicode sequences. Official builds register the bundled Noto Color Emoji
|
|
||||||
font privately for consistent rendering, while preserving platform font
|
|
||||||
fallback if registration is unavailable.
|
|
||||||
|
|
||||||
| File | Source | Version |
|
|
||||||
| ---- | ------ | ------- |
|
|
||||||
| `emoji-test.txt` | [Unicode emoji data](https://unicode.org/Public/emoji/17.0/emoji-test.txt) (mirrored at [unicode-org/unicodetools](https://github.com/unicode-org/unicodetools/blob/main/unicodetools/data/emoji/17.0/emoji-test.txt)) | Emoji 17.0 |
|
|
||||||
| `cldr-annotations-en.xml` | [unicode-org/cldr](https://github.com/unicode-org/cldr/blob/release-48/common/annotations/en.xml) `common/annotations/en.xml` | CLDR release 48 |
|
|
||||||
| `cldr-annotations-derived-en.xml` | [unicode-org/cldr](https://github.com/unicode-org/cldr/blob/release-48/common/annotationsDerived/en.xml) `common/annotationsDerived/en.xml` | CLDR release 48 |
|
|
||||||
| `gemoji.json` | [github/gemoji](https://github.com/github/gemoji/blob/v4.1.0/db/emoji.json) `db/emoji.json` | v4.1.0 |
|
|
||||||
|
|
||||||
The pinned versions are recorded in `VERSIONS`.
|
|
||||||
|
|
||||||
## Licenses
|
|
||||||
|
|
||||||
- `emoji-test.txt`, `cldr-annotations-en.xml`,
|
|
||||||
`cldr-annotations-derived-en.xml`: © Unicode, Inc., distributed under
|
|
||||||
the [Unicode License v3](https://www.unicode.org/license.txt).
|
|
||||||
- `gemoji.json`: from the gemoji project, MIT licensed,
|
|
||||||
© GitHub, Inc.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# Pinned versions of the emoji source data in this directory.
|
|
||||||
# Keep in sync with the files themselves; tools/gen-emoji-data.py
|
|
||||||
# verifies unicode-emoji against the emoji-test.txt header.
|
|
||||||
unicode-emoji=17.0
|
|
||||||
cldr=48
|
|
||||||
gemoji=4.1.0
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
23479
data/emoji/gemoji.json
23479
data/emoji/gemoji.json
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,91 +0,0 @@
|
|||||||
Copyright 2013 Google LLC
|
|
||||||
|
|
||||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
||||||
This license is copied below, and is also available with a FAQ at:
|
|
||||||
https://scripts.sil.org/OFL
|
|
||||||
|
|
||||||
|
|
||||||
-----------------------------------------------------------
|
|
||||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
||||||
-----------------------------------------------------------
|
|
||||||
|
|
||||||
PREAMBLE
|
|
||||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
||||||
development of collaborative font projects, to support the font creation
|
|
||||||
efforts of academic and linguistic communities, and to provide a free and
|
|
||||||
open framework in which fonts may be shared and improved in partnership
|
|
||||||
with others.
|
|
||||||
|
|
||||||
The OFL allows the licensed fonts to be used, studied, modified and
|
|
||||||
redistributed freely as long as they are not sold by themselves. The
|
|
||||||
fonts, including any derivative works, can be bundled, embedded,
|
|
||||||
redistributed and/or sold with any software provided that any reserved
|
|
||||||
names are not used by derivative works. The fonts and derivatives,
|
|
||||||
however, cannot be released under any other type of license. The
|
|
||||||
requirement for fonts to remain under this license does not apply
|
|
||||||
to any document created using the fonts or their derivatives.
|
|
||||||
|
|
||||||
DEFINITIONS
|
|
||||||
"Font Software" refers to the set of files released by the Copyright
|
|
||||||
Holder(s) under this license and clearly marked as such. This may
|
|
||||||
include source files, build scripts and documentation.
|
|
||||||
|
|
||||||
"Reserved Font Name" refers to any names specified as such after the
|
|
||||||
copyright statement(s).
|
|
||||||
|
|
||||||
"Original Version" refers to the collection of Font Software components as
|
|
||||||
distributed by the Copyright Holder(s).
|
|
||||||
|
|
||||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
||||||
or substituting -- in part or in whole -- any of the components of the
|
|
||||||
Original Version, by changing formats or by porting the Font Software to a
|
|
||||||
new environment.
|
|
||||||
|
|
||||||
"Author" refers to any designer, engineer, programmer, technical
|
|
||||||
writer or other person who contributed to the Font Software.
|
|
||||||
|
|
||||||
PERMISSION & CONDITIONS
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
||||||
redistribute, and sell modified and unmodified copies of the Font
|
|
||||||
Software, subject to the following conditions:
|
|
||||||
|
|
||||||
1) Neither the Font Software nor any of its individual components,
|
|
||||||
in Original or Modified Versions, may be sold by itself.
|
|
||||||
|
|
||||||
2) Original or Modified Versions of the Font Software may be bundled,
|
|
||||||
redistributed and/or sold with any software, provided that each copy
|
|
||||||
contains the above copyright notice and this license. These can be
|
|
||||||
included either as stand-alone text files, human-readable headers or
|
|
||||||
in the appropriate machine-readable metadata fields within text or
|
|
||||||
binary files as long as those fields can be easily viewed by the user.
|
|
||||||
|
|
||||||
3) No Modified Version of the Font Software may use the Reserved Font
|
|
||||||
Name(s) unless explicit written permission is granted by the corresponding
|
|
||||||
Copyright Holder. This restriction only applies to the primary font name as
|
|
||||||
presented to the users.
|
|
||||||
|
|
||||||
4) The name(s) of the Copyright Holder(s) or the Author(s) shall not be
|
|
||||||
used to promote, endorse or advertise any Modified Version, except to
|
|
||||||
acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s)
|
|
||||||
or with their explicit written permission.
|
|
||||||
|
|
||||||
5) The Font Software, modified or unmodified, in part or in whole,
|
|
||||||
must be distributed entirely under this license, and must not be
|
|
||||||
distributed under any other license. The requirement for fonts to
|
|
||||||
remain under this license does not apply to any document created
|
|
||||||
using the Font Software.
|
|
||||||
|
|
||||||
TERMINATION
|
|
||||||
This license becomes null and void if any of the above conditions are not met.
|
|
||||||
|
|
||||||
DISCLAIMER
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
||||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
||||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
||||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
||||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Bundled emoji font
|
|
||||||
|
|
||||||
`NotoColorEmoji.ttf` reports Noto Color Emoji version 2.051 and covers
|
|
||||||
Unicode Emoji 17.0. It is vendored from Debian's
|
|
||||||
`fonts-noto-color-emoji` 2.051 package, built from the corresponding
|
|
||||||
Google Noto Emoji release.
|
|
||||||
|
|
||||||
The font is bundled only with Windows builds, where it is installed
|
|
||||||
inside the application and registered only for the ZoiteChat process.
|
|
||||||
It is not installed as a global operating-system font.
|
|
||||||
|
|
||||||
Linux (and other Unix) builds do not install this file. They rely on
|
|
||||||
the emoji font provided by the operating system instead — typically the
|
|
||||||
distribution's Noto Color Emoji package (`fonts-noto-color-emoji` on
|
|
||||||
Debian/Ubuntu, `google-noto-color-emoji-fonts` on Fedora).
|
|
||||||
|
|
||||||
Upstream release: https://github.com/googlefonts/noto-emoji/releases/tag/v2.051
|
|
||||||
|
|
||||||
Vendored file SHA-256:
|
|
||||||
`dac5c27651082d6c53dab5081f50d2022ddf6877d730fa919cf6a4fc2af22de0`
|
|
||||||
|
|
||||||
The font is distributed under the SIL Open Font License 1.1. See
|
|
||||||
`OFL.txt`.
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
emoji_font_dir = join_paths(get_option('datadir'), 'fonts', 'zoitechat')
|
|
||||||
emoji_font_doc_dir = join_paths(get_option('datadir'), 'doc', 'zoitechat', 'fonts')
|
|
||||||
|
|
||||||
install_data(
|
|
||||||
'NotoColorEmoji.ttf',
|
|
||||||
install_dir: emoji_font_dir,
|
|
||||||
)
|
|
||||||
|
|
||||||
install_data(
|
|
||||||
'OFL.txt',
|
|
||||||
'README.md',
|
|
||||||
install_dir: emoji_font_doc_dir,
|
|
||||||
)
|
|
||||||
@@ -3,11 +3,6 @@ if get_option('plugin')
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
if get_option('gtk-frontend')
|
if get_option('gtk-frontend')
|
||||||
# The bundled emoji font ships only with Windows builds; Linux and other
|
|
||||||
# Unix systems use the system emoji font package instead.
|
|
||||||
if host_machine.system() == 'windows'
|
|
||||||
subdir('fonts')
|
|
||||||
endif
|
|
||||||
subdir('icons')
|
subdir('icons')
|
||||||
subdir('misc')
|
subdir('misc')
|
||||||
subdir('man')
|
subdir('man')
|
||||||
|
|||||||
@@ -65,8 +65,7 @@
|
|||||||
"/share/man"
|
"/share/man"
|
||||||
],
|
],
|
||||||
"post-install": [
|
"post-install": [
|
||||||
"install -d /app/extensions",
|
"install -d /app/extensions"
|
||||||
"test ! -e /app/share/fonts/zoitechat/NotoColorEmoji.ttf"
|
|
||||||
],
|
],
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ BuildRequires: pkgconfig(lua)
|
|||||||
BuildRequires: pkgconfig(openssl) >= 0.9.8
|
BuildRequires: pkgconfig(openssl) >= 0.9.8
|
||||||
BuildRequires: pkgconfig(python3)
|
BuildRequires: pkgconfig(python3)
|
||||||
|
|
||||||
Requires: google-noto-color-emoji-fonts
|
|
||||||
Requires: hicolor-icon-theme
|
Requires: hicolor-icon-theme
|
||||||
Requires: iso-codes
|
Requires: iso-codes
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ data/misc/net.zoite.Zoitechat.ThemeManager.desktop.in
|
|||||||
src/common/cfgfiles.c
|
src/common/cfgfiles.c
|
||||||
src/common/chanopt.c
|
src/common/chanopt.c
|
||||||
src/common/dcc.c
|
src/common/dcc.c
|
||||||
src/common/emoji-data.c
|
|
||||||
src/common/zoitechat.c
|
src/common/zoitechat.c
|
||||||
src/common/ignore.c
|
src/common/ignore.c
|
||||||
src/common/inbound.c
|
src/common/inbound.c
|
||||||
@@ -25,7 +24,6 @@ src/fe-gtk/banlist.c
|
|||||||
src/fe-gtk/chanlist.c
|
src/fe-gtk/chanlist.c
|
||||||
src/fe-gtk/dccgui.c
|
src/fe-gtk/dccgui.c
|
||||||
src/fe-gtk/editlist.c
|
src/fe-gtk/editlist.c
|
||||||
src/fe-gtk/emoji-picker.c
|
|
||||||
src/fe-gtk/fe-gtk.c
|
src/fe-gtk/fe-gtk.c
|
||||||
src/fe-gtk/fkeys.c
|
src/fe-gtk/fkeys.c
|
||||||
src/fe-gtk/gtkutil.c
|
src/fe-gtk/gtkutil.c
|
||||||
|
|||||||
@@ -40,18 +40,18 @@
|
|||||||
|
|
||||||
#define DEF_FONT "Monospace 9"
|
#define DEF_FONT "Monospace 9"
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
#define DEF_FONT_ALTER "Segoe UI Emoji,Segoe UI Symbol,Arial Unicode MS,Lucida Sans Unicode,Meiryo,Symbola,Unifont"
|
#define DEF_FONT_ALTER "Segoe UI Emoji,Arial Unicode MS,Lucida Sans Unicode,Meiryo,Symbola,Unifont"
|
||||||
#else
|
#else
|
||||||
#define DEF_FONT_ALTER "Arial Unicode MS,Segoe UI Emoji,Lucida Sans Unicode,Meiryo,Symbola,Unifont"
|
#define DEF_FONT_ALTER "Arial Unicode MS,Segoe UI Emoji,Lucida Sans Unicode,Meiryo,Symbola,Unifont"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char * const languages[LANGUAGES_LENGTH] = {
|
const char * const languages[LANGUAGES_LENGTH] = {
|
||||||
"af", "sq", "am", "ast", "az", "eu", "be", "bg", "ca", "zh_CN",
|
"af", "sq", "am", "ast", "az", "eu", "be", "bg", "ca", "zh_CN", /* 0 .. 9 */
|
||||||
"zh_TW", "cs", "da", "nl", "en_GB", "en", "et", "fi", "fr", "gl",
|
"zh_TW", "cs", "da", "nl", "en_GB", "en", "et", "fi", "fr", "gl", /* 10 .. 19 */
|
||||||
"de", "el", "gu", "hi", "hu", "id", "it", "ja_JP", "kn", "rw",
|
"de", "el", "gu", "hi", "hu", "id", "it", "ja_JP", "kn", "rw", /* 20 .. 29 */
|
||||||
"ko", "lv", "lt", "mk", "ml", "ms", "nb", "no", "pl", "pt",
|
"ko", "lv", "lt", "mk", "ml", "ms", "nb", "no", "pl", "pt", /* 30 .. 39 */
|
||||||
"pt_BR", "pa", "ru", "sr", "sk", "sl", "es", "sv", "th", "tr",
|
"pt_BR", "pa", "ru", "sr", "sk", "sl", "es", "sv", "th", "tr", /* 40 .. 49 */
|
||||||
"uk", "vi", "wa"
|
"uk", "vi", "wa" /* 50 .. */
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -78,6 +78,8 @@ list_addentry (GSList ** list, char *cmd, char *name)
|
|||||||
*list = g_slist_append (*list, pop);
|
*list = g_slist_append (*list, pop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* read it in from a buffer to our linked list */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
list_load_from_data (GSList ** list, char *ibuf, int size)
|
list_load_from_data (GSList ** list, char *ibuf, int size)
|
||||||
{
|
{
|
||||||
@@ -179,9 +181,9 @@ list_delentry (GSList ** list, char *name)
|
|||||||
char *
|
char *
|
||||||
cfg_get_str (char *cfg, const char *var, char *dest, int dest_len)
|
cfg_get_str (char *cfg, const char *var, char *dest, int dest_len)
|
||||||
{
|
{
|
||||||
char buffer[128];
|
char buffer[128]; /* should be plenty for a variable name */
|
||||||
|
|
||||||
sprintf (buffer, "%s ", var);
|
sprintf (buffer, "%s ", var); /* add one space, this way it works against var - var2 checks too */
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
@@ -195,6 +197,8 @@ cfg_get_str (char *cfg, const char *var, char *dest, int dest_len)
|
|||||||
cfg++;
|
cfg++;
|
||||||
while (*cfg == ' ')
|
while (*cfg == ' ')
|
||||||
cfg++;
|
cfg++;
|
||||||
|
/*while (*cfg == ' ' || *cfg == '=')
|
||||||
|
cfg++; */
|
||||||
value = cfg;
|
value = cfg;
|
||||||
while (*cfg != 0 && *cfg != '\n')
|
while (*cfg != 0 && *cfg != '\n')
|
||||||
cfg++;
|
cfg++;
|
||||||
@@ -289,7 +293,7 @@ cfg_get_int (char *cfg, char *var)
|
|||||||
return atoi (str);
|
return atoi (str);
|
||||||
}
|
}
|
||||||
|
|
||||||
char *xdir = NULL;
|
char *xdir = NULL; /* utf-8 encoding */
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
@@ -412,7 +416,6 @@ const struct prefs vars[] =
|
|||||||
{"gui_dialog_left", P_OFFINT (hex_gui_dialog_left), TYPE_INT},
|
{"gui_dialog_left", P_OFFINT (hex_gui_dialog_left), TYPE_INT},
|
||||||
{"gui_dialog_top", P_OFFINT (hex_gui_dialog_top), TYPE_INT},
|
{"gui_dialog_top", P_OFFINT (hex_gui_dialog_top), TYPE_INT},
|
||||||
{"gui_dialog_width", P_OFFINT (hex_gui_dialog_width), TYPE_INT},
|
{"gui_dialog_width", P_OFFINT (hex_gui_dialog_width), TYPE_INT},
|
||||||
{"gui_emoji_skin_tone", P_OFFINT (hex_gui_emoji_skin_tone), TYPE_INT},
|
|
||||||
{"gui_filesize_iec", P_OFFINT (hex_gui_filesize_iec), TYPE_BOOL},
|
{"gui_filesize_iec", P_OFFINT (hex_gui_filesize_iec), TYPE_BOOL},
|
||||||
{"gui_focus_omitalerts", P_OFFINT (hex_gui_focus_omitalerts), TYPE_BOOL},
|
{"gui_focus_omitalerts", P_OFFINT (hex_gui_focus_omitalerts), TYPE_BOOL},
|
||||||
{"gui_hide_menu", P_OFFINT (hex_gui_hide_menu), TYPE_BOOL},
|
{"gui_hide_menu", P_OFFINT (hex_gui_hide_menu), TYPE_BOOL},
|
||||||
@@ -432,6 +435,7 @@ const struct prefs vars[] =
|
|||||||
{"gui_pane_right_size_min", P_OFFINT (hex_gui_pane_right_size_min), TYPE_INT},
|
{"gui_pane_right_size_min", P_OFFINT (hex_gui_pane_right_size_min), TYPE_INT},
|
||||||
{"gui_quit_dialog", P_OFFINT (hex_gui_quit_dialog), TYPE_BOOL},
|
{"gui_quit_dialog", P_OFFINT (hex_gui_quit_dialog), TYPE_BOOL},
|
||||||
{"gui_search_pos", P_OFFINT (hex_gui_search_pos), TYPE_INT},
|
{"gui_search_pos", P_OFFINT (hex_gui_search_pos), TYPE_INT},
|
||||||
|
/* {"gui_single", P_OFFINT (hex_gui_single), TYPE_BOOL}, */
|
||||||
{"gui_slist_fav", P_OFFINT (hex_gui_slist_fav), TYPE_BOOL},
|
{"gui_slist_fav", P_OFFINT (hex_gui_slist_fav), TYPE_BOOL},
|
||||||
{"gui_slist_select", P_OFFINT (hex_gui_slist_select), TYPE_INT},
|
{"gui_slist_select", P_OFFINT (hex_gui_slist_select), TYPE_INT},
|
||||||
{"gui_slist_skip", P_OFFINT (hex_gui_slist_skip), TYPE_BOOL},
|
{"gui_slist_skip", P_OFFINT (hex_gui_slist_skip), TYPE_BOOL},
|
||||||
|
|||||||
@@ -55,7 +55,6 @@
|
|||||||
<ClCompile Include="chanopt.c" />
|
<ClCompile Include="chanopt.c" />
|
||||||
<ClCompile Include="ctcp.c" />
|
<ClCompile Include="ctcp.c" />
|
||||||
<ClCompile Include="dcc.c" />
|
<ClCompile Include="dcc.c" />
|
||||||
<ClCompile Include="emoji-data.c" />
|
|
||||||
<ClCompile Include="history.c" />
|
<ClCompile Include="history.c" />
|
||||||
<ClCompile Include="plugin-identd.c" />
|
<ClCompile Include="plugin-identd.c" />
|
||||||
<ClCompile Include="ignore.c" />
|
<ClCompile Include="ignore.c" />
|
||||||
@@ -114,7 +113,6 @@
|
|||||||
SET SOLUTIONDIR=$(SolutionDir)..\
|
SET SOLUTIONDIR=$(SolutionDir)..\
|
||||||
"$(Python3Path)\python.exe" $(ProjectDir)make-te.py "$(ProjectDir)textevents.in" "$(ZoiteChatLib)textevents.h" "$(ZoiteChatLib)textenums.h"
|
"$(Python3Path)\python.exe" $(ProjectDir)make-te.py "$(ProjectDir)textevents.in" "$(ZoiteChatLib)textevents.h" "$(ZoiteChatLib)textenums.h"
|
||||||
"$(Python3Path)\python.exe" $(ProjectDir)gen-public-suffix.py "$(ZoiteChatLib)public_suffix_data.h"
|
"$(Python3Path)\python.exe" $(ProjectDir)gen-public-suffix.py "$(ZoiteChatLib)public_suffix_data.h"
|
||||||
"$(Python3Path)\python.exe" "$(SolutionDir)..\tools\gen-emoji-data.py" "$(SolutionDir)..\data\emoji\emoji-test.txt" "$(SolutionDir)..\data\emoji\cldr-annotations-en.xml" "$(SolutionDir)..\data\emoji\cldr-annotations-derived-en.xml" "$(SolutionDir)..\data\emoji\gemoji.json" "$(SolutionDir)..\data\emoji\VERSIONS" "$(ZoiteChatLib)emoji-data-table.h"
|
|
||||||
powershell -File "$(SolutionDir)..\win32\version-template.ps1" "$(SolutionDir)..\win32\config.h.tt" "$(ZoiteChatLib)config.h"
|
powershell -File "$(SolutionDir)..\win32\version-template.ps1" "$(SolutionDir)..\win32\config.h.tt" "$(ZoiteChatLib)config.h"
|
||||||
$(GlibGenMarshal) --prefix=_zoitechat_marshal --header "$(ProjectDir)marshalers.list" --output "$(ZoiteChatLib)marshal.h"
|
$(GlibGenMarshal) --prefix=_zoitechat_marshal --header "$(ProjectDir)marshalers.list" --output "$(ZoiteChatLib)marshal.h"
|
||||||
$(GlibGenMarshal) --prefix=_zoitechat_marshal --body "$(ProjectDir)marshalers.list" --output "$(ZoiteChatLib)marshal.c"
|
$(GlibGenMarshal) --prefix=_zoitechat_marshal --body "$(ProjectDir)marshalers.list" --output "$(ZoiteChatLib)marshal.c"
|
||||||
|
|||||||
@@ -1,239 +0,0 @@
|
|||||||
/* ZoiteChat
|
|
||||||
* Copyright (C) 2026 deepend-tildeclub.
|
|
||||||
*
|
|
||||||
* 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 2 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, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
#include <glib/gi18n.h>
|
|
||||||
|
|
||||||
#include "emoji-data.h"
|
|
||||||
|
|
||||||
#include "emoji-data-table.h"
|
|
||||||
|
|
||||||
gsize
|
|
||||||
emoji_data_count (void)
|
|
||||||
{
|
|
||||||
return G_N_ELEMENTS (emoji_entries_table);
|
|
||||||
}
|
|
||||||
|
|
||||||
const EmojiEntry *
|
|
||||||
emoji_data_entry (gsize index)
|
|
||||||
{
|
|
||||||
g_return_val_if_fail (index < G_N_ELEMENTS (emoji_entries_table), NULL);
|
|
||||||
|
|
||||||
return &emoji_entries_table[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
static GHashTable *
|
|
||||||
emoji_sequence_index (void)
|
|
||||||
{
|
|
||||||
static GHashTable *table = NULL;
|
|
||||||
gsize i;
|
|
||||||
int tone;
|
|
||||||
|
|
||||||
if (table)
|
|
||||||
return table;
|
|
||||||
|
|
||||||
table = g_hash_table_new (g_str_hash, g_str_equal);
|
|
||||||
for (i = 0; i < G_N_ELEMENTS (emoji_entries_table); i++)
|
|
||||||
{
|
|
||||||
const EmojiEntry *entry = &emoji_entries_table[i];
|
|
||||||
|
|
||||||
g_hash_table_insert (table, (gpointer) entry->sequence,
|
|
||||||
GSIZE_TO_POINTER (i * EMOJI_TONE_COUNT + 1));
|
|
||||||
if (entry->tone_set < 0)
|
|
||||||
continue;
|
|
||||||
for (tone = EMOJI_TONE_LIGHT; tone < EMOJI_TONE_COUNT; tone++)
|
|
||||||
{
|
|
||||||
const char *seq =
|
|
||||||
emoji_tone_sets_table[entry->tone_set].sequence[tone - 1];
|
|
||||||
|
|
||||||
g_hash_table_insert (table, (gpointer) seq,
|
|
||||||
GSIZE_TO_POINTER (i * EMOJI_TONE_COUNT + tone + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EmojiEntry *
|
|
||||||
emoji_data_find_by_sequence (const char *sequence, EmojiTone *tone_out)
|
|
||||||
{
|
|
||||||
gpointer value;
|
|
||||||
gsize packed;
|
|
||||||
|
|
||||||
if (tone_out)
|
|
||||||
*tone_out = EMOJI_TONE_NONE;
|
|
||||||
if (!sequence || !*sequence)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
value = g_hash_table_lookup (emoji_sequence_index (), sequence);
|
|
||||||
if (!value)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
packed = GPOINTER_TO_SIZE (value) - 1;
|
|
||||||
if (tone_out)
|
|
||||||
*tone_out = (EmojiTone) (packed % EMOJI_TONE_COUNT);
|
|
||||||
|
|
||||||
return &emoji_entries_table[packed / EMOJI_TONE_COUNT];
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *
|
|
||||||
emoji_data_group_display_name (EmojiGroup group)
|
|
||||||
{
|
|
||||||
static const char *const names[EMOJI_GROUP_COUNT] =
|
|
||||||
{
|
|
||||||
N_("Smileys & Emotion"),
|
|
||||||
N_("People & Body"),
|
|
||||||
N_("Animals & Nature"),
|
|
||||||
N_("Food & Drink"),
|
|
||||||
N_("Travel & Places"),
|
|
||||||
N_("Activities"),
|
|
||||||
N_("Objects"),
|
|
||||||
N_("Symbols"),
|
|
||||||
N_("Flags"),
|
|
||||||
};
|
|
||||||
|
|
||||||
g_return_val_if_fail (group < EMOJI_GROUP_COUNT, "");
|
|
||||||
|
|
||||||
return _(names[group]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *
|
|
||||||
emoji_data_group_icon_sequence (EmojiGroup group)
|
|
||||||
{
|
|
||||||
static const char *const icons[EMOJI_GROUP_COUNT] =
|
|
||||||
{
|
|
||||||
"\360\237\230\200", /* 😀 */
|
|
||||||
"\360\237\221\213", /* 👋 */
|
|
||||||
"\360\237\220\273", /* 🐻 */
|
|
||||||
"\360\237\215\224", /* 🍔 */
|
|
||||||
"\360\237\232\227", /* 🚗 */
|
|
||||||
"\342\232\275", /* ⚽ */
|
|
||||||
"\360\237\222\241", /* 💡 */
|
|
||||||
"\360\237\224\243", /* 🔣 */
|
|
||||||
"\360\237\217\201", /* 🏁 */
|
|
||||||
};
|
|
||||||
|
|
||||||
g_return_val_if_fail (group < EMOJI_GROUP_COUNT, "");
|
|
||||||
|
|
||||||
return icons[group];
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *
|
|
||||||
emoji_data_tone_display_name (EmojiTone tone)
|
|
||||||
{
|
|
||||||
static const char *const names[EMOJI_TONE_COUNT] =
|
|
||||||
{
|
|
||||||
N_("No skin tone"),
|
|
||||||
N_("Light skin tone"),
|
|
||||||
N_("Medium-light skin tone"),
|
|
||||||
N_("Medium skin tone"),
|
|
||||||
N_("Medium-dark skin tone"),
|
|
||||||
N_("Dark skin tone"),
|
|
||||||
};
|
|
||||||
|
|
||||||
g_return_val_if_fail (tone < EMOJI_TONE_COUNT, "");
|
|
||||||
|
|
||||||
return _(names[tone]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *
|
|
||||||
emoji_entry_sequence_for_tone (const EmojiEntry *entry, EmojiTone tone)
|
|
||||||
{
|
|
||||||
g_return_val_if_fail (entry != NULL, NULL);
|
|
||||||
|
|
||||||
if (tone == EMOJI_TONE_NONE || tone >= EMOJI_TONE_COUNT ||
|
|
||||||
entry->tone_set < 0)
|
|
||||||
return entry->sequence;
|
|
||||||
|
|
||||||
return emoji_tone_sets_table[entry->tone_set].sequence[tone - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
char **
|
|
||||||
emoji_search_tokenize (const char *text)
|
|
||||||
{
|
|
||||||
GPtrArray *tokens;
|
|
||||||
char *folded;
|
|
||||||
char **split;
|
|
||||||
gsize i;
|
|
||||||
|
|
||||||
if (!text)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
folded = g_utf8_casefold (text, -1);
|
|
||||||
split = g_strsplit_set (folded, " \t\n", -1);
|
|
||||||
g_free (folded);
|
|
||||||
|
|
||||||
tokens = g_ptr_array_new ();
|
|
||||||
for (i = 0; split[i]; i++)
|
|
||||||
{
|
|
||||||
const char *token = split[i];
|
|
||||||
gsize len;
|
|
||||||
|
|
||||||
while (*token == ':')
|
|
||||||
token++;
|
|
||||||
len = strlen (token);
|
|
||||||
while (len > 0 && token[len - 1] == ':')
|
|
||||||
len--;
|
|
||||||
if (len > 0)
|
|
||||||
g_ptr_array_add (tokens, g_strndup (token, len));
|
|
||||||
}
|
|
||||||
g_strfreev (split);
|
|
||||||
|
|
||||||
if (tokens->len == 0)
|
|
||||||
{
|
|
||||||
g_ptr_array_free (tokens, TRUE);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_ptr_array_add (tokens, NULL);
|
|
||||||
|
|
||||||
return (char **) g_ptr_array_free (tokens, FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
gboolean
|
|
||||||
emoji_entry_matches (const EmojiEntry *entry, char *const *needle_tokens)
|
|
||||||
{
|
|
||||||
gsize i;
|
|
||||||
|
|
||||||
g_return_val_if_fail (entry != NULL, FALSE);
|
|
||||||
|
|
||||||
if (!needle_tokens || !needle_tokens[0])
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
for (i = 0; needle_tokens[i]; i++)
|
|
||||||
{
|
|
||||||
if (!strstr (entry->search, needle_tokens[i]))
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *
|
|
||||||
emoji_data_unicode_version (void)
|
|
||||||
{
|
|
||||||
return emoji_data_unicode_version_str;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *
|
|
||||||
emoji_data_cldr_version (void)
|
|
||||||
{
|
|
||||||
return emoji_data_cldr_version_str;
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
/* ZoiteChat
|
|
||||||
* Copyright (C) 2026 deepend-tildeclub.
|
|
||||||
*
|
|
||||||
* 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 2 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, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Bundled emoji catalog, generated at build time from pinned Unicode,
|
|
||||||
* CLDR and gemoji data (see data/emoji/ and tools/gen-emoji-data.py).
|
|
||||||
* Every entry is a fully-qualified Unicode sequence; nothing here depends
|
|
||||||
* on the GTK runtime's emoji data. The catalog only decides what users
|
|
||||||
* can find and insert - rendering stays with the platform's fonts, and
|
|
||||||
* IRC always receives the plain Unicode sequence. */
|
|
||||||
|
|
||||||
#ifndef ZOITECHAT_EMOJI_DATA_H
|
|
||||||
#define ZOITECHAT_EMOJI_DATA_H
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
|
|
||||||
typedef enum
|
|
||||||
{
|
|
||||||
EMOJI_GROUP_SMILEYS_EMOTION,
|
|
||||||
EMOJI_GROUP_PEOPLE_BODY,
|
|
||||||
EMOJI_GROUP_ANIMALS_NATURE,
|
|
||||||
EMOJI_GROUP_FOOD_DRINK,
|
|
||||||
EMOJI_GROUP_TRAVEL_PLACES,
|
|
||||||
EMOJI_GROUP_ACTIVITIES,
|
|
||||||
EMOJI_GROUP_OBJECTS,
|
|
||||||
EMOJI_GROUP_SYMBOLS,
|
|
||||||
EMOJI_GROUP_FLAGS,
|
|
||||||
EMOJI_GROUP_COUNT
|
|
||||||
} EmojiGroup;
|
|
||||||
|
|
||||||
typedef enum
|
|
||||||
{
|
|
||||||
EMOJI_TONE_NONE,
|
|
||||||
EMOJI_TONE_LIGHT,
|
|
||||||
EMOJI_TONE_MEDIUM_LIGHT,
|
|
||||||
EMOJI_TONE_MEDIUM,
|
|
||||||
EMOJI_TONE_MEDIUM_DARK,
|
|
||||||
EMOJI_TONE_DARK,
|
|
||||||
EMOJI_TONE_COUNT
|
|
||||||
} EmojiTone;
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
const char *sequence;
|
|
||||||
const char *name;
|
|
||||||
const char *search;
|
|
||||||
const char *aliases;
|
|
||||||
guint8 group;
|
|
||||||
gint16 tone_set;
|
|
||||||
} EmojiEntry;
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
const char *sequence[EMOJI_TONE_COUNT - 1];
|
|
||||||
} EmojiToneSet;
|
|
||||||
|
|
||||||
gsize emoji_data_count (void);
|
|
||||||
const EmojiEntry *emoji_data_entry (gsize index);
|
|
||||||
|
|
||||||
const EmojiEntry *emoji_data_find_by_sequence (const char *sequence,
|
|
||||||
EmojiTone *tone_out);
|
|
||||||
|
|
||||||
const char *emoji_data_group_display_name (EmojiGroup group);
|
|
||||||
const char *emoji_data_group_icon_sequence (EmojiGroup group);
|
|
||||||
const char *emoji_data_tone_display_name (EmojiTone tone);
|
|
||||||
|
|
||||||
const char *emoji_entry_sequence_for_tone (const EmojiEntry *entry,
|
|
||||||
EmojiTone tone);
|
|
||||||
|
|
||||||
char **emoji_search_tokenize (const char *text);
|
|
||||||
gboolean emoji_entry_matches (const EmojiEntry *entry,
|
|
||||||
char *const *needle_tokens);
|
|
||||||
|
|
||||||
const char *emoji_data_unicode_version (void);
|
|
||||||
const char *emoji_data_cldr_version (void);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -3,7 +3,6 @@ common_sources = [
|
|||||||
'chanopt.c',
|
'chanopt.c',
|
||||||
'ctcp.c',
|
'ctcp.c',
|
||||||
'dcc.c',
|
'dcc.c',
|
||||||
'emoji-data.c',
|
|
||||||
'gtk3-theme-service.c',
|
'gtk3-theme-service.c',
|
||||||
'zoitechat.c',
|
'zoitechat.c',
|
||||||
'history.c',
|
'history.c',
|
||||||
@@ -110,20 +109,6 @@ public_suffix_data = custom_target('public_suffix_data_h',
|
|||||||
command: [python3, files('gen-public-suffix.py'), '@OUTPUT@']
|
command: [python3, files('gen-public-suffix.py'), '@OUTPUT@']
|
||||||
)
|
)
|
||||||
|
|
||||||
emoji_data_table = custom_target('emoji_data_table_h',
|
|
||||||
input: [
|
|
||||||
'../../data/emoji/emoji-test.txt',
|
|
||||||
'../../data/emoji/cldr-annotations-en.xml',
|
|
||||||
'../../data/emoji/cldr-annotations-derived-en.xml',
|
|
||||||
'../../data/emoji/gemoji.json',
|
|
||||||
'../../data/emoji/VERSIONS',
|
|
||||||
],
|
|
||||||
output: 'emoji-data-table.h',
|
|
||||||
command: [python3, files('../../tools/gen-emoji-data.py'),
|
|
||||||
'@INPUT0@', '@INPUT1@', '@INPUT2@', '@INPUT3@', '@INPUT4@',
|
|
||||||
'@OUTPUT@']
|
|
||||||
)
|
|
||||||
|
|
||||||
textevents = custom_target('textevents',
|
textevents = custom_target('textevents',
|
||||||
input: 'textevents.in',
|
input: 'textevents.in',
|
||||||
output: ['textevents.h', 'textenums.h'],
|
output: ['textevents.h', 'textenums.h'],
|
||||||
@@ -147,7 +132,7 @@ if get_option('plugin')
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
zoitechat_common = static_library('zoitechatcommon',
|
zoitechat_common = static_library('zoitechatcommon',
|
||||||
sources: [textevents, public_suffix_data, emoji_data_table] + marshal + common_sources + secretstore_sources,
|
sources: [textevents, public_suffix_data] + marshal + common_sources + secretstore_sources,
|
||||||
include_directories: config_h_include,
|
include_directories: config_h_include,
|
||||||
dependencies: common_deps + common_sysinfo_deps,
|
dependencies: common_deps + common_sysinfo_deps,
|
||||||
c_args: common_cflags,
|
c_args: common_cflags,
|
||||||
@@ -182,18 +167,3 @@ test('GTK3 Theme Service Tests', gtk3_theme_service_tests,
|
|||||||
protocol: 'tap',
|
protocol: 'tap',
|
||||||
timeout: 120,
|
timeout: 120,
|
||||||
)
|
)
|
||||||
|
|
||||||
emoji_data_tests = executable('emoji_data_tests',
|
|
||||||
[
|
|
||||||
'tests/test-emoji-data.c',
|
|
||||||
'emoji-data.c',
|
|
||||||
emoji_data_table,
|
|
||||||
],
|
|
||||||
include_directories: [config_h_include, include_directories('.')],
|
|
||||||
dependencies: [libgio_dep],
|
|
||||||
)
|
|
||||||
|
|
||||||
test('Emoji Data Tests', emoji_data_tests,
|
|
||||||
protocol: 'tap',
|
|
||||||
timeout: 120,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,240 +0,0 @@
|
|||||||
/* ZoiteChat
|
|
||||||
* Copyright (C) 2026 deepend-tildeclub.
|
|
||||||
*
|
|
||||||
* 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 2 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, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
|
|
||||||
#include "../emoji-data.h"
|
|
||||||
|
|
||||||
#define GRINNING_FACE "\360\237\230\200" /* 😀 */
|
|
||||||
#define DUCK "\360\237\246\206" /* 🦆 */
|
|
||||||
#define THUMBS_UP "\360\237\221\215" /* 👍 */
|
|
||||||
#define WAVE_DARK "\360\237\221\213\360\237\217\277" /* 👋🏿 */
|
|
||||||
#define MEDIUM_TONE_MODIFIER "\360\237\217\275" /* U+1F3FD */
|
|
||||||
#define KEYCAP_HASH "#\357\270\217\342\203\243" /* #️⃣ */
|
|
||||||
|
|
||||||
static const EmojiEntry *
|
|
||||||
find_by_name (const char *name)
|
|
||||||
{
|
|
||||||
gsize i;
|
|
||||||
|
|
||||||
for (i = 0; i < emoji_data_count (); i++)
|
|
||||||
{
|
|
||||||
const EmojiEntry *entry = emoji_data_entry (i);
|
|
||||||
|
|
||||||
if (!strcmp (entry->name, name))
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_catalog_integrity (void)
|
|
||||||
{
|
|
||||||
gsize i;
|
|
||||||
|
|
||||||
g_assert_cmpuint (emoji_data_count (), >=, 1800);
|
|
||||||
|
|
||||||
for (i = 0; i < emoji_data_count (); i++)
|
|
||||||
{
|
|
||||||
const EmojiEntry *entry = emoji_data_entry (i);
|
|
||||||
|
|
||||||
g_assert_nonnull (entry->sequence);
|
|
||||||
g_assert_true (*entry->sequence != '\0');
|
|
||||||
g_assert_true (g_utf8_validate (entry->sequence, -1, NULL));
|
|
||||||
g_assert_nonnull (entry->name);
|
|
||||||
g_assert_true (*entry->name != '\0');
|
|
||||||
g_assert_true (g_utf8_validate (entry->name, -1, NULL));
|
|
||||||
g_assert_nonnull (entry->search);
|
|
||||||
g_assert_true (*entry->search != '\0');
|
|
||||||
g_assert_nonnull (entry->aliases);
|
|
||||||
g_assert_cmpuint (entry->group, <, EMOJI_GROUP_COUNT);
|
|
||||||
g_assert_cmpint (entry->tone_set, >=, -1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_cldr_order (void)
|
|
||||||
{
|
|
||||||
const EmojiEntry *first = emoji_data_entry (0);
|
|
||||||
|
|
||||||
g_assert_cmpstr (first->sequence, ==, GRINNING_FACE);
|
|
||||||
g_assert_cmpuint (first->group, ==, EMOJI_GROUP_SMILEYS_EMOTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_groups (void)
|
|
||||||
{
|
|
||||||
int group;
|
|
||||||
gboolean seen[EMOJI_GROUP_COUNT] = { FALSE };
|
|
||||||
gsize i;
|
|
||||||
|
|
||||||
for (group = 0; group < EMOJI_GROUP_COUNT; group++)
|
|
||||||
{
|
|
||||||
g_assert_true (*emoji_data_group_display_name (group) != '\0');
|
|
||||||
g_assert_true (*emoji_data_group_icon_sequence (group) != '\0');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < emoji_data_count (); i++)
|
|
||||||
seen[emoji_data_entry (i)->group] = TRUE;
|
|
||||||
for (group = 0; group < EMOJI_GROUP_COUNT; group++)
|
|
||||||
g_assert_true (seen[group]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_search_by_keyword (void)
|
|
||||||
{
|
|
||||||
const EmojiEntry *duck = find_by_name ("duck");
|
|
||||||
char **tokens = emoji_search_tokenize ("Duck");
|
|
||||||
|
|
||||||
g_assert_nonnull (duck);
|
|
||||||
g_assert_cmpstr (duck->sequence, ==, DUCK);
|
|
||||||
g_assert_nonnull (tokens);
|
|
||||||
g_assert_true (emoji_entry_matches (duck, tokens));
|
|
||||||
g_strfreev (tokens);
|
|
||||||
|
|
||||||
tokens = emoji_search_tokenize ("bird");
|
|
||||||
g_assert_true (emoji_entry_matches (duck, tokens));
|
|
||||||
g_strfreev (tokens);
|
|
||||||
|
|
||||||
tokens = emoji_search_tokenize ("no-such-emoji-keyword");
|
|
||||||
g_assert_false (emoji_entry_matches (duck, tokens));
|
|
||||||
g_strfreev (tokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_search_by_alias (void)
|
|
||||||
{
|
|
||||||
const EmojiEntry *thumbs = find_by_name ("thumbs up");
|
|
||||||
char **tokens;
|
|
||||||
|
|
||||||
g_assert_nonnull (thumbs);
|
|
||||||
g_assert_nonnull (strstr (thumbs->aliases, "thumbsup"));
|
|
||||||
|
|
||||||
tokens = emoji_search_tokenize (":thumbsup:");
|
|
||||||
g_assert_nonnull (tokens);
|
|
||||||
g_assert_true (emoji_entry_matches (thumbs, tokens));
|
|
||||||
g_strfreev (tokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_multi_token_search (void)
|
|
||||||
{
|
|
||||||
const EmojiEntry *flag = find_by_name ("flag: Canada");
|
|
||||||
char **tokens = emoji_search_tokenize ("flag canada");
|
|
||||||
|
|
||||||
g_assert_nonnull (flag);
|
|
||||||
g_assert_cmpuint (flag->group, ==, EMOJI_GROUP_FLAGS);
|
|
||||||
g_assert_nonnull (tokens);
|
|
||||||
g_assert_true (emoji_entry_matches (flag, tokens));
|
|
||||||
g_strfreev (tokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_skin_tones (void)
|
|
||||||
{
|
|
||||||
const EmojiEntry *thumbs = find_by_name ("thumbs up");
|
|
||||||
const EmojiEntry *duck = find_by_name ("duck");
|
|
||||||
const char *toned;
|
|
||||||
|
|
||||||
g_assert_nonnull (thumbs);
|
|
||||||
g_assert_cmpint (thumbs->tone_set, >=, 0);
|
|
||||||
|
|
||||||
toned = emoji_entry_sequence_for_tone (thumbs, EMOJI_TONE_MEDIUM);
|
|
||||||
g_assert_cmpstr (toned, !=, thumbs->sequence);
|
|
||||||
g_assert_nonnull (strstr (toned, MEDIUM_TONE_MODIFIER));
|
|
||||||
g_assert_cmpstr (emoji_entry_sequence_for_tone (thumbs, EMOJI_TONE_NONE),
|
|
||||||
==, thumbs->sequence);
|
|
||||||
|
|
||||||
g_assert_nonnull (duck);
|
|
||||||
g_assert_cmpint (duck->tone_set, ==, -1);
|
|
||||||
g_assert_cmpstr (emoji_entry_sequence_for_tone (duck, EMOJI_TONE_DARK),
|
|
||||||
==, duck->sequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_find_by_sequence (void)
|
|
||||||
{
|
|
||||||
EmojiTone tone;
|
|
||||||
const EmojiEntry *entry;
|
|
||||||
|
|
||||||
entry = emoji_data_find_by_sequence (GRINNING_FACE, &tone);
|
|
||||||
g_assert_nonnull (entry);
|
|
||||||
g_assert_cmpstr (entry->name, ==, "grinning face");
|
|
||||||
g_assert_cmpuint (tone, ==, EMOJI_TONE_NONE);
|
|
||||||
|
|
||||||
entry = emoji_data_find_by_sequence (WAVE_DARK, &tone);
|
|
||||||
g_assert_nonnull (entry);
|
|
||||||
g_assert_cmpstr (entry->name, ==, "waving hand");
|
|
||||||
g_assert_cmpuint (tone, ==, EMOJI_TONE_DARK);
|
|
||||||
|
|
||||||
g_assert_null (emoji_data_find_by_sequence ("not an emoji", &tone));
|
|
||||||
g_assert_null (emoji_data_find_by_sequence ("", NULL));
|
|
||||||
g_assert_null (emoji_data_find_by_sequence (NULL, NULL));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_grapheme_sequences_intact (void)
|
|
||||||
{
|
|
||||||
const EmojiEntry *keycap = find_by_name ("keycap: #");
|
|
||||||
const EmojiEntry *family = find_by_name ("family: man, woman, boy");
|
|
||||||
|
|
||||||
g_assert_nonnull (keycap);
|
|
||||||
g_assert_cmpstr (keycap->sequence, ==, KEYCAP_HASH);
|
|
||||||
|
|
||||||
g_assert_nonnull (family);
|
|
||||||
g_assert_nonnull (strstr (family->sequence, "\342\200\215"));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_tokenize_edge_cases (void)
|
|
||||||
{
|
|
||||||
g_assert_null (emoji_search_tokenize (NULL));
|
|
||||||
g_assert_null (emoji_search_tokenize (""));
|
|
||||||
g_assert_null (emoji_search_tokenize (" "));
|
|
||||||
g_assert_null (emoji_search_tokenize (":::"));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
test_versions (void)
|
|
||||||
{
|
|
||||||
g_assert_true (*emoji_data_unicode_version () != '\0');
|
|
||||||
g_assert_true (*emoji_data_cldr_version () != '\0');
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main (int argc, char **argv)
|
|
||||||
{
|
|
||||||
g_test_init (&argc, &argv, NULL);
|
|
||||||
g_test_add_func ("/emoji-data/catalog-integrity", test_catalog_integrity);
|
|
||||||
g_test_add_func ("/emoji-data/cldr-order", test_cldr_order);
|
|
||||||
g_test_add_func ("/emoji-data/groups", test_groups);
|
|
||||||
g_test_add_func ("/emoji-data/search-by-keyword", test_search_by_keyword);
|
|
||||||
g_test_add_func ("/emoji-data/search-by-alias", test_search_by_alias);
|
|
||||||
g_test_add_func ("/emoji-data/multi-token-search", test_multi_token_search);
|
|
||||||
g_test_add_func ("/emoji-data/skin-tones", test_skin_tones);
|
|
||||||
g_test_add_func ("/emoji-data/find-by-sequence", test_find_by_sequence);
|
|
||||||
g_test_add_func ("/emoji-data/grapheme-sequences-intact",
|
|
||||||
test_grapheme_sequences_intact);
|
|
||||||
g_test_add_func ("/emoji-data/tokenize-edge-cases",
|
|
||||||
test_tokenize_edge_cases);
|
|
||||||
g_test_add_func ("/emoji-data/versions", test_versions);
|
|
||||||
return g_test_run ();
|
|
||||||
}
|
|
||||||
@@ -472,34 +472,6 @@ get_cpu_arch (void)
|
|||||||
return sysinfo_get_build_arch ();
|
return sysinfo_get_build_arch ();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* RtlGetVersion is used because GetVersionEx reports a version capped by
|
|
||||||
* the compatibility manifest rather than the real one. */
|
|
||||||
gboolean
|
|
||||||
win32_is_windows_8_or_newer (void)
|
|
||||||
{
|
|
||||||
typedef LONG (WINAPI *rtl_get_version_func) (PRTL_OSVERSIONINFOW);
|
|
||||||
rtl_get_version_func rtl_get_version;
|
|
||||||
RTL_OSVERSIONINFOW version;
|
|
||||||
HMODULE ntdll;
|
|
||||||
|
|
||||||
ntdll = GetModuleHandleW (L"ntdll.dll");
|
|
||||||
if (ntdll == NULL)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
rtl_get_version = (rtl_get_version_func) GetProcAddress (ntdll, "RtlGetVersion");
|
|
||||||
if (rtl_get_version == NULL)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
memset (&version, 0, sizeof (version));
|
|
||||||
version.dwOSVersionInfoSize = sizeof (version);
|
|
||||||
if (rtl_get_version (&version) != 0)
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
/* Windows 8 is 6.2; Windows 7 is 6.1 */
|
|
||||||
return version.dwMajorVersion > 6 ||
|
|
||||||
(version.dwMajorVersion == 6 && version.dwMinorVersion >= 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *
|
char *
|
||||||
get_sys_str (int with_cpu)
|
get_sys_str (int with_cpu)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ int waitline (int sok, char *buf, int bufsize, int);
|
|||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
int waitline2 (GIOChannel *source, char *buf, int bufsize);
|
int waitline2 (GIOChannel *source, char *buf, int bufsize);
|
||||||
int get_cpu_arch (void);
|
int get_cpu_arch (void);
|
||||||
gboolean win32_is_windows_8_or_newer (void);
|
|
||||||
#else
|
#else
|
||||||
#define waitline2(source,buf,size) waitline(serv->childread,buf,size,0)
|
#define waitline2(source,buf,size) waitline(serv->childread,buf,size,0)
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -259,7 +259,6 @@ struct zoitechatprefs
|
|||||||
int hex_gui_dialog_left;
|
int hex_gui_dialog_left;
|
||||||
int hex_gui_dialog_top;
|
int hex_gui_dialog_top;
|
||||||
int hex_gui_dialog_width;
|
int hex_gui_dialog_width;
|
||||||
int hex_gui_emoji_skin_tone;
|
|
||||||
int hex_gui_lagometer;
|
int hex_gui_lagometer;
|
||||||
int hex_gui_lang;
|
int hex_gui_lang;
|
||||||
int hex_gui_pane_divider_position;
|
int hex_gui_pane_divider_position;
|
||||||
|
|||||||
@@ -307,6 +307,109 @@ chanview_set_impl (chanview *cv, int type)
|
|||||||
cv->func_focus (cv->focused);
|
cv->func_focus (cv->focused);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
chanview_set_sorted (chanview *cv, gboolean sorted)
|
||||||
|
{
|
||||||
|
cv->sorted = sorted ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
chanview_set_use_icons (chanview *cv, gboolean use_icons)
|
||||||
|
{
|
||||||
|
cv->use_icons = use_icons ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
chanview_set_trunc_len (chanview *cv, int trunc_len)
|
||||||
|
{
|
||||||
|
cv->trunc_len = trunc_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
chan *ch;
|
||||||
|
int old_pos;
|
||||||
|
} chanview_sort_entry;
|
||||||
|
|
||||||
|
/* re-sort the session rows below one parent with cb_compare, leaving
|
||||||
|
* utility rows (tag != 0) in place; mirrors chanview_insert_sorted() */
|
||||||
|
static void
|
||||||
|
chanview_resort_children (chanview *cv, GtkTreeIter *parent)
|
||||||
|
{
|
||||||
|
GtkTreeModel *model = GTK_TREE_MODEL (cv->store);
|
||||||
|
GtkTreeIter iter;
|
||||||
|
chanview_sort_entry *entries;
|
||||||
|
int *positions;
|
||||||
|
gint *new_order;
|
||||||
|
chan *ch;
|
||||||
|
int n, i, j, count;
|
||||||
|
|
||||||
|
n = gtk_tree_model_iter_n_children (model, parent);
|
||||||
|
if (n < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entries = g_new0 (chanview_sort_entry, n);
|
||||||
|
positions = g_new0 (int, n);
|
||||||
|
new_order = g_new0 (gint, n);
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
new_order[i] = i;
|
||||||
|
if (!gtk_tree_model_iter_nth_child (model, &iter, parent, i))
|
||||||
|
continue;
|
||||||
|
gtk_tree_model_get (model, &iter, COL_CHAN, &ch, -1);
|
||||||
|
if (ch && ch->tag == 0 && ch->userdata)
|
||||||
|
{
|
||||||
|
entries[count].ch = ch;
|
||||||
|
entries[count].old_pos = i;
|
||||||
|
positions[count] = i;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stable insertion sort; tab groups stay small so this is cheap */
|
||||||
|
for (i = 1; i < count; i++)
|
||||||
|
{
|
||||||
|
chanview_sort_entry key = entries[i];
|
||||||
|
|
||||||
|
j = i - 1;
|
||||||
|
while (j >= 0 && cv->cb_compare (entries[j].ch->userdata, key.ch->userdata) > 0)
|
||||||
|
{
|
||||||
|
entries[j + 1] = entries[j];
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
entries[j + 1] = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
new_order[positions[i]] = entries[i].old_pos;
|
||||||
|
|
||||||
|
gtk_tree_store_reorder (cv->store, parent, new_order);
|
||||||
|
|
||||||
|
g_free (entries);
|
||||||
|
g_free (positions);
|
||||||
|
g_free (new_order);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* top-level rows keep their order (insertion never sorts them either);
|
||||||
|
* only the children within each family are sorted */
|
||||||
|
void
|
||||||
|
chanview_resort (chanview *cv)
|
||||||
|
{
|
||||||
|
GtkTreeIter iter;
|
||||||
|
|
||||||
|
if (!cv->cb_compare)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), &iter))
|
||||||
|
{
|
||||||
|
do
|
||||||
|
chanview_resort_children (cv, &iter);
|
||||||
|
while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
chanview_free_ch (chanview *cv, GtkTreeIter *iter)
|
chanview_free_ch (chanview *cv, GtkTreeIter *iter)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ void chanview_set_callbacks (chanview *cv,
|
|||||||
gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *),
|
gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *),
|
||||||
int (*cb_compare) (void *a, void *b));
|
int (*cb_compare) (void *a, void *b));
|
||||||
void chanview_set_impl (chanview *cv, int type);
|
void chanview_set_impl (chanview *cv, int type);
|
||||||
|
void chanview_set_sorted (chanview *cv, gboolean sorted);
|
||||||
|
void chanview_set_use_icons (chanview *cv, gboolean use_icons);
|
||||||
|
void chanview_set_trunc_len (chanview *cv, int trunc_len);
|
||||||
|
void chanview_resort (chanview *cv);
|
||||||
chan *chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon);
|
chan *chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon);
|
||||||
int chanview_get_size (chanview *cv);
|
int chanview_get_size (chanview *cv);
|
||||||
GtkWidget *chanview_get_box (chanview *cv);
|
GtkWidget *chanview_get_box (chanview *cv);
|
||||||
|
|||||||
@@ -1,779 +0,0 @@
|
|||||||
/* ZoiteChat
|
|
||||||
* Copyright (C) 2026 deepend-tildeclub.
|
|
||||||
*
|
|
||||||
* 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 2 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, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* ZoiteChat's own emoji picker. Replaces GTK's built-in "show-emoji-icon"
|
|
||||||
* entry machinery so that every build offers the same catalog (bundled at
|
|
||||||
* compile time from pinned Unicode/CLDR data, see src/common/emoji-data.h)
|
|
||||||
* regardless of the GTK runtime version or distribution emoji data.
|
|
||||||
* Selecting an emoji inserts the plain Unicode sequence at the cursor -
|
|
||||||
* nothing about what is sent over IRC changes. */
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "fe-gtk.h"
|
|
||||||
|
|
||||||
#include "../common/zoitechat.h"
|
|
||||||
#include "../common/zoitechatc.h"
|
|
||||||
#include "../common/cfgfiles.h"
|
|
||||||
#include "../common/emoji-data.h"
|
|
||||||
|
|
||||||
#include "emoji-picker.h"
|
|
||||||
#include "preferences-persistence.h"
|
|
||||||
|
|
||||||
#define EMOJI_CATEGORY_RECENT -1
|
|
||||||
#define EMOJI_RECENTS_MAX 32
|
|
||||||
#define EMOJI_SEARCH_MAX_RESULTS 256
|
|
||||||
#define EMOJI_GRID_COLUMNS 9
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
GtkWidget *button;
|
|
||||||
GtkWidget *target; /* weak pointer to the input entry */
|
|
||||||
GtkWidget *popover;
|
|
||||||
GtkWidget *search_entry;
|
|
||||||
GtkWidget *stack;
|
|
||||||
GtkWidget *grid; /* GtkFlowBox */
|
|
||||||
GtkWidget *scroller;
|
|
||||||
GtkWidget *empty_label;
|
|
||||||
GtkWidget *category_buttons[EMOJI_GROUP_COUNT + 1]; /* [0] = recents */
|
|
||||||
GtkWidget *tone_buttons[EMOJI_TONE_COUNT];
|
|
||||||
int category; /* EMOJI_CATEGORY_RECENT or EmojiGroup */
|
|
||||||
gboolean populated;
|
|
||||||
gboolean updating_buttons;
|
|
||||||
} EmojiPicker;
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------ *
|
|
||||||
* Recently used emoji, shared by all windows and persisted across runs
|
|
||||||
* ------------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
static GPtrArray *recent_emoji = NULL;
|
|
||||||
|
|
||||||
static char *
|
|
||||||
emoji_recents_filename (void)
|
|
||||||
{
|
|
||||||
return g_build_filename (get_xdir (), "emoji-recents.conf", NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_recents_load (void)
|
|
||||||
{
|
|
||||||
char *filename, *contents = NULL;
|
|
||||||
char **lines;
|
|
||||||
gsize i;
|
|
||||||
|
|
||||||
if (recent_emoji)
|
|
||||||
return;
|
|
||||||
|
|
||||||
recent_emoji = g_ptr_array_new_with_free_func (g_free);
|
|
||||||
|
|
||||||
filename = emoji_recents_filename ();
|
|
||||||
if (g_file_get_contents (filename, &contents, NULL, NULL))
|
|
||||||
{
|
|
||||||
lines = g_strsplit (contents, "\n", -1);
|
|
||||||
for (i = 0; lines[i] && recent_emoji->len < EMOJI_RECENTS_MAX; i++)
|
|
||||||
{
|
|
||||||
g_strstrip (lines[i]);
|
|
||||||
/* only keep sequences that exist in the bundled catalog */
|
|
||||||
if (*lines[i] && emoji_data_find_by_sequence (lines[i], NULL))
|
|
||||||
g_ptr_array_add (recent_emoji, g_strdup (lines[i]));
|
|
||||||
}
|
|
||||||
g_strfreev (lines);
|
|
||||||
g_free (contents);
|
|
||||||
}
|
|
||||||
g_free (filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_recents_save (void)
|
|
||||||
{
|
|
||||||
GString *contents = g_string_new (NULL);
|
|
||||||
char *filename;
|
|
||||||
gsize i;
|
|
||||||
|
|
||||||
for (i = 0; i < recent_emoji->len; i++)
|
|
||||||
{
|
|
||||||
g_string_append (contents, g_ptr_array_index (recent_emoji, i));
|
|
||||||
g_string_append_c (contents, '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
filename = emoji_recents_filename ();
|
|
||||||
g_file_set_contents (filename, contents->str, contents->len, NULL);
|
|
||||||
g_free (filename);
|
|
||||||
g_string_free (contents, TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_recents_add (const char *sequence)
|
|
||||||
{
|
|
||||||
GPtrArray *updated;
|
|
||||||
gsize i;
|
|
||||||
|
|
||||||
emoji_recents_load ();
|
|
||||||
|
|
||||||
updated = g_ptr_array_new_with_free_func (g_free);
|
|
||||||
g_ptr_array_add (updated, g_strdup (sequence));
|
|
||||||
for (i = 0; i < recent_emoji->len && updated->len < EMOJI_RECENTS_MAX; i++)
|
|
||||||
{
|
|
||||||
const char *old = g_ptr_array_index (recent_emoji, i);
|
|
||||||
|
|
||||||
if (strcmp (old, sequence) != 0)
|
|
||||||
g_ptr_array_add (updated, g_strdup (old));
|
|
||||||
}
|
|
||||||
g_ptr_array_free (recent_emoji, TRUE);
|
|
||||||
recent_emoji = updated;
|
|
||||||
|
|
||||||
emoji_recents_save ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------ *
|
|
||||||
* Appearance helpers
|
|
||||||
* ------------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
/* One application-wide provider so every emoji cell prefers a color emoji
|
|
||||||
* font without changing the fonts of surrounding widgets. Emoji families
|
|
||||||
* that are not installed are simply skipped by fontconfig/Pango. */
|
|
||||||
static void
|
|
||||||
emoji_picker_ensure_css (void)
|
|
||||||
{
|
|
||||||
static gboolean done = FALSE;
|
|
||||||
GtkCssProvider *provider;
|
|
||||||
|
|
||||||
if (done)
|
|
||||||
return;
|
|
||||||
done = TRUE;
|
|
||||||
|
|
||||||
provider = gtk_css_provider_new ();
|
|
||||||
gtk_css_provider_load_from_data (provider,
|
|
||||||
".zc-emoji-cell {"
|
|
||||||
" font-family: \"Noto Color Emoji\", \"Segoe UI Emoji\","
|
|
||||||
" \"Apple Color Emoji\", \"Twemoji Mozilla\", \"EmojiOne Color\";"
|
|
||||||
" font-size: 15pt;"
|
|
||||||
" padding: 2px;"
|
|
||||||
"}"
|
|
||||||
".zc-emoji-cell-small {"
|
|
||||||
" font-family: \"Noto Color Emoji\", \"Segoe UI Emoji\","
|
|
||||||
" \"Apple Color Emoji\", \"Twemoji Mozilla\", \"EmojiOne Color\";"
|
|
||||||
"}",
|
|
||||||
-1, NULL);
|
|
||||||
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
|
|
||||||
GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
|
||||||
g_object_unref (provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
static GtkWidget *
|
|
||||||
emoji_label_new (const char *sequence, gboolean small_cell)
|
|
||||||
{
|
|
||||||
GtkWidget *label = gtk_label_new (sequence);
|
|
||||||
|
|
||||||
gtk_style_context_add_class (gtk_widget_get_style_context (label),
|
|
||||||
small_cell ? "zc-emoji-cell-small" : "zc-emoji-cell");
|
|
||||||
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_picker_set_a11y_name (GtkWidget *widget, const char *name)
|
|
||||||
{
|
|
||||||
AtkObject *accessible = gtk_widget_get_accessible (widget);
|
|
||||||
|
|
||||||
if (accessible)
|
|
||||||
atk_object_set_name (accessible, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static EmojiTone
|
|
||||||
emoji_picker_current_tone (void)
|
|
||||||
{
|
|
||||||
int tone = prefs.hex_gui_emoji_skin_tone;
|
|
||||||
|
|
||||||
if (tone < EMOJI_TONE_NONE || tone >= EMOJI_TONE_COUNT)
|
|
||||||
return EMOJI_TONE_NONE;
|
|
||||||
|
|
||||||
return (EmojiTone) tone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* "waving hand" / "waving hand (Dark skin tone)", with :shortcodes: on a
|
|
||||||
* second line - used for both tooltips and accessible names. */
|
|
||||||
static char *
|
|
||||||
emoji_picker_describe (const EmojiEntry *entry, EmojiTone tone)
|
|
||||||
{
|
|
||||||
GString *desc = g_string_new (NULL);
|
|
||||||
char **aliases;
|
|
||||||
gsize i;
|
|
||||||
|
|
||||||
if (tone != EMOJI_TONE_NONE && entry->tone_set >= 0)
|
|
||||||
g_string_append_printf (desc, "%s (%s)", entry->name,
|
|
||||||
emoji_data_tone_display_name (tone));
|
|
||||||
else
|
|
||||||
g_string_append (desc, entry->name);
|
|
||||||
|
|
||||||
if (*entry->aliases)
|
|
||||||
{
|
|
||||||
g_string_append_c (desc, '\n');
|
|
||||||
aliases = g_strsplit (entry->aliases, " ", -1);
|
|
||||||
for (i = 0; aliases[i]; i++)
|
|
||||||
{
|
|
||||||
if (i > 0)
|
|
||||||
g_string_append_c (desc, ' ');
|
|
||||||
g_string_append_printf (desc, ":%s:", aliases[i]);
|
|
||||||
}
|
|
||||||
g_strfreev (aliases);
|
|
||||||
}
|
|
||||||
|
|
||||||
return g_string_free (desc, FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------ *
|
|
||||||
* Insertion
|
|
||||||
* ------------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_picker_insert (EmojiPicker *picker, const char *sequence)
|
|
||||||
{
|
|
||||||
GtkEditable *editable;
|
|
||||||
int pos;
|
|
||||||
|
|
||||||
if (!picker->target || !sequence || !*sequence)
|
|
||||||
return;
|
|
||||||
|
|
||||||
gtk_popover_popdown (GTK_POPOVER (picker->popover));
|
|
||||||
|
|
||||||
editable = GTK_EDITABLE (picker->target);
|
|
||||||
gtk_editable_delete_selection (editable);
|
|
||||||
pos = gtk_editable_get_position (editable);
|
|
||||||
gtk_editable_insert_text (editable, sequence, strlen (sequence), &pos);
|
|
||||||
|
|
||||||
/* focus the entry without letting GtkEntry select everything */
|
|
||||||
gtk_editable_set_editable (editable, FALSE);
|
|
||||||
gtk_widget_grab_focus (picker->target);
|
|
||||||
gtk_editable_set_editable (editable, TRUE);
|
|
||||||
gtk_editable_set_position (editable, pos);
|
|
||||||
|
|
||||||
emoji_recents_add (sequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_picker_activate_child (EmojiPicker *picker, GtkFlowBoxChild *child)
|
|
||||||
{
|
|
||||||
emoji_picker_insert (picker,
|
|
||||||
g_object_get_data (G_OBJECT (child), "emoji-sequence"));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_grid_child_activated_cb (GtkFlowBox *grid, GtkFlowBoxChild *child,
|
|
||||||
gpointer userdata)
|
|
||||||
{
|
|
||||||
emoji_picker_activate_child (userdata, child);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------ *
|
|
||||||
* Grid contents
|
|
||||||
* ------------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_grid_add (EmojiPicker *picker, const EmojiEntry *entry,
|
|
||||||
const char *sequence, EmojiTone tone)
|
|
||||||
{
|
|
||||||
GtkWidget *child, *label;
|
|
||||||
char *desc;
|
|
||||||
|
|
||||||
label = emoji_label_new (sequence, FALSE);
|
|
||||||
child = gtk_flow_box_child_new ();
|
|
||||||
gtk_container_add (GTK_CONTAINER (child), label);
|
|
||||||
g_object_set_data_full (G_OBJECT (child), "emoji-sequence",
|
|
||||||
g_strdup (sequence), g_free);
|
|
||||||
|
|
||||||
desc = emoji_picker_describe (entry, tone);
|
|
||||||
gtk_widget_set_tooltip_text (child, desc);
|
|
||||||
emoji_picker_set_a11y_name (child, desc);
|
|
||||||
g_free (desc);
|
|
||||||
|
|
||||||
gtk_container_add (GTK_CONTAINER (picker->grid), child);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_picker_refresh (EmojiPicker *picker)
|
|
||||||
{
|
|
||||||
const char *text = gtk_entry_get_text (GTK_ENTRY (picker->search_entry));
|
|
||||||
EmojiTone tone = emoji_picker_current_tone ();
|
|
||||||
GtkAdjustment *vadj;
|
|
||||||
gboolean any = FALSE;
|
|
||||||
gsize i, results = 0;
|
|
||||||
|
|
||||||
gtk_container_foreach (GTK_CONTAINER (picker->grid),
|
|
||||||
(GtkCallback) gtk_widget_destroy, NULL);
|
|
||||||
|
|
||||||
if (text && *text)
|
|
||||||
{
|
|
||||||
char **tokens = emoji_search_tokenize (text);
|
|
||||||
|
|
||||||
if (tokens)
|
|
||||||
{
|
|
||||||
for (i = 0; i < emoji_data_count () &&
|
|
||||||
results < EMOJI_SEARCH_MAX_RESULTS; i++)
|
|
||||||
{
|
|
||||||
const EmojiEntry *entry = emoji_data_entry (i);
|
|
||||||
|
|
||||||
if (!emoji_entry_matches (entry, tokens))
|
|
||||||
continue;
|
|
||||||
emoji_grid_add (picker, entry,
|
|
||||||
emoji_entry_sequence_for_tone (entry, tone), tone);
|
|
||||||
results++;
|
|
||||||
}
|
|
||||||
g_strfreev (tokens);
|
|
||||||
any = results > 0;
|
|
||||||
}
|
|
||||||
gtk_label_set_text (GTK_LABEL (picker->empty_label),
|
|
||||||
_("No emoji found"));
|
|
||||||
}
|
|
||||||
else if (picker->category == EMOJI_CATEGORY_RECENT)
|
|
||||||
{
|
|
||||||
emoji_recents_load ();
|
|
||||||
for (i = 0; i < recent_emoji->len; i++)
|
|
||||||
{
|
|
||||||
const char *sequence = g_ptr_array_index (recent_emoji, i);
|
|
||||||
EmojiTone seq_tone;
|
|
||||||
const EmojiEntry *entry =
|
|
||||||
emoji_data_find_by_sequence (sequence, &seq_tone);
|
|
||||||
|
|
||||||
if (!entry)
|
|
||||||
continue;
|
|
||||||
emoji_grid_add (picker, entry, sequence, seq_tone);
|
|
||||||
any = TRUE;
|
|
||||||
}
|
|
||||||
gtk_label_set_text (GTK_LABEL (picker->empty_label),
|
|
||||||
_("No recently used emoji"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (i = 0; i < emoji_data_count (); i++)
|
|
||||||
{
|
|
||||||
const EmojiEntry *entry = emoji_data_entry (i);
|
|
||||||
|
|
||||||
if (entry->group != picker->category)
|
|
||||||
continue;
|
|
||||||
emoji_grid_add (picker, entry,
|
|
||||||
emoji_entry_sequence_for_tone (entry, tone), tone);
|
|
||||||
any = TRUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gtk_widget_show_all (picker->grid);
|
|
||||||
gtk_stack_set_visible_child_name (GTK_STACK (picker->stack),
|
|
||||||
any ? "grid" : "empty");
|
|
||||||
|
|
||||||
vadj = gtk_scrolled_window_get_vadjustment (
|
|
||||||
GTK_SCROLLED_WINDOW (picker->scroller));
|
|
||||||
gtk_adjustment_set_value (vadj, gtk_adjustment_get_lower (vadj));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------ *
|
|
||||||
* Categories
|
|
||||||
* ------------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_picker_sync_category_buttons (EmojiPicker *picker)
|
|
||||||
{
|
|
||||||
gsize i;
|
|
||||||
|
|
||||||
picker->updating_buttons = TRUE;
|
|
||||||
for (i = 0; i < G_N_ELEMENTS (picker->category_buttons); i++)
|
|
||||||
{
|
|
||||||
int category = (int) i - 1;
|
|
||||||
|
|
||||||
gtk_toggle_button_set_active (
|
|
||||||
GTK_TOGGLE_BUTTON (picker->category_buttons[i]),
|
|
||||||
category == picker->category);
|
|
||||||
}
|
|
||||||
picker->updating_buttons = FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_category_toggled_cb (GtkToggleButton *button, gpointer userdata)
|
|
||||||
{
|
|
||||||
EmojiPicker *picker = userdata;
|
|
||||||
int category = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
|
|
||||||
"emoji-category"));
|
|
||||||
|
|
||||||
if (picker->updating_buttons)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!gtk_toggle_button_get_active (button))
|
|
||||||
{
|
|
||||||
/* the active category cannot be untoggled */
|
|
||||||
if (picker->category == category)
|
|
||||||
emoji_picker_sync_category_buttons (picker);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
picker->category = category;
|
|
||||||
emoji_picker_sync_category_buttons (picker);
|
|
||||||
|
|
||||||
/* leave search mode; clearing the entry triggers the refresh */
|
|
||||||
if (*gtk_entry_get_text (GTK_ENTRY (picker->search_entry)))
|
|
||||||
gtk_entry_set_text (GTK_ENTRY (picker->search_entry), "");
|
|
||||||
else
|
|
||||||
emoji_picker_refresh (picker);
|
|
||||||
}
|
|
||||||
|
|
||||||
static GtkWidget *
|
|
||||||
emoji_category_button_new (EmojiPicker *picker, int category,
|
|
||||||
const char *icon_sequence, const char *name)
|
|
||||||
{
|
|
||||||
GtkWidget *button = gtk_toggle_button_new ();
|
|
||||||
|
|
||||||
gtk_container_add (GTK_CONTAINER (button),
|
|
||||||
emoji_label_new (icon_sequence, TRUE));
|
|
||||||
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
|
|
||||||
gtk_widget_set_tooltip_text (button, name);
|
|
||||||
emoji_picker_set_a11y_name (button, name);
|
|
||||||
g_object_set_data (G_OBJECT (button), "emoji-category",
|
|
||||||
GINT_TO_POINTER (category));
|
|
||||||
g_signal_connect (G_OBJECT (button), "toggled",
|
|
||||||
G_CALLBACK (emoji_category_toggled_cb), picker);
|
|
||||||
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------ *
|
|
||||||
* Skin tones
|
|
||||||
* ------------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
#define EMOJI_TONE_SAMPLE "\342\234\213" /* ✋ raised hand */
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_tone_toggled_cb (GtkToggleButton *button, gpointer userdata)
|
|
||||||
{
|
|
||||||
EmojiPicker *picker = userdata;
|
|
||||||
int tone = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button),
|
|
||||||
"emoji-tone"));
|
|
||||||
|
|
||||||
if (!gtk_toggle_button_get_active (button))
|
|
||||||
return;
|
|
||||||
if (prefs.hex_gui_emoji_skin_tone == tone)
|
|
||||||
return;
|
|
||||||
|
|
||||||
prefs.hex_gui_emoji_skin_tone = tone;
|
|
||||||
preferences_persistence_save_all ();
|
|
||||||
emoji_picker_refresh (picker);
|
|
||||||
}
|
|
||||||
|
|
||||||
static GtkWidget *
|
|
||||||
emoji_tone_row_new (EmojiPicker *picker)
|
|
||||||
{
|
|
||||||
GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
|
||||||
GtkWidget *button;
|
|
||||||
GSList *group = NULL;
|
|
||||||
const EmojiEntry *sample = emoji_data_find_by_sequence (EMOJI_TONE_SAMPLE,
|
|
||||||
NULL);
|
|
||||||
int tone;
|
|
||||||
|
|
||||||
gtk_widget_set_halign (box, GTK_ALIGN_CENTER);
|
|
||||||
|
|
||||||
for (tone = EMOJI_TONE_NONE; tone < EMOJI_TONE_COUNT; tone++)
|
|
||||||
{
|
|
||||||
const char *sequence = EMOJI_TONE_SAMPLE;
|
|
||||||
|
|
||||||
if (sample)
|
|
||||||
sequence = emoji_entry_sequence_for_tone (sample, tone);
|
|
||||||
|
|
||||||
button = gtk_radio_button_new (group);
|
|
||||||
group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button));
|
|
||||||
gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE);
|
|
||||||
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
|
|
||||||
gtk_container_add (GTK_CONTAINER (button),
|
|
||||||
emoji_label_new (sequence, TRUE));
|
|
||||||
gtk_widget_set_tooltip_text (button,
|
|
||||||
emoji_data_tone_display_name (tone));
|
|
||||||
emoji_picker_set_a11y_name (button,
|
|
||||||
emoji_data_tone_display_name (tone));
|
|
||||||
g_object_set_data (G_OBJECT (button), "emoji-tone",
|
|
||||||
GINT_TO_POINTER (tone));
|
|
||||||
picker->tone_buttons[tone] = button;
|
|
||||||
|
|
||||||
if (emoji_picker_current_tone () == tone)
|
|
||||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
|
|
||||||
|
|
||||||
g_signal_connect (G_OBJECT (button), "toggled",
|
|
||||||
G_CALLBACK (emoji_tone_toggled_cb), picker);
|
|
||||||
|
|
||||||
gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* the pref can also change externally (/set gui_emoji_skin_tone) */
|
|
||||||
static void
|
|
||||||
emoji_picker_sync_tone_buttons (EmojiPicker *picker)
|
|
||||||
{
|
|
||||||
EmojiTone tone = emoji_picker_current_tone ();
|
|
||||||
|
|
||||||
gtk_toggle_button_set_active (
|
|
||||||
GTK_TOGGLE_BUTTON (picker->tone_buttons[tone]), TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------ *
|
|
||||||
* Search
|
|
||||||
* ------------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_search_changed_cb (GtkSearchEntry *entry, gpointer userdata)
|
|
||||||
{
|
|
||||||
emoji_picker_refresh (userdata);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_search_activate_cb (GtkEntry *entry, gpointer userdata)
|
|
||||||
{
|
|
||||||
EmojiPicker *picker = userdata;
|
|
||||||
GtkFlowBoxChild *child =
|
|
||||||
gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (picker->grid), 0);
|
|
||||||
|
|
||||||
/* Enter inserts the first hit */
|
|
||||||
if (child)
|
|
||||||
emoji_picker_activate_child (picker, child);
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
emoji_search_key_press_cb (GtkWidget *entry, GdkEventKey *event,
|
|
||||||
gpointer userdata)
|
|
||||||
{
|
|
||||||
EmojiPicker *picker = userdata;
|
|
||||||
GtkFlowBoxChild *child;
|
|
||||||
|
|
||||||
if (event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down)
|
|
||||||
{
|
|
||||||
child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (picker->grid), 0);
|
|
||||||
if (child)
|
|
||||||
{
|
|
||||||
gtk_widget_grab_focus (GTK_WIDGET (child));
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------ *
|
|
||||||
* Popover assembly
|
|
||||||
* ------------------------------------------------------------------------ */
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_picker_build_contents (EmojiPicker *picker)
|
|
||||||
{
|
|
||||||
GtkWidget *vbox, *categories, *button;
|
|
||||||
int group;
|
|
||||||
|
|
||||||
emoji_picker_ensure_css ();
|
|
||||||
|
|
||||||
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
|
|
||||||
g_object_set (vbox, "margin", 6, NULL);
|
|
||||||
|
|
||||||
picker->search_entry = gtk_search_entry_new ();
|
|
||||||
gtk_entry_set_placeholder_text (GTK_ENTRY (picker->search_entry),
|
|
||||||
_("Search emoji"));
|
|
||||||
emoji_picker_set_a11y_name (picker->search_entry, _("Search emoji"));
|
|
||||||
g_signal_connect (G_OBJECT (picker->search_entry), "search-changed",
|
|
||||||
G_CALLBACK (emoji_search_changed_cb), picker);
|
|
||||||
g_signal_connect (G_OBJECT (picker->search_entry), "activate",
|
|
||||||
G_CALLBACK (emoji_search_activate_cb), picker);
|
|
||||||
g_signal_connect (G_OBJECT (picker->search_entry), "key-press-event",
|
|
||||||
G_CALLBACK (emoji_search_key_press_cb), picker);
|
|
||||||
gtk_box_pack_start (GTK_BOX (vbox), picker->search_entry, FALSE, FALSE, 0);
|
|
||||||
|
|
||||||
categories = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
|
||||||
gtk_box_set_homogeneous (GTK_BOX (categories), TRUE);
|
|
||||||
|
|
||||||
picker->category_buttons[0] = emoji_category_button_new (picker,
|
|
||||||
EMOJI_CATEGORY_RECENT, "\360\237\225\230" /* 🕘 */,
|
|
||||||
_("Recently Used"));
|
|
||||||
gtk_box_pack_start (GTK_BOX (categories), picker->category_buttons[0],
|
|
||||||
TRUE, TRUE, 0);
|
|
||||||
for (group = 0; group < EMOJI_GROUP_COUNT; group++)
|
|
||||||
{
|
|
||||||
button = emoji_category_button_new (picker, group,
|
|
||||||
emoji_data_group_icon_sequence (group),
|
|
||||||
emoji_data_group_display_name (group));
|
|
||||||
picker->category_buttons[group + 1] = button;
|
|
||||||
gtk_box_pack_start (GTK_BOX (categories), button, TRUE, TRUE, 0);
|
|
||||||
}
|
|
||||||
gtk_box_pack_start (GTK_BOX (vbox), categories, FALSE, FALSE, 0);
|
|
||||||
|
|
||||||
picker->grid = gtk_flow_box_new ();
|
|
||||||
gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (picker->grid),
|
|
||||||
GTK_SELECTION_NONE);
|
|
||||||
gtk_flow_box_set_activate_on_single_click (GTK_FLOW_BOX (picker->grid),
|
|
||||||
TRUE);
|
|
||||||
gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (picker->grid),
|
|
||||||
EMOJI_GRID_COLUMNS);
|
|
||||||
gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (picker->grid),
|
|
||||||
EMOJI_GRID_COLUMNS);
|
|
||||||
gtk_flow_box_set_homogeneous (GTK_FLOW_BOX (picker->grid), TRUE);
|
|
||||||
gtk_widget_set_valign (picker->grid, GTK_ALIGN_START);
|
|
||||||
g_signal_connect (G_OBJECT (picker->grid), "child-activated",
|
|
||||||
G_CALLBACK (emoji_grid_child_activated_cb), picker);
|
|
||||||
|
|
||||||
picker->scroller = gtk_scrolled_window_new (NULL, NULL);
|
|
||||||
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (picker->scroller),
|
|
||||||
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
|
||||||
gtk_scrolled_window_set_min_content_height (
|
|
||||||
GTK_SCROLLED_WINDOW (picker->scroller), 300);
|
|
||||||
gtk_container_add (GTK_CONTAINER (picker->scroller), picker->grid);
|
|
||||||
|
|
||||||
picker->empty_label = gtk_label_new (_("No emoji found"));
|
|
||||||
gtk_style_context_add_class (
|
|
||||||
gtk_widget_get_style_context (picker->empty_label), "dim-label");
|
|
||||||
|
|
||||||
picker->stack = gtk_stack_new ();
|
|
||||||
gtk_widget_set_size_request (picker->stack, 380, -1);
|
|
||||||
gtk_stack_add_named (GTK_STACK (picker->stack), picker->scroller, "grid");
|
|
||||||
gtk_stack_add_named (GTK_STACK (picker->stack), picker->empty_label,
|
|
||||||
"empty");
|
|
||||||
gtk_box_pack_start (GTK_BOX (vbox), picker->stack, TRUE, TRUE, 0);
|
|
||||||
|
|
||||||
gtk_box_pack_start (GTK_BOX (vbox), emoji_tone_row_new (picker),
|
|
||||||
FALSE, FALSE, 0);
|
|
||||||
|
|
||||||
gtk_container_add (GTK_CONTAINER (picker->popover), vbox);
|
|
||||||
gtk_widget_show_all (vbox);
|
|
||||||
|
|
||||||
emoji_picker_sync_category_buttons (picker);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_popover_show_cb (GtkWidget *popover, gpointer userdata)
|
|
||||||
{
|
|
||||||
EmojiPicker *picker = userdata;
|
|
||||||
|
|
||||||
if (!picker->populated)
|
|
||||||
{
|
|
||||||
picker->populated = TRUE;
|
|
||||||
emoji_picker_build_contents (picker);
|
|
||||||
}
|
|
||||||
else if (*gtk_entry_get_text (GTK_ENTRY (picker->search_entry)))
|
|
||||||
{
|
|
||||||
gtk_entry_set_text (GTK_ENTRY (picker->search_entry), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
emoji_picker_sync_tone_buttons (picker);
|
|
||||||
emoji_picker_refresh (picker);
|
|
||||||
gtk_widget_grab_focus (picker->search_entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_popover_closed_cb (GtkWidget *popover, gpointer userdata)
|
|
||||||
{
|
|
||||||
EmojiPicker *picker = userdata;
|
|
||||||
GtkEditable *editable;
|
|
||||||
|
|
||||||
if (!picker->target)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* hand focus back to the input box without selecting its contents */
|
|
||||||
editable = GTK_EDITABLE (picker->target);
|
|
||||||
gtk_editable_set_editable (editable, FALSE);
|
|
||||||
gtk_widget_grab_focus (picker->target);
|
|
||||||
gtk_editable_set_editable (editable, TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *
|
|
||||||
emoji_picker_button_icon_name (void)
|
|
||||||
{
|
|
||||||
static const char *icon_names[] = {
|
|
||||||
"face-smile-symbolic",
|
|
||||||
"face-smile",
|
|
||||||
"insert-emoticon-symbolic",
|
|
||||||
"insert-emoticon",
|
|
||||||
"zc-menu-emoji",
|
|
||||||
};
|
|
||||||
GtkIconTheme *theme = gtk_icon_theme_get_default ();
|
|
||||||
gsize i;
|
|
||||||
|
|
||||||
if (!theme)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
for (i = 0; i < G_N_ELEMENTS (icon_names); i++)
|
|
||||||
{
|
|
||||||
if (gtk_icon_theme_has_icon (theme, icon_names[i]))
|
|
||||||
return icon_names[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
emoji_picker_free (gpointer data)
|
|
||||||
{
|
|
||||||
EmojiPicker *picker = data;
|
|
||||||
|
|
||||||
if (picker->target)
|
|
||||||
g_object_remove_weak_pointer (G_OBJECT (picker->target),
|
|
||||||
(gpointer *) &picker->target);
|
|
||||||
g_free (picker);
|
|
||||||
}
|
|
||||||
|
|
||||||
GtkWidget *
|
|
||||||
emoji_picker_button_new (GtkWidget *target_entry)
|
|
||||||
{
|
|
||||||
EmojiPicker *picker;
|
|
||||||
const char *icon_name;
|
|
||||||
|
|
||||||
g_return_val_if_fail (GTK_IS_ENTRY (target_entry), NULL);
|
|
||||||
|
|
||||||
picker = g_new0 (EmojiPicker, 1);
|
|
||||||
picker->target = target_entry;
|
|
||||||
picker->category = EMOJI_GROUP_SMILEYS_EMOTION;
|
|
||||||
g_object_add_weak_pointer (G_OBJECT (target_entry),
|
|
||||||
(gpointer *) &picker->target);
|
|
||||||
|
|
||||||
picker->button = gtk_menu_button_new ();
|
|
||||||
gtk_button_set_relief (GTK_BUTTON (picker->button), GTK_RELIEF_NONE);
|
|
||||||
gtk_widget_set_can_focus (picker->button, FALSE);
|
|
||||||
gtk_widget_set_tooltip_text (picker->button, _("Insert Emoji"));
|
|
||||||
emoji_picker_set_a11y_name (picker->button, _("Insert Emoji"));
|
|
||||||
g_object_set_data_full (G_OBJECT (picker->button), "emoji-picker",
|
|
||||||
picker, emoji_picker_free);
|
|
||||||
|
|
||||||
icon_name = emoji_picker_button_icon_name ();
|
|
||||||
if (icon_name)
|
|
||||||
{
|
|
||||||
gtk_button_set_image (GTK_BUTTON (picker->button),
|
|
||||||
gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* no themed icon anywhere: fall back to an emoji label so the
|
|
||||||
* button never turns into an unusable placeholder */
|
|
||||||
emoji_picker_ensure_css ();
|
|
||||||
gtk_container_add (GTK_CONTAINER (picker->button),
|
|
||||||
emoji_label_new ("\360\237\231\202" /* 🙂 */, TRUE));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* the popover shell is cheap; its contents are built on first open so
|
|
||||||
* startup cost stays unchanged */
|
|
||||||
picker->popover = gtk_popover_new (picker->button);
|
|
||||||
gtk_popover_set_position (GTK_POPOVER (picker->popover), GTK_POS_TOP);
|
|
||||||
g_signal_connect (G_OBJECT (picker->popover), "show",
|
|
||||||
G_CALLBACK (emoji_popover_show_cb), picker);
|
|
||||||
g_signal_connect (G_OBJECT (picker->popover), "closed",
|
|
||||||
G_CALLBACK (emoji_popover_closed_cb), picker);
|
|
||||||
gtk_menu_button_set_popover (GTK_MENU_BUTTON (picker->button),
|
|
||||||
picker->popover);
|
|
||||||
|
|
||||||
return picker->button;
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/* ZoiteChat
|
|
||||||
* Copyright (C) 2026 deepend-tildeclub.
|
|
||||||
*
|
|
||||||
* 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 2 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, write to the Free Software
|
|
||||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ZOITECHAT_EMOJI_PICKER_H
|
|
||||||
#define ZOITECHAT_EMOJI_PICKER_H
|
|
||||||
|
|
||||||
#include <gtk/gtk.h>
|
|
||||||
|
|
||||||
/* Creates the emoji picker button for an input entry. The returned
|
|
||||||
* GtkMenuButton opens a popover with search, categories, recently used
|
|
||||||
* emoji and skin-tone selection; activating an emoji inserts its Unicode
|
|
||||||
* sequence into target_entry at the cursor. The popover contents are
|
|
||||||
* built lazily on first open. */
|
|
||||||
GtkWidget *emoji_picker_button_new (GtkWidget *target_entry);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -102,86 +102,6 @@ static const GOptionEntry gopt_entries[] =
|
|||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* The emoji font is only bundled with Windows builds; other platforms
|
|
||||||
* rely on the operating system's emoji font (e.g. the distribution's
|
|
||||||
* Noto Color Emoji package on Linux). */
|
|
||||||
#ifdef WIN32
|
|
||||||
static gunichar2 *win32_private_emoji_font;
|
|
||||||
|
|
||||||
static char *
|
|
||||||
fe_bundled_emoji_font_path (void)
|
|
||||||
{
|
|
||||||
const char *override_path;
|
|
||||||
char *font_path = NULL;
|
|
||||||
|
|
||||||
override_path = g_getenv ("ZOITECHAT_EMOJI_FONT");
|
|
||||||
if (override_path && *override_path)
|
|
||||||
font_path = g_strdup (override_path);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
char *base_path;
|
|
||||||
|
|
||||||
base_path = g_win32_get_package_installation_directory_of_module (NULL);
|
|
||||||
if (base_path)
|
|
||||||
{
|
|
||||||
font_path = g_build_filename (base_path, "share", "fonts",
|
|
||||||
"zoitechat", "NotoColorEmoji.ttf", NULL);
|
|
||||||
g_free (base_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (font_path && !g_file_test (font_path, G_FILE_TEST_IS_REGULAR))
|
|
||||||
{
|
|
||||||
g_free (font_path);
|
|
||||||
font_path = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return font_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
fe_register_bundled_emoji_font (void)
|
|
||||||
{
|
|
||||||
char *font_path;
|
|
||||||
|
|
||||||
/* Windows 7 cannot render color emoji fonts, so the bundled Noto
|
|
||||||
* Color Emoji font is not registered there; emoji rendering falls
|
|
||||||
* back to the monochrome Segoe UI Symbol glyphs instead. */
|
|
||||||
if (!win32_is_windows_8_or_newer ())
|
|
||||||
{
|
|
||||||
g_debug ("Windows 7 detected; using Segoe UI Symbol for emoji");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
font_path = fe_bundled_emoji_font_path ();
|
|
||||||
if (!font_path)
|
|
||||||
{
|
|
||||||
g_debug ("Bundled Noto Color Emoji font was not found; using platform font fallback");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
win32_private_emoji_font = g_utf8_to_utf16 (font_path, -1, NULL, NULL, NULL);
|
|
||||||
if (!win32_private_emoji_font ||
|
|
||||||
AddFontResourceExW ((LPCWSTR) win32_private_emoji_font, FR_PRIVATE, NULL) == 0)
|
|
||||||
{
|
|
||||||
g_warning ("Unable to register bundled emoji font: %s", font_path);
|
|
||||||
g_clear_pointer (&win32_private_emoji_font, g_free);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free (font_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
fe_unregister_bundled_emoji_font (void)
|
|
||||||
{
|
|
||||||
if (win32_private_emoji_font)
|
|
||||||
{
|
|
||||||
RemoveFontResourceExW ((LPCWSTR) win32_private_emoji_font, FR_PRIVATE, NULL);
|
|
||||||
g_clear_pointer (&win32_private_emoji_font, g_free);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
static void
|
static void
|
||||||
create_msg_dialog (gchar *title, gchar *message)
|
create_msg_dialog (gchar *title, gchar *message)
|
||||||
@@ -520,8 +440,6 @@ fe_args (int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
fe_register_bundled_emoji_font ();
|
|
||||||
|
|
||||||
win32_set_gsettings_schema_dir ();
|
win32_set_gsettings_schema_dir ();
|
||||||
win32_set_appusermodelid ();
|
win32_set_appusermodelid ();
|
||||||
win32_configure_pixbuf_loaders ();
|
win32_configure_pixbuf_loaders ();
|
||||||
@@ -696,9 +614,6 @@ fe_main (void)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
gtk_main ();
|
gtk_main ();
|
||||||
#ifdef WIN32
|
|
||||||
fe_unregister_bundled_emoji_font ();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* sleep for 2 seconds so any QUIT messages are not lost. The */
|
/* sleep for 2 seconds so any QUIT messages are not lost. The */
|
||||||
/* GUI is closed at this point, so the user doesn't even know! */
|
/* GUI is closed at this point, so the user doesn't even know! */
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<AdditionalLibraryDirectories>$(ArchiveLibDir);$(DepsRoot)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
<AdditionalLibraryDirectories>$(ArchiveLibDir);$(DepsRoot)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||||
<AdditionalDependencies>$(DepLibs);$(ZoiteChatLib)common.lib;wbemuuid.lib;dwmapi.lib;gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
<AdditionalDependencies>$(DepLibs);$(ZoiteChatLib)common.lib;wbemuuid.lib;dwmapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
|
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
@@ -97,7 +97,6 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out-
|
|||||||
<ClCompile Include="custom-list.c" />
|
<ClCompile Include="custom-list.c" />
|
||||||
<ClCompile Include="dccgui.c" />
|
<ClCompile Include="dccgui.c" />
|
||||||
<ClCompile Include="editlist.c" />
|
<ClCompile Include="editlist.c" />
|
||||||
<ClCompile Include="emoji-picker.c" />
|
|
||||||
<ClCompile Include="fe-gtk.c" />
|
<ClCompile Include="fe-gtk.c" />
|
||||||
<ClCompile Include="fkeys.c" />
|
<ClCompile Include="fkeys.c" />
|
||||||
<ClCompile Include="gtkutil.c" />
|
<ClCompile Include="gtkutil.c" />
|
||||||
|
|||||||
@@ -168,28 +168,7 @@ gtkutil_append_font_css (GString *css, const PangoFontDescription *font_desc)
|
|||||||
const char *family = pango_font_description_get_family (font_desc);
|
const char *family = pango_font_description_get_family (font_desc);
|
||||||
|
|
||||||
if (family && *family)
|
if (family && *family)
|
||||||
{
|
g_string_append_printf (css, " font-family: \"%s\";", family);
|
||||||
char **families = g_strsplit (family, ",", -1);
|
|
||||||
gboolean appended = FALSE;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
g_string_append (css, " font-family:");
|
|
||||||
for (i = 0; families[i]; i++)
|
|
||||||
{
|
|
||||||
char *name = g_strstrip (families[i]);
|
|
||||||
|
|
||||||
if (!*name)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (appended)
|
|
||||||
g_string_append (css, ",");
|
|
||||||
g_string_append_printf (css, " \"%s\"", name);
|
|
||||||
appended = TRUE;
|
|
||||||
}
|
|
||||||
if (appended)
|
|
||||||
g_string_append (css, ";");
|
|
||||||
g_strfreev (families);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mask & PANGO_FONT_MASK_STYLE)
|
if (mask & PANGO_FONT_MASK_STYLE)
|
||||||
|
|||||||
@@ -45,7 +45,6 @@
|
|||||||
#include "theme/theme-manager.h"
|
#include "theme/theme-manager.h"
|
||||||
#include "theme/theme-css.h"
|
#include "theme/theme-css.h"
|
||||||
#include "banlist.h"
|
#include "banlist.h"
|
||||||
#include "emoji-picker.h"
|
|
||||||
#include "gtkutil.h"
|
#include "gtkutil.h"
|
||||||
#include "icon-resolver.h"
|
#include "icon-resolver.h"
|
||||||
#include "joind.h"
|
#include "joind.h"
|
||||||
@@ -201,7 +200,7 @@ enum
|
|||||||
#define TAG_IRC 0 /* server, channel, dialog */
|
#define TAG_IRC 0 /* server, channel, dialog */
|
||||||
#define TAG_UTIL 1 /* dcc, notify, chanlist */
|
#define TAG_UTIL 1 /* dcc, notify, chanlist */
|
||||||
|
|
||||||
static void mg_apply_emoji_fallback_widget (GtkWidget *widget, const PangoFontDescription *font);
|
static void mg_apply_emoji_fallback_widget (GtkWidget *widget);
|
||||||
static void mg_reply_show_child (GtkWidget *widget, gpointer data);
|
static void mg_reply_show_child (GtkWidget *widget, gpointer data);
|
||||||
|
|
||||||
#define MG_CONFIG_SAVE_DEBOUNCE_MS 250
|
#define MG_CONFIG_SAVE_DEBOUNCE_MS 250
|
||||||
@@ -570,6 +569,18 @@ mg_create_tab_colors (void)
|
|||||||
away_list = mg_attr_list_create (&gui_palette[THEME_TOKEN_TAB_AWAY], FALSE);
|
away_list = mg_attr_list_create (&gui_palette[THEME_TOKEN_TAB_AWAY], FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PangoAttrList *
|
||||||
|
mg_attr_list_for_tab_state (int state)
|
||||||
|
{
|
||||||
|
if (state & TAB_STATE_NEW_HILIGHT)
|
||||||
|
return nickseen_list;
|
||||||
|
if (state & TAB_STATE_NEW_MSG)
|
||||||
|
return newmsg_list;
|
||||||
|
if (state & TAB_STATE_NEW_DATA)
|
||||||
|
return newdata_list;
|
||||||
|
return plain_list;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
set_window_urgency (GtkWidget *win, gboolean set)
|
set_window_urgency (GtkWidget *win, gboolean set)
|
||||||
{
|
{
|
||||||
@@ -715,7 +726,10 @@ mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away)
|
|||||||
{
|
{
|
||||||
if (gui->op_xpm)
|
if (gui->op_xpm)
|
||||||
{
|
{
|
||||||
if (pix == gtk_image_get_pixbuf (GTK_IMAGE (gui->op_xpm))) /* no change? */
|
/* no change? (the pref check makes sure a live settings
|
||||||
|
change can still remove the icon) */
|
||||||
|
if (pix == gtk_image_get_pixbuf (GTK_IMAGE (gui->op_xpm)) &&
|
||||||
|
prefs.hex_gui_input_icon)
|
||||||
{
|
{
|
||||||
mg_set_myself_away (gui, away);
|
mg_set_myself_away (gui, away);
|
||||||
return;
|
return;
|
||||||
@@ -3232,7 +3246,7 @@ mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box)
|
|||||||
gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 23);
|
gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 23);
|
||||||
gtk_widget_set_size_request (gui->key_entry, 58, 11);
|
gtk_widget_set_size_request (gui->key_entry, 58, 11);
|
||||||
gtk_box_pack_start (GTK_BOX (box), gui->key_entry, 0, 0, 0);
|
gtk_box_pack_start (GTK_BOX (box), gui->key_entry, 0, 0, 0);
|
||||||
mg_apply_emoji_fallback_widget (gui->key_entry, NULL);
|
mg_apply_emoji_fallback_widget (gui->key_entry);
|
||||||
mg_apply_compact_mode_css (gui->key_entry);
|
mg_apply_compact_mode_css (gui->key_entry);
|
||||||
g_signal_connect (G_OBJECT (gui->key_entry), "activate",
|
g_signal_connect (G_OBJECT (gui->key_entry), "activate",
|
||||||
G_CALLBACK (mg_key_entry_cb), NULL);
|
G_CALLBACK (mg_key_entry_cb), NULL);
|
||||||
@@ -3250,7 +3264,7 @@ mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box)
|
|||||||
gtk_entry_set_width_chars (GTK_ENTRY (gui->limit_entry), 5);
|
gtk_entry_set_width_chars (GTK_ENTRY (gui->limit_entry), 5);
|
||||||
gtk_widget_set_size_request (gui->limit_entry, 45, 11);
|
gtk_widget_set_size_request (gui->limit_entry, 45, 11);
|
||||||
gtk_box_pack_start (GTK_BOX (box), gui->limit_entry, 0, 0, 0);
|
gtk_box_pack_start (GTK_BOX (box), gui->limit_entry, 0, 0, 0);
|
||||||
mg_apply_emoji_fallback_widget (gui->limit_entry, NULL);
|
mg_apply_emoji_fallback_widget (gui->limit_entry);
|
||||||
mg_apply_compact_mode_css (gui->limit_entry);
|
mg_apply_compact_mode_css (gui->limit_entry);
|
||||||
g_signal_connect (G_OBJECT (gui->limit_entry), "activate",
|
g_signal_connect (G_OBJECT (gui->limit_entry), "activate",
|
||||||
G_CALLBACK (mg_limit_entry_cb), NULL);
|
G_CALLBACK (mg_limit_entry_cb), NULL);
|
||||||
@@ -3515,13 +3529,8 @@ mg_apply_session_font_prefs (session_gui *gui)
|
|||||||
mg_topicbar_update_height (gui->topic_entry);
|
mg_topicbar_update_height (gui->topic_entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gui->input_box)
|
if (gui->input_box && prefs.hex_gui_input_style)
|
||||||
{
|
theme_manager_apply_entry_palette (gui->input_box, font);
|
||||||
if (prefs.hex_gui_input_style)
|
|
||||||
theme_manager_apply_entry_palette (gui->input_box, font);
|
|
||||||
mg_apply_emoji_fallback_widget (gui->input_box,
|
|
||||||
prefs.hex_gui_input_style ? font : NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gui->chanview)
|
if (gui->chanview)
|
||||||
chanview_apply_theme (gui->chanview);
|
chanview_apply_theme (gui->chanview);
|
||||||
@@ -3602,6 +3611,48 @@ mg_create_topicbar (session *sess, GtkWidget *box)
|
|||||||
mg_create_chanmodebuttons (gui, bbox);
|
mg_create_chanmodebuttons (gui, bbox);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* re-apply the multi-line topic and inline mode button preferences to an
|
||||||
|
* already built topic bar (see mg_create_topicbar () for the layout) */
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_update_topicbar_layout (session_gui *gui)
|
||||||
|
{
|
||||||
|
GtkWidget *topic = gui->topic_entry;
|
||||||
|
GtkWidget *vbox = gui->topic_bar;
|
||||||
|
GtkWidget *topic_scroll;
|
||||||
|
GtkWidget *hbox;
|
||||||
|
GtkWidget *mode_hbox;
|
||||||
|
GtkWidget *desired_parent;
|
||||||
|
GtkWidget *current_parent;
|
||||||
|
gboolean wrap;
|
||||||
|
|
||||||
|
if (!topic || !GTK_IS_TEXT_VIEW (topic) || !vbox || !gui->topicbutton_box)
|
||||||
|
return;
|
||||||
|
|
||||||
|
wrap = prefs.hex_gui_topicbar_multiline && !prefs.hex_gui_mode_buttons_inline;
|
||||||
|
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (topic),
|
||||||
|
wrap ? GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE);
|
||||||
|
|
||||||
|
topic_scroll = gtk_widget_get_parent (topic);
|
||||||
|
hbox = topic_scroll ? gtk_widget_get_parent (topic_scroll) : NULL;
|
||||||
|
mode_hbox = gtk_widget_get_parent (gui->topicbutton_box);
|
||||||
|
|
||||||
|
if (mode_hbox && hbox)
|
||||||
|
{
|
||||||
|
desired_parent = prefs.hex_gui_mode_buttons_inline ? hbox : vbox;
|
||||||
|
current_parent = gtk_widget_get_parent (mode_hbox);
|
||||||
|
if (current_parent && current_parent != desired_parent)
|
||||||
|
{
|
||||||
|
g_object_ref (mode_hbox);
|
||||||
|
gtk_container_remove (GTK_CONTAINER (current_parent), mode_hbox);
|
||||||
|
gtk_box_pack_start (GTK_BOX (desired_parent), mode_hbox, 0, 0, 0);
|
||||||
|
g_object_unref (mode_hbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mg_topicbar_update_height (topic);
|
||||||
|
}
|
||||||
|
|
||||||
/* check if a word is clickable */
|
/* check if a word is clickable */
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -4010,8 +4061,12 @@ mg_create_userlist (session_gui *gui, GtkWidget *box)
|
|||||||
gtk_widget_set_margin_end (gui->namelistinfo, 0);
|
gtk_widget_set_margin_end (gui->namelistinfo, 0);
|
||||||
gtk_widget_set_hexpand (gui->namelistinfo, TRUE);
|
gtk_widget_set_hexpand (gui->namelistinfo, TRUE);
|
||||||
gtk_widget_set_halign (gui->namelistinfo, GTK_ALIGN_FILL);
|
gtk_widget_set_halign (gui->namelistinfo, GTK_ALIGN_FILL);
|
||||||
|
/* always pack it so the preference can be toggled without a
|
||||||
|
restart; visibility is controlled explicitly */
|
||||||
|
gtk_widget_set_no_show_all (gui->namelistinfo, TRUE);
|
||||||
|
gtk_box_pack_start (GTK_BOX (vbox), gui->namelistinfo, 0, 0, 0);
|
||||||
if (prefs.hex_gui_ulist_count)
|
if (prefs.hex_gui_ulist_count)
|
||||||
gtk_box_pack_start (GTK_BOX (vbox), gui->namelistinfo, 0, 0, 0);
|
gtk_widget_show (gui->namelistinfo);
|
||||||
|
|
||||||
gui->user_tree = ulist = userlist_create (vbox);
|
gui->user_tree = ulist = userlist_create (vbox);
|
||||||
|
|
||||||
@@ -4368,31 +4423,13 @@ mg_inputbox_rightclick (GtkEntry *entry, GtkWidget *menu)
|
|||||||
* font size/style/weight, and without breaking user-configured fonts.
|
* font size/style/weight, and without breaking user-configured fonts.
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
static const char *
|
static const char *mg_emoji_family_fallback =
|
||||||
mg_emoji_family_fallback (void)
|
|
||||||
{
|
|
||||||
#ifdef G_OS_WIN32
|
#ifdef G_OS_WIN32
|
||||||
static char family_list[sizeof prefs.hex_text_font_alternative + 32];
|
"Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji, Apple Color Emoji, Twemoji Mozilla, EmojiOne Color";
|
||||||
|
|
||||||
if (!win32_is_windows_8_or_newer ())
|
|
||||||
{
|
|
||||||
if (prefs.hex_text_font_alternative[0])
|
|
||||||
{
|
|
||||||
g_snprintf (family_list, sizeof family_list, "%s, Segoe UI Symbol", prefs.hex_text_font_alternative);
|
|
||||||
return family_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Segoe UI Symbol";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Noto Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Apple Color Emoji, Twemoji Mozilla, EmojiOne Color";
|
|
||||||
#else
|
#else
|
||||||
return "Noto Color Emoji, Segoe UI Emoji, Apple Color Emoji, Twemoji Mozilla, EmojiOne Color";
|
"Noto Color Emoji, Segoe UI Emoji, Apple Color Emoji, Twemoji Mozilla, EmojiOne Color";
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef G_OS_WIN32
|
|
||||||
/* Only used by the Windows 7 fallback to GTK's built-in emoji chooser */
|
|
||||||
static const char *
|
static const char *
|
||||||
mg_find_available_icon_name (const char *const *icon_names)
|
mg_find_available_icon_name (const char *const *icon_names)
|
||||||
{
|
{
|
||||||
@@ -4424,7 +4461,6 @@ mg_find_available_icon_name (const char *const *icon_names)
|
|||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
mg_family_already_has_emoji (const gchar *family)
|
mg_family_already_has_emoji (const gchar *family)
|
||||||
@@ -4459,14 +4495,14 @@ mg_fontdesc_with_fallback (const PangoFontDescription *base_desc, gboolean emoji
|
|||||||
if (emoji_first)
|
if (emoji_first)
|
||||||
{
|
{
|
||||||
family_list = g_strdup_printf ("%s, %s",
|
family_list = g_strdup_printf ("%s, %s",
|
||||||
mg_emoji_family_fallback (),
|
mg_emoji_family_fallback,
|
||||||
(base_family && *base_family) ? base_family : "Sans");
|
(base_family && *base_family) ? base_family : "Sans");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
family_list = g_strdup_printf ("%s, %s",
|
family_list = g_strdup_printf ("%s, %s",
|
||||||
(base_family && *base_family) ? base_family : "Sans",
|
(base_family && *base_family) ? base_family : "Sans",
|
||||||
mg_emoji_family_fallback ());
|
mg_emoji_family_fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
pango_font_description_set_family (desc, family_list);
|
pango_font_description_set_family (desc, family_list);
|
||||||
@@ -4476,7 +4512,7 @@ mg_fontdesc_with_fallback (const PangoFontDescription *base_desc, gboolean emoji
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
mg_apply_emoji_fallback_widget (GtkWidget *widget, const PangoFontDescription *font)
|
mg_apply_emoji_fallback_widget (GtkWidget *widget)
|
||||||
{
|
{
|
||||||
PangoFontDescription *desc;
|
PangoFontDescription *desc;
|
||||||
GtkStyleContext *context;
|
GtkStyleContext *context;
|
||||||
@@ -4489,12 +4525,9 @@ mg_apply_emoji_fallback_widget (GtkWidget *widget, const PangoFontDescription *f
|
|||||||
if (!context)
|
if (!context)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (font)
|
gtk_style_context_get (context, GTK_STATE_FLAG_NORMAL,
|
||||||
base_desc = pango_font_description_copy (font);
|
"font", &base_desc,
|
||||||
else
|
NULL);
|
||||||
gtk_style_context_get (context, GTK_STATE_FLAG_NORMAL,
|
|
||||||
"font", &base_desc,
|
|
||||||
NULL);
|
|
||||||
if (!base_desc)
|
if (!base_desc)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -4699,7 +4732,7 @@ mg_create_search(session *sess, GtkWidget *box)
|
|||||||
gui->shentry = entry = gtk_entry_new();
|
gui->shentry = entry = gtk_entry_new();
|
||||||
gtk_box_pack_start(GTK_BOX(gui->shbox), entry, FALSE, FALSE, 0);
|
gtk_box_pack_start(GTK_BOX(gui->shbox), entry, FALSE, FALSE, 0);
|
||||||
gtk_widget_set_size_request (gui->shentry, 180, -1);
|
gtk_widget_set_size_request (gui->shentry, 180, -1);
|
||||||
mg_apply_emoji_fallback_widget (entry, NULL);
|
mg_apply_emoji_fallback_widget (entry);
|
||||||
mg_apply_entry_scroll_artifact_fix (entry);
|
mg_apply_entry_scroll_artifact_fix (entry);
|
||||||
gui->search_changed_signal = g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(search_handle_change), sess);
|
gui->search_changed_signal = g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(search_handle_change), sess);
|
||||||
g_signal_connect (G_OBJECT (entry), "key-press-event", G_CALLBACK (search_handle_esc), sess);
|
g_signal_connect (G_OBJECT (entry), "key-press-event", G_CALLBACK (search_handle_esc), sess);
|
||||||
@@ -4755,6 +4788,15 @@ mg_create_entry (session *sess, GtkWidget *box)
|
|||||||
{
|
{
|
||||||
GtkWidget *hbox, *but, *entry;
|
GtkWidget *hbox, *but, *entry;
|
||||||
session_gui *gui = sess->gui;
|
session_gui *gui = sess->gui;
|
||||||
|
const char *emoji_fallback_icon_names[] = {
|
||||||
|
"face-smile-symbolic",
|
||||||
|
"face-smile",
|
||||||
|
"insert-emoticon-symbolic",
|
||||||
|
"insert-emoticon",
|
||||||
|
"zc-menu-emoji",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
const char *emoji_fallback_icon_name;
|
||||||
|
|
||||||
gui->reply_box = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
|
gui->reply_box = mg_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
|
||||||
gtk_widget_set_name (gui->reply_box, "zoitechat-replybar");
|
gtk_widget_set_name (gui->reply_box, "zoitechat-replybar");
|
||||||
@@ -4807,45 +4849,16 @@ mg_create_entry (session *sess, GtkWidget *box)
|
|||||||
|
|
||||||
if (prefs.hex_gui_input_style)
|
if (prefs.hex_gui_input_style)
|
||||||
mg_apply_entry_style (entry);
|
mg_apply_entry_style (entry);
|
||||||
mg_apply_emoji_fallback_widget (entry,
|
|
||||||
prefs.hex_gui_input_style && input_style ? input_style->font_desc : NULL);
|
|
||||||
mg_apply_entry_scroll_artifact_fix (entry);
|
mg_apply_entry_scroll_artifact_fix (entry);
|
||||||
|
|
||||||
#ifdef G_OS_WIN32
|
g_object_set (G_OBJECT (entry), "show-emoji-icon", TRUE, NULL);
|
||||||
/* Windows 7 cannot render the color emoji font that ZoiteChat's own
|
|
||||||
* picker is designed around, so it keeps the GTK built-in emoji
|
if (gtk_entry_get_icon_storage_type (GTK_ENTRY (entry), GTK_ENTRY_ICON_SECONDARY) == GTK_IMAGE_EMPTY)
|
||||||
* chooser that was used before the picker existed (the HexChat-era
|
|
||||||
* behavior, which works on Windows 7). */
|
|
||||||
if (!win32_is_windows_8_or_newer ())
|
|
||||||
{
|
{
|
||||||
const char *emoji_fallback_icon_names[] = {
|
emoji_fallback_icon_name = mg_find_available_icon_name (emoji_fallback_icon_names);
|
||||||
"face-smile-symbolic",
|
if (emoji_fallback_icon_name)
|
||||||
"face-smile",
|
gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry), GTK_ENTRY_ICON_SECONDARY, emoji_fallback_icon_name);
|
||||||
"insert-emoticon-symbolic",
|
|
||||||
"insert-emoticon",
|
|
||||||
"zc-menu-emoji",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
g_object_set (G_OBJECT (entry), "show-emoji-icon", TRUE, NULL);
|
|
||||||
|
|
||||||
if (gtk_entry_get_icon_storage_type (GTK_ENTRY (entry), GTK_ENTRY_ICON_SECONDARY) == GTK_IMAGE_EMPTY)
|
|
||||||
{
|
|
||||||
const char *icon_name = mg_find_available_icon_name (emoji_fallback_icon_names);
|
|
||||||
|
|
||||||
if (icon_name)
|
|
||||||
gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry), GTK_ENTRY_ICON_SECONDARY, icon_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/* ZoiteChat's own emoji picker; deliberately not GTK's
|
|
||||||
* "show-emoji-icon" machinery, so the catalog is the same on every
|
|
||||||
* GTK runtime and the button always has a working callback */
|
|
||||||
but = emoji_picker_button_new (entry);
|
|
||||||
gtk_box_pack_start (GTK_BOX (hbox), but, FALSE, FALSE, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -5281,6 +5294,10 @@ mg_apply_setup (void)
|
|||||||
sess = list->data;
|
sess = list->data;
|
||||||
gtk_xtext_set_time_stamp (sess->res->buffer, prefs.hex_stamp_text);
|
gtk_xtext_set_time_stamp (sess->res->buffer, prefs.hex_stamp_text);
|
||||||
((xtext_buffer *)sess->res->buffer)->needs_recalc = TRUE;
|
((xtext_buffer *)sess->res->buffer)->needs_recalc = TRUE;
|
||||||
|
/* the attribute lists were just recreated, so re-apply the
|
||||||
|
color matching each tab's activity state */
|
||||||
|
if (sess->res->tab)
|
||||||
|
chan_set_color (sess->res->tab, mg_attr_list_for_tab_state (sess->tab_state));
|
||||||
if (!sess->gui->is_tab || !done_main)
|
if (!sess->gui->is_tab || !done_main)
|
||||||
mg_place_userlist_and_chanview (sess->gui);
|
mg_place_userlist_and_chanview (sess->gui);
|
||||||
if (sess->gui->is_tab)
|
if (sess->gui->is_tab)
|
||||||
@@ -5323,6 +5340,49 @@ fe_buttons_update (session *sess)
|
|||||||
gtk_widget_hide (sess->gui->button_box);
|
gtk_widget_hide (sess->gui->button_box);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* tab label for a session that has no joined channel: the channel we are
|
||||||
|
* waiting to join in parentheses, or "<none>". tbuf must hold CHANLEN+6. */
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_tab_pending_name (session *sess, char *tbuf)
|
||||||
|
{
|
||||||
|
if (sess->waitchannel[0])
|
||||||
|
{
|
||||||
|
if (prefs.hex_gui_tab_trunc > 2 && g_utf8_strlen (sess->waitchannel, -1) > prefs.hex_gui_tab_trunc)
|
||||||
|
{
|
||||||
|
/* truncate long channel names */
|
||||||
|
tbuf[0] = '(';
|
||||||
|
strcpy (tbuf + 1, sess->waitchannel);
|
||||||
|
g_utf8_offset_to_pointer(tbuf, prefs.hex_gui_tab_trunc)[0] = 0;
|
||||||
|
strcat (tbuf, "..)");
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
sprintf (tbuf, "(%s)", sess->waitchannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
strcpy (tbuf, _("<none>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* re-apply the tab label truncation preference to a session's tab */
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_retrunc_tab (session *sess)
|
||||||
|
{
|
||||||
|
char tbuf[CHANLEN+6];
|
||||||
|
|
||||||
|
if (!sess->res->tab)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (sess->channel[0])
|
||||||
|
chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mg_tab_pending_name (sess, tbuf);
|
||||||
|
chan_rename (sess->res->tab, tbuf, prefs.hex_gui_tab_trunc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
fe_clear_channel (session *sess)
|
fe_clear_channel (session *sess)
|
||||||
{
|
{
|
||||||
@@ -5331,22 +5391,7 @@ fe_clear_channel (session *sess)
|
|||||||
|
|
||||||
if (sess->gui->is_tab)
|
if (sess->gui->is_tab)
|
||||||
{
|
{
|
||||||
if (sess->waitchannel[0])
|
mg_tab_pending_name (sess, tbuf);
|
||||||
{
|
|
||||||
if (prefs.hex_gui_tab_trunc > 2 && g_utf8_strlen (sess->waitchannel, -1) > prefs.hex_gui_tab_trunc)
|
|
||||||
{
|
|
||||||
/* truncate long channel names */
|
|
||||||
tbuf[0] = '(';
|
|
||||||
strcpy (tbuf + 1, sess->waitchannel);
|
|
||||||
g_utf8_offset_to_pointer(tbuf, prefs.hex_gui_tab_trunc)[0] = 0;
|
|
||||||
strcat (tbuf, "..)");
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
sprintf (tbuf, "(%s)", sess->waitchannel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
strcpy (tbuf, _("<none>"));
|
|
||||||
chan_rename (sess->res->tab, tbuf, prefs.hex_gui_tab_trunc);
|
chan_rename (sess->res->tab, tbuf, prefs.hex_gui_tab_trunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5470,6 +5515,110 @@ fe_set_channel (session *sess)
|
|||||||
chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc);
|
chan_rename (sess->res->tab, sess->channel, prefs.hex_gui_tab_trunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mg_apply_live_prefs_to_gui (session_gui *gui, session *sess, const mg_live_prefs *changes)
|
||||||
|
{
|
||||||
|
if (changes->meters)
|
||||||
|
mg_update_meters (gui);
|
||||||
|
|
||||||
|
if (changes->ulist_columns && gui->user_tree)
|
||||||
|
userlist_rebuild_columns (gui->user_tree);
|
||||||
|
|
||||||
|
if (changes->ulist_count && gui->namelistinfo)
|
||||||
|
{
|
||||||
|
if (prefs.hex_gui_ulist_count)
|
||||||
|
gtk_widget_show (gui->namelistinfo);
|
||||||
|
else
|
||||||
|
gtk_widget_hide (gui->namelistinfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes->input_box && gui->nick_box)
|
||||||
|
{
|
||||||
|
if (prefs.hex_gui_input_nick)
|
||||||
|
gtk_widget_show (gui->nick_box);
|
||||||
|
else
|
||||||
|
gtk_widget_hide (gui->nick_box);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* refresh the access icon; re-filling the user list rows can
|
||||||
|
clear it as a side effect, so restore it here too */
|
||||||
|
if ((changes->input_box || changes->ulist_rows) && sess && is_session (sess))
|
||||||
|
{
|
||||||
|
if (sess->type == SESS_DIALOG)
|
||||||
|
mg_set_access_icon (gui, NULL, sess->server->is_away);
|
||||||
|
else
|
||||||
|
mg_set_access_icon (gui, get_user_icon (sess->server, sess->me), sess->server->is_away);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes->topic_bar)
|
||||||
|
mg_update_topicbar_layout (gui);
|
||||||
|
|
||||||
|
if (changes->transparency && gui->window)
|
||||||
|
gtk_widget_set_opacity (gui->window, (prefs.hex_gui_transparency / 255.));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* apply preference changes that used to require a restart. called by
|
||||||
|
* setup.c after the new prefs have been committed. */
|
||||||
|
|
||||||
|
void
|
||||||
|
mg_apply_live_prefs (const mg_live_prefs *changes)
|
||||||
|
{
|
||||||
|
GSList *list;
|
||||||
|
session *sess;
|
||||||
|
int done_main = FALSE;
|
||||||
|
|
||||||
|
if (!changes)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* the tab bar / channel tree lives in the shared tab window */
|
||||||
|
if (mg_gui && mg_gui->chanview)
|
||||||
|
{
|
||||||
|
if (changes->tab_resort)
|
||||||
|
{
|
||||||
|
chanview_set_sorted (mg_gui->chanview, prefs.hex_gui_tab_sort);
|
||||||
|
chanview_resort (mg_gui->chanview);
|
||||||
|
}
|
||||||
|
if (changes->tab_trunc)
|
||||||
|
chanview_set_trunc_len (mg_gui->chanview, prefs.hex_gui_tab_trunc);
|
||||||
|
if (changes->chanview || changes->tab_resort)
|
||||||
|
{
|
||||||
|
chanview_set_use_icons (mg_gui->chanview,
|
||||||
|
prefs.hex_gui_tab_icons && (pix_tree_channel || pix_tree_dialog ||
|
||||||
|
pix_tree_server || pix_tree_util));
|
||||||
|
/* rebuild the tab bar / tree widgets from the model */
|
||||||
|
chanview_set_impl (mg_gui->chanview, prefs.hex_gui_tab_layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list = sess_list;
|
||||||
|
while (list)
|
||||||
|
{
|
||||||
|
sess = list->data;
|
||||||
|
|
||||||
|
if (changes->tab_trunc)
|
||||||
|
mg_retrunc_tab (sess);
|
||||||
|
if (changes->ulist_sort)
|
||||||
|
userlist_apply_sort (sess);
|
||||||
|
if (changes->ulist_rows)
|
||||||
|
userlist_refill (sess);
|
||||||
|
|
||||||
|
if (sess->gui->is_tab)
|
||||||
|
{
|
||||||
|
/* only apply to the shared tab window once, using the
|
||||||
|
front-most session for session-dependent bits */
|
||||||
|
if (!done_main)
|
||||||
|
{
|
||||||
|
done_main = TRUE;
|
||||||
|
mg_apply_live_prefs_to_gui (sess->gui, current_tab, changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
mg_apply_live_prefs_to_gui (sess->gui, sess, changes);
|
||||||
|
|
||||||
|
list = list->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
fe_set_typing (session *sess, const char *nick, const char *state)
|
fe_set_typing (session *sess, const char *nick, const char *state)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,6 +37,24 @@ GtkWidget *mg_create_generic_tab (char *name, char *title, int force_toplevel, i
|
|||||||
void mg_set_title (GtkWidget *button, char *title);
|
void mg_set_title (GtkWidget *button, char *title);
|
||||||
void mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away);
|
void mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away);
|
||||||
void mg_apply_setup (void);
|
void mg_apply_setup (void);
|
||||||
|
|
||||||
|
/* which parts of the GUI need a live rebuild after a preferences change */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned int chanview:1; /* rebuild the tab bar / channel tree */
|
||||||
|
unsigned int tab_resort:1; /* re-sort channel tabs */
|
||||||
|
unsigned int tab_trunc:1; /* re-truncate tab labels */
|
||||||
|
unsigned int ulist_columns:1; /* rebuild user list columns */
|
||||||
|
unsigned int ulist_rows:1; /* re-fill user list rows */
|
||||||
|
unsigned int ulist_sort:1; /* re-apply user list sort order */
|
||||||
|
unsigned int ulist_count:1; /* user count label visibility */
|
||||||
|
unsigned int meters:1; /* rebuild lag/throttle meters */
|
||||||
|
unsigned int input_box:1; /* nick box visibility and access icon */
|
||||||
|
unsigned int topic_bar:1; /* topic wrap mode and mode button placement */
|
||||||
|
unsigned int transparency:1; /* main window opacity */
|
||||||
|
} mg_live_prefs;
|
||||||
|
|
||||||
|
void mg_apply_live_prefs (const mg_live_prefs *changes);
|
||||||
void mg_apply_session_font_prefs (session_gui *gui);
|
void mg_apply_session_font_prefs (session_gui *gui);
|
||||||
void mg_close_sess (session *);
|
void mg_close_sess (session *);
|
||||||
void mg_tab_close (session *sess);
|
void mg_tab_close (session *sess);
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ zoitechat_gtk_sources = [
|
|||||||
'custom-list.c',
|
'custom-list.c',
|
||||||
'dccgui.c',
|
'dccgui.c',
|
||||||
'editlist.c',
|
'editlist.c',
|
||||||
'emoji-picker.c',
|
|
||||||
'fe-gtk.c',
|
'fe-gtk.c',
|
||||||
'fkeys.c',
|
'fkeys.c',
|
||||||
'gtkutil.c',
|
'gtkutil.c',
|
||||||
|
|||||||
@@ -1045,28 +1045,30 @@ setup_create_spin (GtkWidget *table, int row, const setting *set)
|
|||||||
return wid;
|
return wid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static guint setup_trans_apply_tag = 0;
|
||||||
|
/* the window opacity the dialog opened with, so cancelling can undo the
|
||||||
|
* live preview done by the slider */
|
||||||
|
static int setup_transparency_snapshot;
|
||||||
|
|
||||||
static gint
|
static gint
|
||||||
setup_apply_trans (int *tag)
|
setup_apply_trans (gpointer userdata)
|
||||||
{
|
{
|
||||||
prefs.hex_gui_transparency = setup_prefs.hex_gui_transparency;
|
prefs.hex_gui_transparency = setup_prefs.hex_gui_transparency;
|
||||||
gtk_widget_set_opacity (current_sess->gui->window,
|
if (current_sess)
|
||||||
(prefs.hex_gui_transparency / 255.));
|
gtk_widget_set_opacity (current_sess->gui->window,
|
||||||
|
(prefs.hex_gui_transparency / 255.));
|
||||||
|
|
||||||
*tag = 0;
|
setup_trans_apply_tag = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
setup_hscale_cb (GtkRange *wid, const setting *set)
|
setup_hscale_cb (GtkRange *wid, const setting *set)
|
||||||
{
|
{
|
||||||
static int tag = 0;
|
|
||||||
|
|
||||||
setup_set_int (&setup_prefs, set, (int) gtk_range_get_value (wid));
|
setup_set_int (&setup_prefs, set, (int) gtk_range_get_value (wid));
|
||||||
|
|
||||||
if (tag == 0)
|
if (setup_trans_apply_tag == 0)
|
||||||
{
|
setup_trans_apply_tag = g_idle_add ((GSourceFunc) setup_apply_trans, NULL);
|
||||||
tag = g_idle_add ((GSourceFunc) setup_apply_trans, &tag);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -1091,7 +1093,8 @@ setup_create_hscale (GtkWidget *table, int row, const setting *set)
|
|||||||
|
|
||||||
#ifndef WIN32 /* Windows always supports this */
|
#ifndef WIN32 /* Windows always supports this */
|
||||||
/* Only used for transparency currently */
|
/* Only used for transparency currently */
|
||||||
if (!gdk_screen_is_composited (gtk_widget_get_screen (current_sess->gui->window)))
|
if (current_sess &&
|
||||||
|
!gdk_screen_is_composited (gtk_widget_get_screen (current_sess->gui->window)))
|
||||||
gtk_widget_set_sensitive (wid, FALSE);
|
gtk_widget_set_sensitive (wid, FALSE);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -2293,7 +2296,9 @@ setup_apply (struct zoitechatprefs *pr)
|
|||||||
ThemeChangedEvent event;
|
ThemeChangedEvent event;
|
||||||
struct zoitechatprefs old_prefs = prefs;
|
struct zoitechatprefs old_prefs = prefs;
|
||||||
int old_dark_mode = prefs.hex_gui_dark_mode;
|
int old_dark_mode = prefs.hex_gui_dark_mode;
|
||||||
|
mg_live_prefs live_changes;
|
||||||
|
|
||||||
|
memset (&live_changes, 0, sizeof (live_changes));
|
||||||
|
|
||||||
#define DIFF(a) (pr->a != prefs.a)
|
#define DIFF(a) (pr->a != prefs.a)
|
||||||
|
|
||||||
@@ -2301,40 +2306,49 @@ setup_apply (struct zoitechatprefs *pr)
|
|||||||
if (DIFF (hex_gui_lang))
|
if (DIFF (hex_gui_lang))
|
||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
#endif
|
#endif
|
||||||
if (DIFF (hex_gui_compact))
|
/* server tabs are only created when a connection is opened, so
|
||||||
noapply = TRUE;
|
existing connections keep their current tabs until a restart;
|
||||||
if (DIFF (hex_gui_input_icon))
|
the channel tree indentation is still re-applied below */
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_input_nick))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_lagometer))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_mode_buttons_inline))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_tab_icons))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_tab_closebuttons))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_tab_server))
|
if (DIFF (hex_gui_tab_server))
|
||||||
|
{
|
||||||
noapply = TRUE;
|
noapply = TRUE;
|
||||||
if (DIFF (hex_gui_tab_small))
|
live_changes.chanview = TRUE;
|
||||||
noapply = TRUE;
|
}
|
||||||
|
|
||||||
|
if (DIFF (hex_gui_compact))
|
||||||
|
{
|
||||||
|
live_changes.chanview = TRUE;
|
||||||
|
live_changes.ulist_columns = TRUE;
|
||||||
|
}
|
||||||
|
if (DIFF (hex_gui_input_icon) || DIFF (hex_gui_input_nick))
|
||||||
|
live_changes.input_box = TRUE;
|
||||||
|
if (DIFF (hex_gui_lagometer) || DIFF (hex_gui_throttlemeter))
|
||||||
|
live_changes.meters = TRUE;
|
||||||
|
if (DIFF (hex_gui_mode_buttons_inline) || DIFF (hex_gui_topicbar_multiline))
|
||||||
|
live_changes.topic_bar = TRUE;
|
||||||
|
if (DIFF (hex_gui_tab_icons) || DIFF (hex_gui_tab_closebuttons) ||
|
||||||
|
DIFF (hex_gui_tab_small))
|
||||||
|
live_changes.chanview = TRUE;
|
||||||
if (DIFF (hex_gui_tab_sort))
|
if (DIFF (hex_gui_tab_sort))
|
||||||
noapply = TRUE;
|
live_changes.tab_resort = TRUE;
|
||||||
if (DIFF (hex_gui_tab_trunc))
|
if (DIFF (hex_gui_tab_trunc))
|
||||||
noapply = TRUE;
|
live_changes.tab_trunc = TRUE;
|
||||||
if (DIFF (hex_gui_throttlemeter))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_topicbar_multiline))
|
|
||||||
noapply = TRUE;
|
|
||||||
if (DIFF (hex_gui_ulist_count))
|
if (DIFF (hex_gui_ulist_count))
|
||||||
noapply = TRUE;
|
live_changes.ulist_count = TRUE;
|
||||||
if (DIFF (hex_gui_ulist_icons))
|
if (DIFF (hex_gui_ulist_icons))
|
||||||
noapply = TRUE;
|
live_changes.ulist_rows = TRUE;
|
||||||
if (DIFF (hex_gui_ulist_show_hosts))
|
if (DIFF (hex_gui_ulist_show_hosts))
|
||||||
noapply = TRUE;
|
{
|
||||||
|
live_changes.ulist_columns = TRUE;
|
||||||
|
live_changes.ulist_rows = TRUE; /* hostnames may be stale */
|
||||||
|
}
|
||||||
if (DIFF (hex_gui_ulist_sort))
|
if (DIFF (hex_gui_ulist_sort))
|
||||||
noapply = TRUE;
|
live_changes.ulist_sort = TRUE;
|
||||||
|
/* compare against the pre-dialog value: the slider previews the
|
||||||
|
opacity live on the current window only, so OK still needs to
|
||||||
|
apply it to the other windows */
|
||||||
|
if (pr->hex_gui_transparency != setup_transparency_snapshot)
|
||||||
|
live_changes.transparency = TRUE;
|
||||||
|
|
||||||
if ((pr->hex_gui_tab_pos == 5 || pr->hex_gui_tab_pos == 6) &&
|
if ((pr->hex_gui_tab_pos == 5 || pr->hex_gui_tab_pos == 6) &&
|
||||||
pr->hex_gui_tab_layout == 2 && pr->hex_gui_tab_pos != prefs.hex_gui_tab_pos)
|
pr->hex_gui_tab_layout == 2 && pr->hex_gui_tab_pos != prefs.hex_gui_tab_pos)
|
||||||
@@ -2378,6 +2392,8 @@ setup_apply (struct zoitechatprefs *pr)
|
|||||||
|
|
||||||
theme_manager_dispatch_setup_apply (&event);
|
theme_manager_dispatch_setup_apply (&event);
|
||||||
|
|
||||||
|
mg_apply_live_prefs (&live_changes);
|
||||||
|
|
||||||
if (noapply)
|
if (noapply)
|
||||||
fe_message (_("Some settings were changed that require a"
|
fe_message (_("Some settings were changed that require a"
|
||||||
" restart to take full effect."), FE_MSG_WARN);
|
" restart to take full effect."), FE_MSG_WARN);
|
||||||
@@ -2410,6 +2426,9 @@ setup_ok_cb (GtkWidget *but, GtkWidget *win)
|
|||||||
if (save_result.success)
|
if (save_result.success)
|
||||||
{
|
{
|
||||||
theme_preferences_stage_commit ();
|
theme_preferences_stage_commit ();
|
||||||
|
/* the previewed values were accepted, so the close handler
|
||||||
|
must not revert them */
|
||||||
|
setup_transparency_snapshot = prefs.hex_gui_transparency;
|
||||||
gtk_widget_destroy (win);
|
gtk_widget_destroy (win);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2472,6 +2491,24 @@ setup_close_cb (GtkWidget *win, GtkWidget **swin)
|
|||||||
{
|
{
|
||||||
*swin = NULL;
|
*swin = NULL;
|
||||||
|
|
||||||
|
/* drop any pending transparency preview and undo the previewed
|
||||||
|
opacity when the dialog is cancelled; after OK the snapshot
|
||||||
|
was updated so this is a no-op */
|
||||||
|
if (setup_trans_apply_tag)
|
||||||
|
{
|
||||||
|
g_source_remove (setup_trans_apply_tag);
|
||||||
|
setup_trans_apply_tag = 0;
|
||||||
|
}
|
||||||
|
if (prefs.hex_gui_transparency != setup_transparency_snapshot)
|
||||||
|
{
|
||||||
|
mg_live_prefs revert_changes;
|
||||||
|
|
||||||
|
prefs.hex_gui_transparency = setup_transparency_snapshot;
|
||||||
|
memset (&revert_changes, 0, sizeof (revert_changes));
|
||||||
|
revert_changes.transparency = TRUE;
|
||||||
|
mg_apply_live_prefs (&revert_changes);
|
||||||
|
}
|
||||||
|
|
||||||
theme_preferences_stage_discard ();
|
theme_preferences_stage_discard ();
|
||||||
|
|
||||||
if (font_dialog)
|
if (font_dialog)
|
||||||
@@ -2493,6 +2530,7 @@ setup_open (void)
|
|||||||
memcpy (&setup_prefs, &prefs, sizeof (prefs));
|
memcpy (&setup_prefs, &prefs, sizeof (prefs));
|
||||||
|
|
||||||
color_change = FALSE;
|
color_change = FALSE;
|
||||||
|
setup_transparency_snapshot = prefs.hex_gui_transparency;
|
||||||
theme_preferences_stage_begin ();
|
theme_preferences_stage_begin ();
|
||||||
setup_window = setup_window_open ();
|
setup_window = setup_window_open ();
|
||||||
|
|
||||||
|
|||||||
@@ -106,6 +106,11 @@ typedef struct
|
|||||||
gboolean staged_dirty[THEME_TOKEN_COUNT];
|
gboolean staged_dirty[THEME_TOKEN_COUNT];
|
||||||
GdkRGBA snapshot[THEME_TOKEN_COUNT];
|
GdkRGBA snapshot[THEME_TOKEN_COUNT];
|
||||||
GdkRGBA staged[THEME_TOKEN_COUNT];
|
GdkRGBA staged[THEME_TOKEN_COUNT];
|
||||||
|
/* the GTK3 theme selection is previewed live from the combo, so
|
||||||
|
* remember what was active when the dialog opened in order to
|
||||||
|
* restore it when the dialog is cancelled */
|
||||||
|
char gtk3_theme_snapshot[sizeof ((struct zoitechatprefs *) 0)->hex_gui_gtk3_theme];
|
||||||
|
int gtk3_variant_snapshot;
|
||||||
} theme_preferences_stage_state;
|
} theme_preferences_stage_state;
|
||||||
|
|
||||||
static theme_preferences_stage_state theme_preferences_stage;
|
static theme_preferences_stage_state theme_preferences_stage;
|
||||||
@@ -204,6 +209,11 @@ theme_preferences_stage_begin (void)
|
|||||||
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
||||||
theme_preferences_stage.active = TRUE;
|
theme_preferences_stage.active = TRUE;
|
||||||
|
|
||||||
|
g_strlcpy (theme_preferences_stage.gtk3_theme_snapshot,
|
||||||
|
prefs.hex_gui_gtk3_theme,
|
||||||
|
sizeof (theme_preferences_stage.gtk3_theme_snapshot));
|
||||||
|
theme_preferences_stage.gtk3_variant_snapshot = prefs.hex_gui_gtk3_variant;
|
||||||
|
|
||||||
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
for (token = THEME_TOKEN_MIRC_0; token < THEME_TOKEN_COUNT; token++)
|
||||||
{
|
{
|
||||||
GdkRGBA rgba;
|
GdkRGBA rgba;
|
||||||
@@ -237,12 +247,36 @@ theme_preferences_stage_commit (void)
|
|||||||
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* undo a live GTK3 theme preview: put the selection that was active when
|
||||||
|
* the dialog opened back and re-apply it */
|
||||||
|
static void
|
||||||
|
theme_preferences_stage_restore_gtk3 (void)
|
||||||
|
{
|
||||||
|
if (g_strcmp0 (prefs.hex_gui_gtk3_theme, theme_preferences_stage.gtk3_theme_snapshot) == 0 &&
|
||||||
|
prefs.hex_gui_gtk3_variant == theme_preferences_stage.gtk3_variant_snapshot)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_strlcpy (prefs.hex_gui_gtk3_theme,
|
||||||
|
theme_preferences_stage.gtk3_theme_snapshot,
|
||||||
|
sizeof (prefs.hex_gui_gtk3_theme));
|
||||||
|
prefs.hex_gui_gtk3_variant = theme_preferences_stage.gtk3_variant_snapshot;
|
||||||
|
|
||||||
|
theme_gtk3_apply_current (NULL);
|
||||||
|
theme_manager_dispatch_changed (THEME_CHANGED_REASON_THEME_PACK |
|
||||||
|
THEME_CHANGED_REASON_PALETTE |
|
||||||
|
THEME_CHANGED_REASON_WIDGET_STYLE |
|
||||||
|
THEME_CHANGED_REASON_USERLIST |
|
||||||
|
THEME_CHANGED_REASON_MODE);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
theme_preferences_stage_discard (void)
|
theme_preferences_stage_discard (void)
|
||||||
{
|
{
|
||||||
if (!theme_preferences_stage.active)
|
if (!theme_preferences_stage.active)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
/* restore the GTK3 theme first so the color snapshot below wins */
|
||||||
|
theme_preferences_stage_restore_gtk3 ();
|
||||||
theme_preferences_stage_sync_runtime_to_snapshot ();
|
theme_preferences_stage_sync_runtime_to_snapshot ();
|
||||||
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
memset (&theme_preferences_stage, 0, sizeof (theme_preferences_stage));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -796,16 +796,12 @@ userlist_store_color (GtkListStore *store, GtkTreeIter *iter, ThemeSemanticToken
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GtkListStore *
|
static void
|
||||||
userlist_create_model (session *sess)
|
userlist_model_set_sort (GtkListStore *store, session *sess)
|
||||||
{
|
{
|
||||||
GtkListStore *store;
|
|
||||||
GtkTreeIterCompareFunc cmp_func;
|
GtkTreeIterCompareFunc cmp_func;
|
||||||
GtkSortType sort_type;
|
GtkSortType sort_type;
|
||||||
|
|
||||||
store = gtk_list_store_new (6, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING,
|
|
||||||
G_TYPE_STRING, G_TYPE_POINTER, THEME_GTK_COLOR_TYPE);
|
|
||||||
|
|
||||||
switch (prefs.hex_gui_ulist_sort)
|
switch (prefs.hex_gui_ulist_sort)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
@@ -825,18 +821,77 @@ userlist_create_model (session *sess)
|
|||||||
sort_type = GTK_SORT_DESCENDING;
|
sort_type = GTK_SORT_DESCENDING;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
/* No sorting */
|
/* No sorting; rows keep their current order and new rows
|
||||||
|
go wherever they are inserted */
|
||||||
|
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(store),
|
||||||
|
GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
|
||||||
gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE(store), NULL, NULL, NULL);
|
gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE(store), NULL, NULL, NULL);
|
||||||
return store;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE(store), cmp_func, sess, NULL);
|
gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE(store), cmp_func, sess, NULL);
|
||||||
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(store),
|
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(store),
|
||||||
GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, sort_type);
|
GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, sort_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkListStore *
|
||||||
|
userlist_create_model (session *sess)
|
||||||
|
{
|
||||||
|
GtkListStore *store;
|
||||||
|
|
||||||
|
store = gtk_list_store_new (6, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING,
|
||||||
|
G_TYPE_STRING, G_TYPE_POINTER, THEME_GTK_COLOR_TYPE);
|
||||||
|
userlist_model_set_sort (store, sess);
|
||||||
|
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
userlist_apply_sort (session *sess)
|
||||||
|
{
|
||||||
|
if (!sess->res || !sess->res->user_model)
|
||||||
|
return;
|
||||||
|
|
||||||
|
userlist_model_set_sort (sess->res->user_model, sess);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove and re-insert every row, so per-row content that depends on
|
||||||
|
* preferences (mode icons vs. text prefixes, hostnames) is rebuilt */
|
||||||
|
void
|
||||||
|
userlist_refill (session *sess)
|
||||||
|
{
|
||||||
|
GtkTreeModel *model;
|
||||||
|
GtkTreeIter iter;
|
||||||
|
GSList *users = NULL;
|
||||||
|
GSList *it;
|
||||||
|
struct User *user;
|
||||||
|
|
||||||
|
if (!sess->res || !sess->res->user_model)
|
||||||
|
return;
|
||||||
|
|
||||||
|
model = GTK_TREE_MODEL (sess->res->user_model);
|
||||||
|
if (gtk_tree_model_get_iter_first (model, &iter))
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
gtk_tree_model_get (model, &iter, COL_USER, &user, -1);
|
||||||
|
if (user)
|
||||||
|
users = g_slist_prepend (users, user);
|
||||||
|
}
|
||||||
|
while (gtk_tree_model_iter_next (model, &iter));
|
||||||
|
}
|
||||||
|
|
||||||
|
fe_userlist_clear (sess);
|
||||||
|
|
||||||
|
/* fe_userlist_insert() prepends, so inserting in reverse display
|
||||||
|
order restores the on-screen order for unsorted lists; sorted
|
||||||
|
models position the rows themselves */
|
||||||
|
for (it = users; it; it = it->next)
|
||||||
|
fe_userlist_insert (sess, it->data, FALSE);
|
||||||
|
|
||||||
|
g_slist_free (users);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
userlist_add_columns (GtkTreeView * treeview)
|
userlist_add_columns (GtkTreeView * treeview)
|
||||||
{
|
{
|
||||||
@@ -906,6 +961,23 @@ userlist_add_columns (GtkTreeView * treeview)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
userlist_rebuild_columns (GtkWidget *treeview)
|
||||||
|
{
|
||||||
|
GList *columns;
|
||||||
|
GList *it;
|
||||||
|
|
||||||
|
if (!treeview)
|
||||||
|
return;
|
||||||
|
|
||||||
|
columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (treeview));
|
||||||
|
for (it = columns; it; it = it->next)
|
||||||
|
gtk_tree_view_remove_column (GTK_TREE_VIEW (treeview), it->data);
|
||||||
|
g_list_free (columns);
|
||||||
|
|
||||||
|
userlist_add_columns (GTK_TREE_VIEW (treeview));
|
||||||
|
}
|
||||||
|
|
||||||
static gint
|
static gint
|
||||||
userlist_click_cb (GtkWidget *widget, GdkEventButton *event, gpointer userdata)
|
userlist_click_cb (GtkWidget *widget, GdkEventButton *event, gpointer userdata)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ void userlist_set_value (GtkWidget *treeview, gfloat val);
|
|||||||
gfloat userlist_get_value (GtkWidget *treeview);
|
gfloat userlist_get_value (GtkWidget *treeview);
|
||||||
GtkWidget *userlist_create (GtkWidget *box);
|
GtkWidget *userlist_create (GtkWidget *box);
|
||||||
GtkListStore *userlist_create_model (session *sess);
|
GtkListStore *userlist_create_model (session *sess);
|
||||||
|
void userlist_apply_sort (session *sess);
|
||||||
|
void userlist_refill (session *sess);
|
||||||
|
void userlist_rebuild_columns (GtkWidget *treeview);
|
||||||
void userlist_show (session *sess);
|
void userlist_show (session *sess);
|
||||||
void userlist_select (session *sess, char *name);
|
void userlist_select (session *sess, char *name);
|
||||||
void fe_userlist_set_typing (session *sess, const char *nick, const char *state);
|
void fe_userlist_set_typing (session *sess, const char *nick, const char *state);
|
||||||
|
|||||||
@@ -47,7 +47,6 @@
|
|||||||
#include "fe-gtk.h"
|
#include "fe-gtk.h"
|
||||||
#include "xtext.h"
|
#include "xtext.h"
|
||||||
#include "fkeys.h"
|
#include "fkeys.h"
|
||||||
#include "gtkutil.h"
|
|
||||||
#include "theme/theme-access.h"
|
#include "theme/theme-access.h"
|
||||||
|
|
||||||
#define charlen(str) g_utf8_skip[*(guchar *)(str)]
|
#define charlen(str) g_utf8_skip[*(guchar *)(str)]
|
||||||
@@ -658,93 +657,6 @@ backend_font_open (GtkXText *xtext, char *name)
|
|||||||
pango_font_metrics_unref (metrics);
|
pango_font_metrics_unref (metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
static gboolean
|
|
||||||
xtext_win7_is_emoji_char (gunichar ch)
|
|
||||||
{
|
|
||||||
return (ch >= 0x1f000 && ch <= 0x1faff) ||
|
|
||||||
(ch >= 0x2600 && ch <= 0x27bf) ||
|
|
||||||
(ch >= 0x2300 && ch <= 0x23ff) ||
|
|
||||||
(ch >= 0x2b00 && ch <= 0x2bff) ||
|
|
||||||
(ch >= 0xfe00 && ch <= 0xfe0f) ||
|
|
||||||
ch == 0x200d;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *
|
|
||||||
xtext_win7_emoji_font_family (void)
|
|
||||||
{
|
|
||||||
static char family[FONTNAMELEN + 1];
|
|
||||||
static char source[sizeof prefs.hex_text_font_alternative];
|
|
||||||
char **families;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (win32_is_windows_8_or_newer ())
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
if (strcmp (source, prefs.hex_text_font_alternative) == 0 && family[0])
|
|
||||||
return family;
|
|
||||||
|
|
||||||
family[0] = 0;
|
|
||||||
g_strlcpy (source, prefs.hex_text_font_alternative, sizeof source);
|
|
||||||
families = g_strsplit (prefs.hex_text_font_alternative, ",", -1);
|
|
||||||
for (i = 0; families[i]; i++)
|
|
||||||
{
|
|
||||||
char *name = g_strstrip (families[i]);
|
|
||||||
|
|
||||||
if (*name && gtkutil_find_font (name))
|
|
||||||
{
|
|
||||||
g_strlcpy (family, name, sizeof family);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_strfreev (families);
|
|
||||||
|
|
||||||
if (!family[0] && gtkutil_find_font ("Segoe UI Symbol"))
|
|
||||||
g_strlcpy (family, "Segoe UI Symbol", sizeof family);
|
|
||||||
|
|
||||||
return family[0] ? family : NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PangoAttrList *
|
|
||||||
xtext_win7_emoji_attrs (const char *str, int len, int emphasis)
|
|
||||||
{
|
|
||||||
const char *family;
|
|
||||||
const char *p;
|
|
||||||
const char *end;
|
|
||||||
PangoAttrList *attrs = NULL;
|
|
||||||
|
|
||||||
family = xtext_win7_emoji_font_family ();
|
|
||||||
if (!family)
|
|
||||||
return attr_lists[emphasis];
|
|
||||||
|
|
||||||
p = str;
|
|
||||||
end = str + len;
|
|
||||||
while (p < end)
|
|
||||||
{
|
|
||||||
const char *next = g_utf8_next_char (p);
|
|
||||||
gunichar ch = g_utf8_get_char_validated (p, end - p);
|
|
||||||
|
|
||||||
if (ch != (gunichar)-1 && ch != (gunichar)-2 && xtext_win7_is_emoji_char (ch))
|
|
||||||
{
|
|
||||||
PangoAttribute *attr;
|
|
||||||
|
|
||||||
if (!attrs)
|
|
||||||
attrs = pango_attr_list_copy (attr_lists[emphasis]);
|
|
||||||
|
|
||||||
attr = pango_attr_family_new (family);
|
|
||||||
attr->start_index = p - str;
|
|
||||||
attr->end_index = next - str;
|
|
||||||
pango_attr_list_insert (attrs, attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
p = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
return attrs ? attrs : attr_lists[emphasis];
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
backend_get_text_width_emph (GtkXText *xtext, guchar *str, int len, int emphasis)
|
backend_get_text_width_emph (GtkXText *xtext, guchar *str, int len, int emphasis)
|
||||||
{
|
{
|
||||||
@@ -768,18 +680,8 @@ backend_get_text_width_emph (GtkXText *xtext, guchar *str, int len, int emphasis
|
|||||||
deltaw = fontwidths[emphasis][*str];
|
deltaw = fontwidths[emphasis][*str];
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#ifdef WIN32
|
|
||||||
PangoAttrList *attrs = xtext_win7_emoji_attrs ((const char *)str, mbl, emphasis);
|
|
||||||
|
|
||||||
pango_layout_set_attributes (xtext->layout, attrs);
|
|
||||||
#endif
|
|
||||||
pango_layout_set_text (xtext->layout, str, mbl);
|
pango_layout_set_text (xtext->layout, str, mbl);
|
||||||
pango_layout_get_pixel_size (xtext->layout, &deltaw, NULL);
|
pango_layout_get_pixel_size (xtext->layout, &deltaw, NULL);
|
||||||
#ifdef WIN32
|
|
||||||
if (attrs != attr_lists[emphasis])
|
|
||||||
pango_attr_list_unref (attrs);
|
|
||||||
pango_layout_set_attributes (xtext->layout, attr_lists[emphasis]);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
width += deltaw;
|
width += deltaw;
|
||||||
str += mbl;
|
str += mbl;
|
||||||
@@ -813,18 +715,10 @@ backend_draw_text_emph (GtkXText *xtext, gboolean dofill, int x, int y,
|
|||||||
{
|
{
|
||||||
cairo_t *cr;
|
cairo_t *cr;
|
||||||
PangoLayoutLine *line;
|
PangoLayoutLine *line;
|
||||||
#ifdef WIN32
|
|
||||||
PangoAttrList *attrs;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
cr = xtext_create_context (xtext);
|
cr = xtext_create_context (xtext);
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
attrs = xtext_win7_emoji_attrs (str, len, emphasis);
|
|
||||||
pango_layout_set_attributes (xtext->layout, attrs);
|
|
||||||
#else
|
|
||||||
pango_layout_set_attributes (xtext->layout, attr_lists[emphasis]);
|
pango_layout_set_attributes (xtext->layout, attr_lists[emphasis]);
|
||||||
#endif
|
|
||||||
pango_layout_set_text (xtext->layout, str, len);
|
pango_layout_set_text (xtext->layout, str, len);
|
||||||
|
|
||||||
if (dofill)
|
if (dofill)
|
||||||
@@ -840,10 +734,7 @@ backend_draw_text_emph (GtkXText *xtext, gboolean dofill, int x, int y,
|
|||||||
cairo_move_to (cr, x, y);
|
cairo_move_to (cr, x, y);
|
||||||
pango_cairo_show_layout_line (cr, line);
|
pango_cairo_show_layout_line (cr, line);
|
||||||
cairo_restore (cr);
|
cairo_restore (cr);
|
||||||
#ifdef WIN32
|
|
||||||
if (attrs != attr_lists[emphasis])
|
|
||||||
pango_attr_list_unref (attrs);
|
|
||||||
#endif
|
|
||||||
cairo_destroy (cr);
|
cairo_destroy (cr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,280 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
#
|
|
||||||
# gen-emoji-data.py - generate ZoiteChat's bundled emoji catalog
|
|
||||||
#
|
|
||||||
# Consumes the pinned data files checked in under data/emoji/ and emits a
|
|
||||||
# C table that is compiled into the application, so every ZoiteChat package
|
|
||||||
# ships the same emoji, names, categories and search keywords regardless of
|
|
||||||
# which GTK runtime or /usr/share/gtk-3.0/emoji data a distribution provides.
|
|
||||||
#
|
|
||||||
# Inputs:
|
|
||||||
# emoji-test.txt Unicode emoji-test data file (catalog,
|
|
||||||
# CLDR ordering, groups, short names)
|
|
||||||
# cldr-annotations-en.xml CLDR annotations (search keywords)
|
|
||||||
# cldr-annotations-derived-en.xml CLDR derived annotations (sequences)
|
|
||||||
# gemoji.json gemoji database (:shortcode: aliases, tags)
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# gen-emoji-data.py EMOJI_TEST ANNOTATIONS DERIVED_ANNOTATIONS GEMOJI \
|
|
||||||
# VERSIONS OUTPUT
|
|
||||||
#
|
|
||||||
# Only fully-qualified sequences are emitted, so everything the picker
|
|
||||||
# inserts is a well-formed emoji presentation sequence. Skin-tone variants
|
|
||||||
# are folded into per-base tone sets: the picker shows the neutral emoji and
|
|
||||||
# swaps in the modified sequence for the user's preferred tone. Sequences
|
|
||||||
# that mix two different skin tones are not emitted.
|
|
||||||
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
|
|
||||||
# Must match the EmojiGroup enum in src/common/emoji-data.h. "Component"
|
|
||||||
# entries (bare tone swatches, hair styles) are not meaningful to insert.
|
|
||||||
GROUPS = [
|
|
||||||
"Smileys & Emotion",
|
|
||||||
"People & Body",
|
|
||||||
"Animals & Nature",
|
|
||||||
"Food & Drink",
|
|
||||||
"Travel & Places",
|
|
||||||
"Activities",
|
|
||||||
"Objects",
|
|
||||||
"Symbols",
|
|
||||||
"Flags",
|
|
||||||
]
|
|
||||||
SKIPPED_GROUPS = {"Component"}
|
|
||||||
|
|
||||||
# Order must match the EmojiTone enum (minus EMOJI_TONE_NONE).
|
|
||||||
TONE_DESCRIPTORS = [
|
|
||||||
"light skin tone",
|
|
||||||
"medium-light skin tone",
|
|
||||||
"medium skin tone",
|
|
||||||
"medium-dark skin tone",
|
|
||||||
"dark skin tone",
|
|
||||||
]
|
|
||||||
TONE_MODIFIERS = set(range(0x1F3FB, 0x1F3FF + 1))
|
|
||||||
VS16 = 0xFE0F
|
|
||||||
|
|
||||||
DATA_LINE = re.compile(
|
|
||||||
r"^(?P<cps>[0-9A-F ]+?)\s*;\s*(?P<status>[\w-]+)\s*#\s*(?P<emoji>\S+)"
|
|
||||||
r"\s+E\d+\.\d+\s+(?P<name>.+)$"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_emoji_test(path):
|
|
||||||
"""Yield (group, codepoints, string, name) for fully-qualified entries."""
|
|
||||||
group = None
|
|
||||||
unicode_version = None
|
|
||||||
with open(path, encoding="utf-8") as fp:
|
|
||||||
for line in fp:
|
|
||||||
line = line.rstrip("\n")
|
|
||||||
if line.startswith("# group:"):
|
|
||||||
group = line.split(":", 1)[1].strip()
|
|
||||||
continue
|
|
||||||
if line.startswith("# Version:"):
|
|
||||||
unicode_version = line.split(":", 1)[1].strip()
|
|
||||||
continue
|
|
||||||
if not line or line.startswith("#"):
|
|
||||||
continue
|
|
||||||
m = DATA_LINE.match(line)
|
|
||||||
if not m:
|
|
||||||
continue
|
|
||||||
if m.group("status") != "fully-qualified":
|
|
||||||
continue
|
|
||||||
if group in SKIPPED_GROUPS:
|
|
||||||
continue
|
|
||||||
if group not in GROUPS:
|
|
||||||
sys.exit("gen-emoji-data.py: unknown emoji group %r; "
|
|
||||||
"update GROUPS and the EmojiGroup enum" % group)
|
|
||||||
cps = [int(cp, 16) for cp in m.group("cps").split()]
|
|
||||||
yield group, cps, "".join(map(chr, cps)), m.group("name")
|
|
||||||
if unicode_version is None:
|
|
||||||
sys.exit("gen-emoji-data.py: no '# Version:' header in emoji-test.txt")
|
|
||||||
parse_emoji_test.version = unicode_version
|
|
||||||
|
|
||||||
|
|
||||||
def parse_annotations(path, keywords, names):
|
|
||||||
root = ET.parse(path).getroot()
|
|
||||||
for node in root.iter("annotation"):
|
|
||||||
cp = node.attrib["cp"]
|
|
||||||
if node.attrib.get("type") == "tts":
|
|
||||||
names[cp] = node.text.strip()
|
|
||||||
else:
|
|
||||||
keywords[cp] = [k.strip() for k in node.text.split("|")]
|
|
||||||
|
|
||||||
|
|
||||||
def parse_versions(path):
|
|
||||||
versions = {}
|
|
||||||
with open(path, encoding="utf-8") as fp:
|
|
||||||
for line in fp:
|
|
||||||
line = line.strip()
|
|
||||||
if line and not line.startswith("#") and "=" in line:
|
|
||||||
key, value = line.split("=", 1)
|
|
||||||
versions[key.strip()] = value.strip()
|
|
||||||
for key in ("unicode-emoji", "cldr", "gemoji"):
|
|
||||||
if key not in versions:
|
|
||||||
sys.exit("gen-emoji-data.py: %r missing from %s" % (key, path))
|
|
||||||
return versions
|
|
||||||
|
|
||||||
|
|
||||||
def strip_vs16(text):
|
|
||||||
return text.replace(chr(VS16), "")
|
|
||||||
|
|
||||||
|
|
||||||
def split_name(name):
|
|
||||||
"""Split 'kiss: woman, man, light skin tone' into a base name and the
|
|
||||||
list of tone descriptors it contains."""
|
|
||||||
if ": " not in name:
|
|
||||||
return name, []
|
|
||||||
head, qualifier = name.split(": ", 1)
|
|
||||||
parts = [p.strip() for p in qualifier.split(",")]
|
|
||||||
tones = [p for p in parts if p in TONE_DESCRIPTORS]
|
|
||||||
rest = [p for p in parts if p not in TONE_DESCRIPTORS]
|
|
||||||
base = head if not rest else head + ": " + ", ".join(rest)
|
|
||||||
return base, tones
|
|
||||||
|
|
||||||
|
|
||||||
def c_escape(text):
|
|
||||||
"""Escape text as the body of a C string literal. Non-ASCII bytes use
|
|
||||||
three-digit octal escapes, which cannot swallow a following character
|
|
||||||
the way hex escapes can."""
|
|
||||||
out = []
|
|
||||||
for byte in text.encode("utf-8"):
|
|
||||||
if byte in (0x22, 0x5C): # '"' and '\\'
|
|
||||||
out.append("\\" + chr(byte))
|
|
||||||
elif 0x20 <= byte < 0x7F:
|
|
||||||
out.append(chr(byte))
|
|
||||||
else:
|
|
||||||
out.append("\\%03o" % byte)
|
|
||||||
return "".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def build_search(name, keyword_list, aliases, tags):
|
|
||||||
tokens = []
|
|
||||||
for source in ([name] + keyword_list + tags):
|
|
||||||
tokens.extend(source.casefold().split())
|
|
||||||
tokens.extend(alias.casefold() for alias in aliases)
|
|
||||||
seen = {}
|
|
||||||
for token in tokens:
|
|
||||||
token = token.strip('",')
|
|
||||||
if token and token not in seen:
|
|
||||||
seen[token] = True
|
|
||||||
return " ".join(seen)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) != 7:
|
|
||||||
sys.exit("usage: gen-emoji-data.py EMOJI_TEST ANNOTATIONS "
|
|
||||||
"DERIVED_ANNOTATIONS GEMOJI VERSIONS OUTPUT")
|
|
||||||
(emoji_test, ann_path, derived_path, gemoji_path,
|
|
||||||
versions_path, output) = sys.argv[1:]
|
|
||||||
|
|
||||||
versions = parse_versions(versions_path)
|
|
||||||
cldr_version = versions["cldr"]
|
|
||||||
|
|
||||||
keywords = {}
|
|
||||||
cldr_names = {}
|
|
||||||
parse_annotations(ann_path, keywords, cldr_names)
|
|
||||||
parse_annotations(derived_path, keywords, cldr_names)
|
|
||||||
|
|
||||||
aliases = {}
|
|
||||||
tags = {}
|
|
||||||
with open(gemoji_path, encoding="utf-8") as fp:
|
|
||||||
for item in json.load(fp):
|
|
||||||
if "emoji" not in item:
|
|
||||||
continue
|
|
||||||
key = strip_vs16(item["emoji"])
|
|
||||||
aliases[key] = item.get("aliases", [])
|
|
||||||
tags[key] = item.get("tags", [])
|
|
||||||
|
|
||||||
entries = [] # base emoji, in CLDR order
|
|
||||||
by_name = {} # base name -> index into entries
|
|
||||||
tone_sets = [] # list of [seq or None] * 5
|
|
||||||
skipped_mixed = 0
|
|
||||||
|
|
||||||
for group, cps, string, name in parse_emoji_test(emoji_test):
|
|
||||||
tones_in_seq = {cp for cp in cps if cp in TONE_MODIFIERS}
|
|
||||||
base_name, tone_descs = split_name(name)
|
|
||||||
|
|
||||||
if not tones_in_seq:
|
|
||||||
key = strip_vs16(string)
|
|
||||||
entry = {
|
|
||||||
"sequence": string,
|
|
||||||
"name": name,
|
|
||||||
"keywords": keywords.get(key, keywords.get(string, [])),
|
|
||||||
"aliases": aliases.get(key, []),
|
|
||||||
"tags": tags.get(key, []),
|
|
||||||
"group": GROUPS.index(group),
|
|
||||||
"tone_set": -1,
|
|
||||||
}
|
|
||||||
by_name[name] = len(entries)
|
|
||||||
entries.append(entry)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Tone-modified sequence: attach to its base if it uses one uniform
|
|
||||||
# tone; sequences mixing two tones are intentionally not bundled.
|
|
||||||
if len(tones_in_seq) != 1 or len(tone_descs) != 1:
|
|
||||||
skipped_mixed += 1
|
|
||||||
continue
|
|
||||||
if base_name not in by_name:
|
|
||||||
sys.exit("gen-emoji-data.py: no base emoji %r for %r"
|
|
||||||
% (base_name, name))
|
|
||||||
entry = entries[by_name[base_name]]
|
|
||||||
if entry["tone_set"] == -1:
|
|
||||||
entry["tone_set"] = len(tone_sets)
|
|
||||||
tone_sets.append([None] * len(TONE_DESCRIPTORS))
|
|
||||||
tone_sets[entry["tone_set"]][TONE_DESCRIPTORS.index(tone_descs[0])] = string
|
|
||||||
|
|
||||||
for i, tone_set in enumerate(tone_sets):
|
|
||||||
if None in tone_set:
|
|
||||||
sys.exit("gen-emoji-data.py: incomplete tone set %d" % i)
|
|
||||||
|
|
||||||
unicode_version = parse_emoji_test.version
|
|
||||||
if unicode_version != versions["unicode-emoji"]:
|
|
||||||
sys.exit("gen-emoji-data.py: emoji-test.txt is Emoji %s but VERSIONS "
|
|
||||||
"pins %s" % (unicode_version, versions["unicode-emoji"]))
|
|
||||||
|
|
||||||
with open(output, "w", encoding="utf-8", newline="\n") as out:
|
|
||||||
out.write("/*\n"
|
|
||||||
" * Generated by tools/gen-emoji-data.py -- do not edit.\n"
|
|
||||||
" *\n"
|
|
||||||
" * Sources (pinned copies in data/emoji/):\n"
|
|
||||||
" * Unicode emoji-test.txt, Emoji %s\n"
|
|
||||||
" * CLDR annotations (en), release %s\n"
|
|
||||||
" * gemoji %s shortcode aliases\n"
|
|
||||||
" */\n\n" % (unicode_version, cldr_version,
|
|
||||||
versions["gemoji"]))
|
|
||||||
|
|
||||||
out.write("static const EmojiToneSet emoji_tone_sets_table[] =\n{\n")
|
|
||||||
for tone_set in tone_sets:
|
|
||||||
out.write("\t{ { %s } },\n"
|
|
||||||
% ", ".join('"%s"' % c_escape(seq) for seq in tone_set))
|
|
||||||
out.write("};\n\n")
|
|
||||||
|
|
||||||
out.write("static const EmojiEntry emoji_entries_table[] =\n{\n")
|
|
||||||
for entry in entries:
|
|
||||||
search = build_search(entry["name"], entry["keywords"],
|
|
||||||
entry["aliases"], entry["tags"])
|
|
||||||
out.write('\t{ "%s", "%s", "%s", "%s", %d, %d },\n' % (
|
|
||||||
c_escape(entry["sequence"]),
|
|
||||||
c_escape(entry["name"]),
|
|
||||||
c_escape(search),
|
|
||||||
c_escape(" ".join(entry["aliases"])),
|
|
||||||
entry["group"],
|
|
||||||
entry["tone_set"],
|
|
||||||
))
|
|
||||||
out.write("};\n\n")
|
|
||||||
|
|
||||||
out.write('static const char emoji_data_unicode_version_str[] = "%s";\n'
|
|
||||||
% c_escape(unicode_version))
|
|
||||||
out.write('static const char emoji_data_cldr_version_str[] = "%s";\n'
|
|
||||||
% c_escape(cldr_version))
|
|
||||||
|
|
||||||
sys.stderr.write("gen-emoji-data.py: %d emoji, %d tone sets, "
|
|
||||||
"%d mixed-tone sequences skipped (Emoji %s, CLDR %s)\n"
|
|
||||||
% (len(entries), len(tone_sets), skipped_mixed,
|
|
||||||
unicode_version, cldr_version))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -82,8 +82,6 @@
|
|||||||
<GdkPixbufLoaderCache Include="$(DepsRoot)\lib\gdk-pixbuf-2.0\**\loaders.cache" />
|
<GdkPixbufLoaderCache Include="$(DepsRoot)\lib\gdk-pixbuf-2.0\**\loaders.cache" />
|
||||||
|
|
||||||
<FontConfig Include="$(DepsRoot)\etc\fonts\*" />
|
<FontConfig Include="$(DepsRoot)\etc\fonts\*" />
|
||||||
<EmojiFont Include="..\..\data\fonts\NotoColorEmoji.ttf" />
|
|
||||||
<EmojiFontLicense Include="..\..\data\fonts\OFL.txt" />
|
|
||||||
<Share Include="share\**\*" />
|
<Share Include="share\**\*" />
|
||||||
<Locale Include="$(ZoiteChatBin)locale\**\*;$(DepsRoot)\share\locale\**\*" />
|
<Locale Include="$(ZoiteChatBin)locale\**\*;$(DepsRoot)\share\locale\**\*" />
|
||||||
<MSWindowsTheme Include="$(DepsRoot)\share\themes\MS-Windows\**\*" />
|
<MSWindowsTheme Include="$(DepsRoot)\share\themes\MS-Windows\**\*" />
|
||||||
@@ -93,8 +91,6 @@
|
|||||||
|
|
||||||
<Copy SourceFiles="@(None)" DestinationFolder="$(ZoiteChatRel)" />
|
<Copy SourceFiles="@(None)" DestinationFolder="$(ZoiteChatRel)" />
|
||||||
<Copy SourceFiles="@(FontConfig)" DestinationFolder="$(ZoiteChatRel)\etc\fonts" />
|
<Copy SourceFiles="@(FontConfig)" DestinationFolder="$(ZoiteChatRel)\etc\fonts" />
|
||||||
<Copy SourceFiles="@(EmojiFont)" DestinationFolder="$(ZoiteChatRel)\share\fonts\zoitechat" />
|
|
||||||
<Copy SourceFiles="@(EmojiFontLicense)" DestinationFolder="$(ZoiteChatRel)\share\doc\zoitechat\fonts" />
|
|
||||||
<Copy SourceFiles="@(Gtk3Immodules)" DestinationFiles="@(Gtk3Immodules->'$(ZoiteChatRel)\lib\gtk-3.0\3.0.0\immodules\%(RecursiveDir)%(Filename)%(Extension)')" />
|
<Copy SourceFiles="@(Gtk3Immodules)" DestinationFiles="@(Gtk3Immodules->'$(ZoiteChatRel)\lib\gtk-3.0\3.0.0\immodules\%(RecursiveDir)%(Filename)%(Extension)')" />
|
||||||
<Copy SourceFiles="@(Gtk3PrintBackends)" DestinationFiles="@(Gtk3PrintBackends->'$(ZoiteChatRel)\lib\gtk-3.0\3.0.0\printbackends\%(RecursiveDir)%(Filename)%(Extension)')" />
|
<Copy SourceFiles="@(Gtk3PrintBackends)" DestinationFiles="@(Gtk3PrintBackends->'$(ZoiteChatRel)\lib\gtk-3.0\3.0.0\printbackends\%(RecursiveDir)%(Filename)%(Extension)')" />
|
||||||
<Copy SourceFiles="@(GdkPixbufLoaders)" DestinationFiles="@(GdkPixbufLoaders->'$(ZoiteChatRel)\lib\gdk-pixbuf-2.0\%(RecursiveDir)%(Filename)%(Extension)')" />
|
<Copy SourceFiles="@(GdkPixbufLoaders)" DestinationFiles="@(GdkPixbufLoaders->'$(ZoiteChatRel)\lib\gdk-pixbuf-2.0\%(RecursiveDir)%(Filename)%(Extension)')" />
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ Source: "readme.url"; DestDir: "{app}"; Flags: ignoreversion; Components: libs
|
|||||||
Source: "cert.pem"; DestDir: "{app}"; Flags: ignoreversion; Components: libs
|
Source: "cert.pem"; DestDir: "{app}"; Flags: ignoreversion; Components: libs
|
||||||
Source: "share\xml\*"; DestDir: "{app}\share\xml"; Flags: ignoreversion createallsubdirs recursesubdirs; Components: libs
|
Source: "share\xml\*"; DestDir: "{app}\share\xml"; Flags: ignoreversion createallsubdirs recursesubdirs; Components: libs
|
||||||
Source: "share\doc\zoitechat\*"; DestDir: "{app}\share\doc\zoitechat"; Flags: ignoreversion createallsubdirs recursesubdirs; Components: libs
|
Source: "share\doc\zoitechat\*"; DestDir: "{app}\share\doc\zoitechat"; Flags: ignoreversion createallsubdirs recursesubdirs; Components: libs
|
||||||
Source: "share\fonts\zoitechat\*"; DestDir: "{app}\share\fonts\zoitechat"; Flags: ignoreversion createallsubdirs recursesubdirs; Components: libs
|
|
||||||
Source: "share\doc\WinSparkle\*"; DestDir: "{app}\share\doc\WinSparkle"; Flags: ignoreversion createallsubdirs recursesubdirs; Components: libs
|
Source: "share\doc\WinSparkle\*"; DestDir: "{app}\share\doc\WinSparkle"; Flags: ignoreversion createallsubdirs recursesubdirs; Components: libs
|
||||||
Source: "share\themes\MS-Windows\*"; DestDir: "{app}\share\themes\MS-Windows"; Flags: ignoreversion createallsubdirs recursesubdirs skipifsourcedoesntexist; Components: libs
|
Source: "share\themes\MS-Windows\*"; DestDir: "{app}\share\themes\MS-Windows"; Flags: ignoreversion createallsubdirs recursesubdirs skipifsourcedoesntexist; Components: libs
|
||||||
Source: "share\glib-2.0\schemas\*"; DestDir: "{app}\share\glib-2.0\schemas"; Flags: ignoreversion createallsubdirs recursesubdirs skipifsourcedoesntexist; Components: libs
|
Source: "share\glib-2.0\schemas\*"; DestDir: "{app}\share\glib-2.0\schemas"; Flags: ignoreversion createallsubdirs recursesubdirs skipifsourcedoesntexist; Components: libs
|
||||||
|
|||||||
Reference in New Issue
Block a user