diff --git a/emoji-test.txt b/emoji-test.txt deleted file mode 100644 index 533739f..0000000 --- a/emoji-test.txt +++ /dev/null @@ -1,3033 +0,0 @@ -# emoji-test.txt -# Date: 2016-11-16, 18:29:53 GMT -# © 2016 Unicode®, Inc. -# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. -# For terms of use, see http://www.unicode.org/terms_of_use.html -# -# Emoji Keyboard/Display Test Data for UTR #51 -# Version: 4.0 -# -# For documentation and usage, see http://www.unicode.org/reports/tr51 -# -# This file provides data for testing which emoji forms should be in keyboards and which should also be displayed/processed. -# Format -# Code points; status # emoji name -# Status -# keyboard — see “Emoji Implementation Notes” in UTR#51 -# process — see “Emoji Implementation Notes” in UTR#51 -# Notes: -# • This currently omits the 12 keycap bases, the 5 modifier characters, and 26 singleton Regional Indicator characters -# • The file is in CLDR order, not codepoint order. This is recommended (but not required!) for keyboard palettes. -# • The groups and subgroups are purely illustrative. See the Emoji Order chart for more information. - -# group: Smileys & People - -# subgroup: face-positive -1F600 ; fully-qualified # 😀 grinning face -1F601 ; fully-qualified # 😁 grinning face with smiling eyes -1F602 ; fully-qualified # 😂 face with tears of joy -1F923 ; fully-qualified # 🤣 rolling on the floor laughing -1F603 ; fully-qualified # 😃 smiling face with open mouth -1F604 ; fully-qualified # 😄 smiling face with open mouth & smiling eyes -1F605 ; fully-qualified # 😅 smiling face with open mouth & cold sweat -1F606 ; fully-qualified # 😆 smiling face with open mouth & closed eyes -1F609 ; fully-qualified # 😉 winking face -1F60A ; fully-qualified # 😊 smiling face with smiling eyes -1F60B ; fully-qualified # 😋 face savouring delicious food -1F60E ; fully-qualified # 😎 smiling face with sunglasses -1F60D ; fully-qualified # 😍 smiling face with heart-eyes -1F618 ; fully-qualified # 😘 face blowing a kiss -1F617 ; fully-qualified # 😗 kissing face -1F619 ; fully-qualified # 😙 kissing face with smiling eyes -1F61A ; fully-qualified # 😚 kissing face with closed eyes -263A FE0F ; fully-qualified # ☺️ smiling face -1F642 ; fully-qualified # 🙂 slightly smiling face -1F917 ; fully-qualified # 🤗 hugging face - -# subgroup: face-neutral -1F914 ; fully-qualified # 🤔 thinking face -1F610 ; fully-qualified # 😐 neutral face -1F611 ; fully-qualified # 😑 expressionless face -1F636 ; fully-qualified # 😶 face without mouth -1F644 ; fully-qualified # 🙄 face with rolling eyes -1F60F ; fully-qualified # 😏 smirking face -1F623 ; fully-qualified # 😣 persevering face -1F625 ; fully-qualified # 😥 disappointed but relieved face -1F62E ; fully-qualified # 😮 face with open mouth -1F910 ; fully-qualified # 🤐 zipper-mouth face -1F62F ; fully-qualified # 😯 hushed face -1F62A ; fully-qualified # 😪 sleepy face -1F62B ; fully-qualified # 😫 tired face -1F634 ; fully-qualified # 😴 sleeping face -1F60C ; fully-qualified # 😌 relieved face -1F913 ; fully-qualified # 🤓 nerd face -1F61B ; fully-qualified # 😛 face with stuck-out tongue -1F61C ; fully-qualified # 😜 face with stuck-out tongue & winking eye -1F61D ; fully-qualified # 😝 face with stuck-out tongue & closed eyes -1F924 ; fully-qualified # 🤤 drooling face -1F612 ; fully-qualified # 😒 unamused face -1F613 ; fully-qualified # 😓 face with cold sweat -1F614 ; fully-qualified # 😔 pensive face -1F615 ; fully-qualified # 😕 confused face -1F643 ; fully-qualified # 🙃 upside-down face -1F911 ; fully-qualified # 🤑 money-mouth face -1F632 ; fully-qualified # 😲 astonished face - -# subgroup: face-negative -2639 FE0F ; fully-qualified # ☹️ frowning face -1F641 ; fully-qualified # 🙁 slightly frowning face -1F616 ; fully-qualified # 😖 confounded face -1F61E ; fully-qualified # 😞 disappointed face -1F61F ; fully-qualified # 😟 worried face -1F624 ; fully-qualified # 😤 face with steam from nose -1F622 ; fully-qualified # 😢 crying face -1F62D ; fully-qualified # 😭 loudly crying face -1F626 ; fully-qualified # 😦 frowning face with open mouth -1F627 ; fully-qualified # 😧 anguished face -1F628 ; fully-qualified # 😨 fearful face -1F629 ; fully-qualified # 😩 weary face -1F62C ; fully-qualified # 😬 grimacing face -1F630 ; fully-qualified # 😰 face with open mouth & cold sweat -1F631 ; fully-qualified # 😱 face screaming in fear -1F633 ; fully-qualified # 😳 flushed face -1F635 ; fully-qualified # 😵 dizzy face -1F621 ; fully-qualified # 😡 pouting face -1F620 ; fully-qualified # 😠 angry face - -# subgroup: face-role -1F607 ; fully-qualified # 😇 smiling face with halo -1F920 ; fully-qualified # 🤠 cowboy hat face -1F921 ; fully-qualified # 🤡 clown face -1F925 ; fully-qualified # 🤥 lying face - -# subgroup: face-sick -1F637 ; fully-qualified # 😷 face with medical mask -1F912 ; fully-qualified # 🤒 face with thermometer -1F915 ; fully-qualified # 🤕 face with head-bandage -1F922 ; fully-qualified # 🤢 nauseated face -1F927 ; fully-qualified # 🤧 sneezing face - -# subgroup: creature-face -1F608 ; fully-qualified # 😈 smiling face with horns -1F47F ; fully-qualified # 👿 angry face with horns -1F479 ; fully-qualified # 👹 ogre -1F47A ; fully-qualified # 👺 goblin -1F480 ; fully-qualified # 💀 skull -2620 FE0F ; fully-qualified # ☠️ skull and crossbones -1F47B ; fully-qualified # 👻 ghost -1F47D ; fully-qualified # 👽 alien -1F47E ; fully-qualified # 👾 alien monster -1F916 ; fully-qualified # 🤖 robot face -1F4A9 ; fully-qualified # 💩 pile of poo - -# subgroup: cat-face -1F63A ; fully-qualified # 😺 smiling cat face with open mouth -1F638 ; fully-qualified # 😸 grinning cat face with smiling eyes -1F639 ; fully-qualified # 😹 cat face with tears of joy -1F63B ; fully-qualified # 😻 smiling cat face with heart-eyes -1F63C ; fully-qualified # 😼 cat face with wry smile -1F63D ; fully-qualified # 😽 kissing cat face with closed eyes -1F640 ; fully-qualified # 🙀 weary cat face -1F63F ; fully-qualified # 😿 crying cat face -1F63E ; fully-qualified # 😾 pouting cat face - -# subgroup: monkey-face -1F648 ; fully-qualified # 🙈 see-no-evil monkey -1F649 ; fully-qualified # 🙉 hear-no-evil monkey -1F64A ; fully-qualified # 🙊 speak-no-evil monkey - -# subgroup: person -1F466 ; fully-qualified # 👦 boy -1F466 1F3FB ; fully-qualified # 👦🏻 boy: light skin tone -1F466 1F3FC ; fully-qualified # 👦🏼 boy: medium-light skin tone -1F466 1F3FD ; fully-qualified # 👦🏽 boy: medium skin tone -1F466 1F3FE ; fully-qualified # 👦🏾 boy: medium-dark skin tone -1F466 1F3FF ; fully-qualified # 👦🏿 boy: dark skin tone -1F467 ; fully-qualified # 👧 girl -1F467 1F3FB ; fully-qualified # 👧🏻 girl: light skin tone -1F467 1F3FC ; fully-qualified # 👧🏼 girl: medium-light skin tone -1F467 1F3FD ; fully-qualified # 👧🏽 girl: medium skin tone -1F467 1F3FE ; fully-qualified # 👧🏾 girl: medium-dark skin tone -1F467 1F3FF ; fully-qualified # 👧🏿 girl: dark skin tone -1F468 ; fully-qualified # 👨 man -1F468 1F3FB ; fully-qualified # 👨🏻 man: light skin tone -1F468 1F3FC ; fully-qualified # 👨🏼 man: medium-light skin tone -1F468 1F3FD ; fully-qualified # 👨🏽 man: medium skin tone -1F468 1F3FE ; fully-qualified # 👨🏾 man: medium-dark skin tone -1F468 1F3FF ; fully-qualified # 👨🏿 man: dark skin tone -1F469 ; fully-qualified # 👩 woman -1F469 1F3FB ; fully-qualified # 👩🏻 woman: light skin tone -1F469 1F3FC ; fully-qualified # 👩🏼 woman: medium-light skin tone -1F469 1F3FD ; fully-qualified # 👩🏽 woman: medium skin tone -1F469 1F3FE ; fully-qualified # 👩🏾 woman: medium-dark skin tone -1F469 1F3FF ; fully-qualified # 👩🏿 woman: dark skin tone -1F474 ; fully-qualified # 👴 old man -1F474 1F3FB ; fully-qualified # 👴🏻 old man: light skin tone -1F474 1F3FC ; fully-qualified # 👴🏼 old man: medium-light skin tone -1F474 1F3FD ; fully-qualified # 👴🏽 old man: medium skin tone -1F474 1F3FE ; fully-qualified # 👴🏾 old man: medium-dark skin tone -1F474 1F3FF ; fully-qualified # 👴🏿 old man: dark skin tone -1F475 ; fully-qualified # 👵 old woman -1F475 1F3FB ; fully-qualified # 👵🏻 old woman: light skin tone -1F475 1F3FC ; fully-qualified # 👵🏼 old woman: medium-light skin tone -1F475 1F3FD ; fully-qualified # 👵🏽 old woman: medium skin tone -1F475 1F3FE ; fully-qualified # 👵🏾 old woman: medium-dark skin tone -1F475 1F3FF ; fully-qualified # 👵🏿 old woman: dark skin tone -1F476 ; fully-qualified # 👶 baby -1F476 1F3FB ; fully-qualified # 👶🏻 baby: light skin tone -1F476 1F3FC ; fully-qualified # 👶🏼 baby: medium-light skin tone -1F476 1F3FD ; fully-qualified # 👶🏽 baby: medium skin tone -1F476 1F3FE ; fully-qualified # 👶🏾 baby: medium-dark skin tone -1F476 1F3FF ; fully-qualified # 👶🏿 baby: dark skin tone -1F47C ; fully-qualified # 👼 baby angel -1F47C 1F3FB ; fully-qualified # 👼🏻 baby angel: light skin tone -1F47C 1F3FC ; fully-qualified # 👼🏼 baby angel: medium-light skin tone -1F47C 1F3FD ; fully-qualified # 👼🏽 baby angel: medium skin tone -1F47C 1F3FE ; fully-qualified # 👼🏾 baby angel: medium-dark skin tone -1F47C 1F3FF ; fully-qualified # 👼🏿 baby angel: dark skin tone - -# subgroup: person-role -1F468 200D 2695 FE0F ; fully-qualified # 👨‍⚕️ man health worker -1F468 200D 2695 ; non-fully-qualified # 👨‍⚕ man health worker -1F468 1F3FB 200D 2695 FE0F ; fully-qualified # 👨🏻‍⚕️ man health worker: light skin tone -1F468 1F3FB 200D 2695 ; non-fully-qualified # 👨🏻‍⚕ man health worker: light skin tone -1F468 1F3FC 200D 2695 FE0F ; fully-qualified # 👨🏼‍⚕️ man health worker: medium-light skin tone -1F468 1F3FC 200D 2695 ; non-fully-qualified # 👨🏼‍⚕ man health worker: medium-light skin tone -1F468 1F3FD 200D 2695 FE0F ; fully-qualified # 👨🏽‍⚕️ man health worker: medium skin tone -1F468 1F3FD 200D 2695 ; non-fully-qualified # 👨🏽‍⚕ man health worker: medium skin tone -1F468 1F3FE 200D 2695 FE0F ; fully-qualified # 👨🏾‍⚕️ man health worker: medium-dark skin tone -1F468 1F3FE 200D 2695 ; non-fully-qualified # 👨🏾‍⚕ man health worker: medium-dark skin tone -1F468 1F3FF 200D 2695 FE0F ; fully-qualified # 👨🏿‍⚕️ man health worker: dark skin tone -1F468 1F3FF 200D 2695 ; non-fully-qualified # 👨🏿‍⚕ man health worker: dark skin tone -1F469 200D 2695 FE0F ; fully-qualified # 👩‍⚕️ woman health worker -1F469 200D 2695 ; non-fully-qualified # 👩‍⚕ woman health worker -1F469 1F3FB 200D 2695 FE0F ; fully-qualified # 👩🏻‍⚕️ woman health worker: light skin tone -1F469 1F3FB 200D 2695 ; non-fully-qualified # 👩🏻‍⚕ woman health worker: light skin tone -1F469 1F3FC 200D 2695 FE0F ; fully-qualified # 👩🏼‍⚕️ woman health worker: medium-light skin tone -1F469 1F3FC 200D 2695 ; non-fully-qualified # 👩🏼‍⚕ woman health worker: medium-light skin tone -1F469 1F3FD 200D 2695 FE0F ; fully-qualified # 👩🏽‍⚕️ woman health worker: medium skin tone -1F469 1F3FD 200D 2695 ; non-fully-qualified # 👩🏽‍⚕ woman health worker: medium skin tone -1F469 1F3FE 200D 2695 FE0F ; fully-qualified # 👩🏾‍⚕️ woman health worker: medium-dark skin tone -1F469 1F3FE 200D 2695 ; non-fully-qualified # 👩🏾‍⚕ woman health worker: medium-dark skin tone -1F469 1F3FF 200D 2695 FE0F ; fully-qualified # 👩🏿‍⚕️ woman health worker: dark skin tone -1F469 1F3FF 200D 2695 ; non-fully-qualified # 👩🏿‍⚕ woman health worker: dark skin tone -1F468 200D 1F393 ; fully-qualified # 👨‍🎓 man student -1F468 1F3FB 200D 1F393 ; fully-qualified # 👨🏻‍🎓 man student: light skin tone -1F468 1F3FC 200D 1F393 ; fully-qualified # 👨🏼‍🎓 man student: medium-light skin tone -1F468 1F3FD 200D 1F393 ; fully-qualified # 👨🏽‍🎓 man student: medium skin tone -1F468 1F3FE 200D 1F393 ; fully-qualified # 👨🏾‍🎓 man student: medium-dark skin tone -1F468 1F3FF 200D 1F393 ; fully-qualified # 👨🏿‍🎓 man student: dark skin tone -1F469 200D 1F393 ; fully-qualified # 👩‍🎓 woman student -1F469 1F3FB 200D 1F393 ; fully-qualified # 👩🏻‍🎓 woman student: light skin tone -1F469 1F3FC 200D 1F393 ; fully-qualified # 👩🏼‍🎓 woman student: medium-light skin tone -1F469 1F3FD 200D 1F393 ; fully-qualified # 👩🏽‍🎓 woman student: medium skin tone -1F469 1F3FE 200D 1F393 ; fully-qualified # 👩🏾‍🎓 woman student: medium-dark skin tone -1F469 1F3FF 200D 1F393 ; fully-qualified # 👩🏿‍🎓 woman student: dark skin tone -1F468 200D 1F3EB ; fully-qualified # 👨‍🏫 man teacher -1F468 1F3FB 200D 1F3EB ; fully-qualified # 👨🏻‍🏫 man teacher: light skin tone -1F468 1F3FC 200D 1F3EB ; fully-qualified # 👨🏼‍🏫 man teacher: medium-light skin tone -1F468 1F3FD 200D 1F3EB ; fully-qualified # 👨🏽‍🏫 man teacher: medium skin tone -1F468 1F3FE 200D 1F3EB ; fully-qualified # 👨🏾‍🏫 man teacher: medium-dark skin tone -1F468 1F3FF 200D 1F3EB ; fully-qualified # 👨🏿‍🏫 man teacher: dark skin tone -1F469 200D 1F3EB ; fully-qualified # 👩‍🏫 woman teacher -1F469 1F3FB 200D 1F3EB ; fully-qualified # 👩🏻‍🏫 woman teacher: light skin tone -1F469 1F3FC 200D 1F3EB ; fully-qualified # 👩🏼‍🏫 woman teacher: medium-light skin tone -1F469 1F3FD 200D 1F3EB ; fully-qualified # 👩🏽‍🏫 woman teacher: medium skin tone -1F469 1F3FE 200D 1F3EB ; fully-qualified # 👩🏾‍🏫 woman teacher: medium-dark skin tone -1F469 1F3FF 200D 1F3EB ; fully-qualified # 👩🏿‍🏫 woman teacher: dark skin tone -1F468 200D 2696 FE0F ; fully-qualified # 👨‍⚖️ man judge -1F468 200D 2696 ; non-fully-qualified # 👨‍⚖ man judge -1F468 1F3FB 200D 2696 FE0F ; fully-qualified # 👨🏻‍⚖️ man judge: light skin tone -1F468 1F3FB 200D 2696 ; non-fully-qualified # 👨🏻‍⚖ man judge: light skin tone -1F468 1F3FC 200D 2696 FE0F ; fully-qualified # 👨🏼‍⚖️ man judge: medium-light skin tone -1F468 1F3FC 200D 2696 ; non-fully-qualified # 👨🏼‍⚖ man judge: medium-light skin tone -1F468 1F3FD 200D 2696 FE0F ; fully-qualified # 👨🏽‍⚖️ man judge: medium skin tone -1F468 1F3FD 200D 2696 ; non-fully-qualified # 👨🏽‍⚖ man judge: medium skin tone -1F468 1F3FE 200D 2696 FE0F ; fully-qualified # 👨🏾‍⚖️ man judge: medium-dark skin tone -1F468 1F3FE 200D 2696 ; non-fully-qualified # 👨🏾‍⚖ man judge: medium-dark skin tone -1F468 1F3FF 200D 2696 FE0F ; fully-qualified # 👨🏿‍⚖️ man judge: dark skin tone -1F468 1F3FF 200D 2696 ; non-fully-qualified # 👨🏿‍⚖ man judge: dark skin tone -1F469 200D 2696 FE0F ; fully-qualified # 👩‍⚖️ woman judge -1F469 200D 2696 ; non-fully-qualified # 👩‍⚖ woman judge -1F469 1F3FB 200D 2696 FE0F ; fully-qualified # 👩🏻‍⚖️ woman judge: light skin tone -1F469 1F3FB 200D 2696 ; non-fully-qualified # 👩🏻‍⚖ woman judge: light skin tone -1F469 1F3FC 200D 2696 FE0F ; fully-qualified # 👩🏼‍⚖️ woman judge: medium-light skin tone -1F469 1F3FC 200D 2696 ; non-fully-qualified # 👩🏼‍⚖ woman judge: medium-light skin tone -1F469 1F3FD 200D 2696 FE0F ; fully-qualified # 👩🏽‍⚖️ woman judge: medium skin tone -1F469 1F3FD 200D 2696 ; non-fully-qualified # 👩🏽‍⚖ woman judge: medium skin tone -1F469 1F3FE 200D 2696 FE0F ; fully-qualified # 👩🏾‍⚖️ woman judge: medium-dark skin tone -1F469 1F3FE 200D 2696 ; non-fully-qualified # 👩🏾‍⚖ woman judge: medium-dark skin tone -1F469 1F3FF 200D 2696 FE0F ; fully-qualified # 👩🏿‍⚖️ woman judge: dark skin tone -1F469 1F3FF 200D 2696 ; non-fully-qualified # 👩🏿‍⚖ woman judge: dark skin tone -1F468 200D 1F33E ; fully-qualified # 👨‍🌾 man farmer -1F468 1F3FB 200D 1F33E ; fully-qualified # 👨🏻‍🌾 man farmer: light skin tone -1F468 1F3FC 200D 1F33E ; fully-qualified # 👨🏼‍🌾 man farmer: medium-light skin tone -1F468 1F3FD 200D 1F33E ; fully-qualified # 👨🏽‍🌾 man farmer: medium skin tone -1F468 1F3FE 200D 1F33E ; fully-qualified # 👨🏾‍🌾 man farmer: medium-dark skin tone -1F468 1F3FF 200D 1F33E ; fully-qualified # 👨🏿‍🌾 man farmer: dark skin tone -1F469 200D 1F33E ; fully-qualified # 👩‍🌾 woman farmer -1F469 1F3FB 200D 1F33E ; fully-qualified # 👩🏻‍🌾 woman farmer: light skin tone -1F469 1F3FC 200D 1F33E ; fully-qualified # 👩🏼‍🌾 woman farmer: medium-light skin tone -1F469 1F3FD 200D 1F33E ; fully-qualified # 👩🏽‍🌾 woman farmer: medium skin tone -1F469 1F3FE 200D 1F33E ; fully-qualified # 👩🏾‍🌾 woman farmer: medium-dark skin tone -1F469 1F3FF 200D 1F33E ; fully-qualified # 👩🏿‍🌾 woman farmer: dark skin tone -1F468 200D 1F373 ; fully-qualified # 👨‍🍳 man cook -1F468 1F3FB 200D 1F373 ; fully-qualified # 👨🏻‍🍳 man cook: light skin tone -1F468 1F3FC 200D 1F373 ; fully-qualified # 👨🏼‍🍳 man cook: medium-light skin tone -1F468 1F3FD 200D 1F373 ; fully-qualified # 👨🏽‍🍳 man cook: medium skin tone -1F468 1F3FE 200D 1F373 ; fully-qualified # 👨🏾‍🍳 man cook: medium-dark skin tone -1F468 1F3FF 200D 1F373 ; fully-qualified # 👨🏿‍🍳 man cook: dark skin tone -1F469 200D 1F373 ; fully-qualified # 👩‍🍳 woman cook -1F469 1F3FB 200D 1F373 ; fully-qualified # 👩🏻‍🍳 woman cook: light skin tone -1F469 1F3FC 200D 1F373 ; fully-qualified # 👩🏼‍🍳 woman cook: medium-light skin tone -1F469 1F3FD 200D 1F373 ; fully-qualified # 👩🏽‍🍳 woman cook: medium skin tone -1F469 1F3FE 200D 1F373 ; fully-qualified # 👩🏾‍🍳 woman cook: medium-dark skin tone -1F469 1F3FF 200D 1F373 ; fully-qualified # 👩🏿‍🍳 woman cook: dark skin tone -1F468 200D 1F527 ; fully-qualified # 👨‍🔧 man mechanic -1F468 1F3FB 200D 1F527 ; fully-qualified # 👨🏻‍🔧 man mechanic: light skin tone -1F468 1F3FC 200D 1F527 ; fully-qualified # 👨🏼‍🔧 man mechanic: medium-light skin tone -1F468 1F3FD 200D 1F527 ; fully-qualified # 👨🏽‍🔧 man mechanic: medium skin tone -1F468 1F3FE 200D 1F527 ; fully-qualified # 👨🏾‍🔧 man mechanic: medium-dark skin tone -1F468 1F3FF 200D 1F527 ; fully-qualified # 👨🏿‍🔧 man mechanic: dark skin tone -1F469 200D 1F527 ; fully-qualified # 👩‍🔧 woman mechanic -1F469 1F3FB 200D 1F527 ; fully-qualified # 👩🏻‍🔧 woman mechanic: light skin tone -1F469 1F3FC 200D 1F527 ; fully-qualified # 👩🏼‍🔧 woman mechanic: medium-light skin tone -1F469 1F3FD 200D 1F527 ; fully-qualified # 👩🏽‍🔧 woman mechanic: medium skin tone -1F469 1F3FE 200D 1F527 ; fully-qualified # 👩🏾‍🔧 woman mechanic: medium-dark skin tone -1F469 1F3FF 200D 1F527 ; fully-qualified # 👩🏿‍🔧 woman mechanic: dark skin tone -1F468 200D 1F3ED ; fully-qualified # 👨‍🏭 man factory worker -1F468 1F3FB 200D 1F3ED ; fully-qualified # 👨🏻‍🏭 man factory worker: light skin tone -1F468 1F3FC 200D 1F3ED ; fully-qualified # 👨🏼‍🏭 man factory worker: medium-light skin tone -1F468 1F3FD 200D 1F3ED ; fully-qualified # 👨🏽‍🏭 man factory worker: medium skin tone -1F468 1F3FE 200D 1F3ED ; fully-qualified # 👨🏾‍🏭 man factory worker: medium-dark skin tone -1F468 1F3FF 200D 1F3ED ; fully-qualified # 👨🏿‍🏭 man factory worker: dark skin tone -1F469 200D 1F3ED ; fully-qualified # 👩‍🏭 woman factory worker -1F469 1F3FB 200D 1F3ED ; fully-qualified # 👩🏻‍🏭 woman factory worker: light skin tone -1F469 1F3FC 200D 1F3ED ; fully-qualified # 👩🏼‍🏭 woman factory worker: medium-light skin tone -1F469 1F3FD 200D 1F3ED ; fully-qualified # 👩🏽‍🏭 woman factory worker: medium skin tone -1F469 1F3FE 200D 1F3ED ; fully-qualified # 👩🏾‍🏭 woman factory worker: medium-dark skin tone -1F469 1F3FF 200D 1F3ED ; fully-qualified # 👩🏿‍🏭 woman factory worker: dark skin tone -1F468 200D 1F4BC ; fully-qualified # 👨‍💼 man office worker -1F468 1F3FB 200D 1F4BC ; fully-qualified # 👨🏻‍💼 man office worker: light skin tone -1F468 1F3FC 200D 1F4BC ; fully-qualified # 👨🏼‍💼 man office worker: medium-light skin tone -1F468 1F3FD 200D 1F4BC ; fully-qualified # 👨🏽‍💼 man office worker: medium skin tone -1F468 1F3FE 200D 1F4BC ; fully-qualified # 👨🏾‍💼 man office worker: medium-dark skin tone -1F468 1F3FF 200D 1F4BC ; fully-qualified # 👨🏿‍💼 man office worker: dark skin tone -1F469 200D 1F4BC ; fully-qualified # 👩‍💼 woman office worker -1F469 1F3FB 200D 1F4BC ; fully-qualified # 👩🏻‍💼 woman office worker: light skin tone -1F469 1F3FC 200D 1F4BC ; fully-qualified # 👩🏼‍💼 woman office worker: medium-light skin tone -1F469 1F3FD 200D 1F4BC ; fully-qualified # 👩🏽‍💼 woman office worker: medium skin tone -1F469 1F3FE 200D 1F4BC ; fully-qualified # 👩🏾‍💼 woman office worker: medium-dark skin tone -1F469 1F3FF 200D 1F4BC ; fully-qualified # 👩🏿‍💼 woman office worker: dark skin tone -1F468 200D 1F52C ; fully-qualified # 👨‍🔬 man scientist -1F468 1F3FB 200D 1F52C ; fully-qualified # 👨🏻‍🔬 man scientist: light skin tone -1F468 1F3FC 200D 1F52C ; fully-qualified # 👨🏼‍🔬 man scientist: medium-light skin tone -1F468 1F3FD 200D 1F52C ; fully-qualified # 👨🏽‍🔬 man scientist: medium skin tone -1F468 1F3FE 200D 1F52C ; fully-qualified # 👨🏾‍🔬 man scientist: medium-dark skin tone -1F468 1F3FF 200D 1F52C ; fully-qualified # 👨🏿‍🔬 man scientist: dark skin tone -1F469 200D 1F52C ; fully-qualified # 👩‍🔬 woman scientist -1F469 1F3FB 200D 1F52C ; fully-qualified # 👩🏻‍🔬 woman scientist: light skin tone -1F469 1F3FC 200D 1F52C ; fully-qualified # 👩🏼‍🔬 woman scientist: medium-light skin tone -1F469 1F3FD 200D 1F52C ; fully-qualified # 👩🏽‍🔬 woman scientist: medium skin tone -1F469 1F3FE 200D 1F52C ; fully-qualified # 👩🏾‍🔬 woman scientist: medium-dark skin tone -1F469 1F3FF 200D 1F52C ; fully-qualified # 👩🏿‍🔬 woman scientist: dark skin tone -1F468 200D 1F4BB ; fully-qualified # 👨‍💻 man technologist -1F468 1F3FB 200D 1F4BB ; fully-qualified # 👨🏻‍💻 man technologist: light skin tone -1F468 1F3FC 200D 1F4BB ; fully-qualified # 👨🏼‍💻 man technologist: medium-light skin tone -1F468 1F3FD 200D 1F4BB ; fully-qualified # 👨🏽‍💻 man technologist: medium skin tone -1F468 1F3FE 200D 1F4BB ; fully-qualified # 👨🏾‍💻 man technologist: medium-dark skin tone -1F468 1F3FF 200D 1F4BB ; fully-qualified # 👨🏿‍💻 man technologist: dark skin tone -1F469 200D 1F4BB ; fully-qualified # 👩‍💻 woman technologist -1F469 1F3FB 200D 1F4BB ; fully-qualified # 👩🏻‍💻 woman technologist: light skin tone -1F469 1F3FC 200D 1F4BB ; fully-qualified # 👩🏼‍💻 woman technologist: medium-light skin tone -1F469 1F3FD 200D 1F4BB ; fully-qualified # 👩🏽‍💻 woman technologist: medium skin tone -1F469 1F3FE 200D 1F4BB ; fully-qualified # 👩🏾‍💻 woman technologist: medium-dark skin tone -1F469 1F3FF 200D 1F4BB ; fully-qualified # 👩🏿‍💻 woman technologist: dark skin tone -1F468 200D 1F3A4 ; fully-qualified # 👨‍🎤 man singer -1F468 1F3FB 200D 1F3A4 ; fully-qualified # 👨🏻‍🎤 man singer: light skin tone -1F468 1F3FC 200D 1F3A4 ; fully-qualified # 👨🏼‍🎤 man singer: medium-light skin tone -1F468 1F3FD 200D 1F3A4 ; fully-qualified # 👨🏽‍🎤 man singer: medium skin tone -1F468 1F3FE 200D 1F3A4 ; fully-qualified # 👨🏾‍🎤 man singer: medium-dark skin tone -1F468 1F3FF 200D 1F3A4 ; fully-qualified # 👨🏿‍🎤 man singer: dark skin tone -1F469 200D 1F3A4 ; fully-qualified # 👩‍🎤 woman singer -1F469 1F3FB 200D 1F3A4 ; fully-qualified # 👩🏻‍🎤 woman singer: light skin tone -1F469 1F3FC 200D 1F3A4 ; fully-qualified # 👩🏼‍🎤 woman singer: medium-light skin tone -1F469 1F3FD 200D 1F3A4 ; fully-qualified # 👩🏽‍🎤 woman singer: medium skin tone -1F469 1F3FE 200D 1F3A4 ; fully-qualified # 👩🏾‍🎤 woman singer: medium-dark skin tone -1F469 1F3FF 200D 1F3A4 ; fully-qualified # 👩🏿‍🎤 woman singer: dark skin tone -1F468 200D 1F3A8 ; fully-qualified # 👨‍🎨 man artist -1F468 1F3FB 200D 1F3A8 ; fully-qualified # 👨🏻‍🎨 man artist: light skin tone -1F468 1F3FC 200D 1F3A8 ; fully-qualified # 👨🏼‍🎨 man artist: medium-light skin tone -1F468 1F3FD 200D 1F3A8 ; fully-qualified # 👨🏽‍🎨 man artist: medium skin tone -1F468 1F3FE 200D 1F3A8 ; fully-qualified # 👨🏾‍🎨 man artist: medium-dark skin tone -1F468 1F3FF 200D 1F3A8 ; fully-qualified # 👨🏿‍🎨 man artist: dark skin tone -1F469 200D 1F3A8 ; fully-qualified # 👩‍🎨 woman artist -1F469 1F3FB 200D 1F3A8 ; fully-qualified # 👩🏻‍🎨 woman artist: light skin tone -1F469 1F3FC 200D 1F3A8 ; fully-qualified # 👩🏼‍🎨 woman artist: medium-light skin tone -1F469 1F3FD 200D 1F3A8 ; fully-qualified # 👩🏽‍🎨 woman artist: medium skin tone -1F469 1F3FE 200D 1F3A8 ; fully-qualified # 👩🏾‍🎨 woman artist: medium-dark skin tone -1F469 1F3FF 200D 1F3A8 ; fully-qualified # 👩🏿‍🎨 woman artist: dark skin tone -1F468 200D 2708 FE0F ; fully-qualified # 👨‍✈️ man pilot -1F468 200D 2708 ; non-fully-qualified # 👨‍✈ man pilot -1F468 1F3FB 200D 2708 FE0F ; fully-qualified # 👨🏻‍✈️ man pilot: light skin tone -1F468 1F3FB 200D 2708 ; non-fully-qualified # 👨🏻‍✈ man pilot: light skin tone -1F468 1F3FC 200D 2708 FE0F ; fully-qualified # 👨🏼‍✈️ man pilot: medium-light skin tone -1F468 1F3FC 200D 2708 ; non-fully-qualified # 👨🏼‍✈ man pilot: medium-light skin tone -1F468 1F3FD 200D 2708 FE0F ; fully-qualified # 👨🏽‍✈️ man pilot: medium skin tone -1F468 1F3FD 200D 2708 ; non-fully-qualified # 👨🏽‍✈ man pilot: medium skin tone -1F468 1F3FE 200D 2708 FE0F ; fully-qualified # 👨🏾‍✈️ man pilot: medium-dark skin tone -1F468 1F3FE 200D 2708 ; non-fully-qualified # 👨🏾‍✈ man pilot: medium-dark skin tone -1F468 1F3FF 200D 2708 FE0F ; fully-qualified # 👨🏿‍✈️ man pilot: dark skin tone -1F468 1F3FF 200D 2708 ; non-fully-qualified # 👨🏿‍✈ man pilot: dark skin tone -1F469 200D 2708 FE0F ; fully-qualified # 👩‍✈️ woman pilot -1F469 200D 2708 ; non-fully-qualified # 👩‍✈ woman pilot -1F469 1F3FB 200D 2708 FE0F ; fully-qualified # 👩🏻‍✈️ woman pilot: light skin tone -1F469 1F3FB 200D 2708 ; non-fully-qualified # 👩🏻‍✈ woman pilot: light skin tone -1F469 1F3FC 200D 2708 FE0F ; fully-qualified # 👩🏼‍✈️ woman pilot: medium-light skin tone -1F469 1F3FC 200D 2708 ; non-fully-qualified # 👩🏼‍✈ woman pilot: medium-light skin tone -1F469 1F3FD 200D 2708 FE0F ; fully-qualified # 👩🏽‍✈️ woman pilot: medium skin tone -1F469 1F3FD 200D 2708 ; non-fully-qualified # 👩🏽‍✈ woman pilot: medium skin tone -1F469 1F3FE 200D 2708 FE0F ; fully-qualified # 👩🏾‍✈️ woman pilot: medium-dark skin tone -1F469 1F3FE 200D 2708 ; non-fully-qualified # 👩🏾‍✈ woman pilot: medium-dark skin tone -1F469 1F3FF 200D 2708 FE0F ; fully-qualified # 👩🏿‍✈️ woman pilot: dark skin tone -1F469 1F3FF 200D 2708 ; non-fully-qualified # 👩🏿‍✈ woman pilot: dark skin tone -1F468 200D 1F680 ; fully-qualified # 👨‍🚀 man astronaut -1F468 1F3FB 200D 1F680 ; fully-qualified # 👨🏻‍🚀 man astronaut: light skin tone -1F468 1F3FC 200D 1F680 ; fully-qualified # 👨🏼‍🚀 man astronaut: medium-light skin tone -1F468 1F3FD 200D 1F680 ; fully-qualified # 👨🏽‍🚀 man astronaut: medium skin tone -1F468 1F3FE 200D 1F680 ; fully-qualified # 👨🏾‍🚀 man astronaut: medium-dark skin tone -1F468 1F3FF 200D 1F680 ; fully-qualified # 👨🏿‍🚀 man astronaut: dark skin tone -1F469 200D 1F680 ; fully-qualified # 👩‍🚀 woman astronaut -1F469 1F3FB 200D 1F680 ; fully-qualified # 👩🏻‍🚀 woman astronaut: light skin tone -1F469 1F3FC 200D 1F680 ; fully-qualified # 👩🏼‍🚀 woman astronaut: medium-light skin tone -1F469 1F3FD 200D 1F680 ; fully-qualified # 👩🏽‍🚀 woman astronaut: medium skin tone -1F469 1F3FE 200D 1F680 ; fully-qualified # 👩🏾‍🚀 woman astronaut: medium-dark skin tone -1F469 1F3FF 200D 1F680 ; fully-qualified # 👩🏿‍🚀 woman astronaut: dark skin tone -1F468 200D 1F692 ; fully-qualified # 👨‍🚒 man firefighter -1F468 1F3FB 200D 1F692 ; fully-qualified # 👨🏻‍🚒 man firefighter: light skin tone -1F468 1F3FC 200D 1F692 ; fully-qualified # 👨🏼‍🚒 man firefighter: medium-light skin tone -1F468 1F3FD 200D 1F692 ; fully-qualified # 👨🏽‍🚒 man firefighter: medium skin tone -1F468 1F3FE 200D 1F692 ; fully-qualified # 👨🏾‍🚒 man firefighter: medium-dark skin tone -1F468 1F3FF 200D 1F692 ; fully-qualified # 👨🏿‍🚒 man firefighter: dark skin tone -1F469 200D 1F692 ; fully-qualified # 👩‍🚒 woman firefighter -1F469 1F3FB 200D 1F692 ; fully-qualified # 👩🏻‍🚒 woman firefighter: light skin tone -1F469 1F3FC 200D 1F692 ; fully-qualified # 👩🏼‍🚒 woman firefighter: medium-light skin tone -1F469 1F3FD 200D 1F692 ; fully-qualified # 👩🏽‍🚒 woman firefighter: medium skin tone -1F469 1F3FE 200D 1F692 ; fully-qualified # 👩🏾‍🚒 woman firefighter: medium-dark skin tone -1F469 1F3FF 200D 1F692 ; fully-qualified # 👩🏿‍🚒 woman firefighter: dark skin tone -1F46E ; fully-qualified # 👮 police officer -1F46E 1F3FB ; fully-qualified # 👮🏻 police officer: light skin tone -1F46E 1F3FC ; fully-qualified # 👮🏼 police officer: medium-light skin tone -1F46E 1F3FD ; fully-qualified # 👮🏽 police officer: medium skin tone -1F46E 1F3FE ; fully-qualified # 👮🏾 police officer: medium-dark skin tone -1F46E 1F3FF ; fully-qualified # 👮🏿 police officer: dark skin tone -1F46E 200D 2642 FE0F ; fully-qualified # 👮‍♂️ man police officer -1F46E 200D 2642 ; non-fully-qualified # 👮‍♂ man police officer -1F46E 1F3FB 200D 2642 FE0F ; fully-qualified # 👮🏻‍♂️ man police officer: light skin tone -1F46E 1F3FB 200D 2642 ; non-fully-qualified # 👮🏻‍♂ man police officer: light skin tone -1F46E 1F3FC 200D 2642 FE0F ; fully-qualified # 👮🏼‍♂️ man police officer: medium-light skin tone -1F46E 1F3FC 200D 2642 ; non-fully-qualified # 👮🏼‍♂ man police officer: medium-light skin tone -1F46E 1F3FD 200D 2642 FE0F ; fully-qualified # 👮🏽‍♂️ man police officer: medium skin tone -1F46E 1F3FD 200D 2642 ; non-fully-qualified # 👮🏽‍♂ man police officer: medium skin tone -1F46E 1F3FE 200D 2642 FE0F ; fully-qualified # 👮🏾‍♂️ man police officer: medium-dark skin tone -1F46E 1F3FE 200D 2642 ; non-fully-qualified # 👮🏾‍♂ man police officer: medium-dark skin tone -1F46E 1F3FF 200D 2642 FE0F ; fully-qualified # 👮🏿‍♂️ man police officer: dark skin tone -1F46E 1F3FF 200D 2642 ; non-fully-qualified # 👮🏿‍♂ man police officer: dark skin tone -1F46E 200D 2640 FE0F ; fully-qualified # 👮‍♀️ woman police officer -1F46E 200D 2640 ; non-fully-qualified # 👮‍♀ woman police officer -1F46E 1F3FB 200D 2640 FE0F ; fully-qualified # 👮🏻‍♀️ woman police officer: light skin tone -1F46E 1F3FB 200D 2640 ; non-fully-qualified # 👮🏻‍♀ woman police officer: light skin tone -1F46E 1F3FC 200D 2640 FE0F ; fully-qualified # 👮🏼‍♀️ woman police officer: medium-light skin tone -1F46E 1F3FC 200D 2640 ; non-fully-qualified # 👮🏼‍♀ woman police officer: medium-light skin tone -1F46E 1F3FD 200D 2640 FE0F ; fully-qualified # 👮🏽‍♀️ woman police officer: medium skin tone -1F46E 1F3FD 200D 2640 ; non-fully-qualified # 👮🏽‍♀ woman police officer: medium skin tone -1F46E 1F3FE 200D 2640 FE0F ; fully-qualified # 👮🏾‍♀️ woman police officer: medium-dark skin tone -1F46E 1F3FE 200D 2640 ; non-fully-qualified # 👮🏾‍♀ woman police officer: medium-dark skin tone -1F46E 1F3FF 200D 2640 FE0F ; fully-qualified # 👮🏿‍♀️ woman police officer: dark skin tone -1F46E 1F3FF 200D 2640 ; non-fully-qualified # 👮🏿‍♀ woman police officer: dark skin tone -1F575 FE0F ; fully-qualified # 🕵️ detective -1F575 1F3FB ; fully-qualified # 🕵🏻 detective: light skin tone -1F575 1F3FC ; fully-qualified # 🕵🏼 detective: medium-light skin tone -1F575 1F3FD ; fully-qualified # 🕵🏽 detective: medium skin tone -1F575 1F3FE ; fully-qualified # 🕵🏾 detective: medium-dark skin tone -1F575 1F3FF ; fully-qualified # 🕵🏿 detective: dark skin tone -1F575 FE0F 200D 2642 FE0F ; fully-qualified # 🕵️‍♂️ man detective -1F575 200D 2642 ; non-fully-qualified # 🕵‍♂ man detective -1F575 FE0F 200D 2642 ; non-fully-qualified # 🕵️‍♂ man detective -1F575 200D 2642 FE0F ; non-fully-qualified # 🕵‍♂️ man detective -1F575 1F3FB 200D 2642 FE0F ; fully-qualified # 🕵🏻‍♂️ man detective: light skin tone -1F575 1F3FB 200D 2642 ; non-fully-qualified # 🕵🏻‍♂ man detective: light skin tone -1F575 1F3FC 200D 2642 FE0F ; fully-qualified # 🕵🏼‍♂️ man detective: medium-light skin tone -1F575 1F3FC 200D 2642 ; non-fully-qualified # 🕵🏼‍♂ man detective: medium-light skin tone -1F575 1F3FD 200D 2642 FE0F ; fully-qualified # 🕵🏽‍♂️ man detective: medium skin tone -1F575 1F3FD 200D 2642 ; non-fully-qualified # 🕵🏽‍♂ man detective: medium skin tone -1F575 1F3FE 200D 2642 FE0F ; fully-qualified # 🕵🏾‍♂️ man detective: medium-dark skin tone -1F575 1F3FE 200D 2642 ; non-fully-qualified # 🕵🏾‍♂ man detective: medium-dark skin tone -1F575 1F3FF 200D 2642 FE0F ; fully-qualified # 🕵🏿‍♂️ man detective: dark skin tone -1F575 1F3FF 200D 2642 ; non-fully-qualified # 🕵🏿‍♂ man detective: dark skin tone -1F575 FE0F 200D 2640 FE0F ; fully-qualified # 🕵️‍♀️ woman detective -1F575 200D 2640 ; non-fully-qualified # 🕵‍♀ woman detective -1F575 FE0F 200D 2640 ; non-fully-qualified # 🕵️‍♀ woman detective -1F575 200D 2640 FE0F ; non-fully-qualified # 🕵‍♀️ woman detective -1F575 1F3FB 200D 2640 FE0F ; fully-qualified # 🕵🏻‍♀️ woman detective: light skin tone -1F575 1F3FB 200D 2640 ; non-fully-qualified # 🕵🏻‍♀ woman detective: light skin tone -1F575 1F3FC 200D 2640 FE0F ; fully-qualified # 🕵🏼‍♀️ woman detective: medium-light skin tone -1F575 1F3FC 200D 2640 ; non-fully-qualified # 🕵🏼‍♀ woman detective: medium-light skin tone -1F575 1F3FD 200D 2640 FE0F ; fully-qualified # 🕵🏽‍♀️ woman detective: medium skin tone -1F575 1F3FD 200D 2640 ; non-fully-qualified # 🕵🏽‍♀ woman detective: medium skin tone -1F575 1F3FE 200D 2640 FE0F ; fully-qualified # 🕵🏾‍♀️ woman detective: medium-dark skin tone -1F575 1F3FE 200D 2640 ; non-fully-qualified # 🕵🏾‍♀ woman detective: medium-dark skin tone -1F575 1F3FF 200D 2640 FE0F ; fully-qualified # 🕵🏿‍♀️ woman detective: dark skin tone -1F575 1F3FF 200D 2640 ; non-fully-qualified # 🕵🏿‍♀ woman detective: dark skin tone -1F482 ; fully-qualified # 💂 guard -1F482 1F3FB ; fully-qualified # 💂🏻 guard: light skin tone -1F482 1F3FC ; fully-qualified # 💂🏼 guard: medium-light skin tone -1F482 1F3FD ; fully-qualified # 💂🏽 guard: medium skin tone -1F482 1F3FE ; fully-qualified # 💂🏾 guard: medium-dark skin tone -1F482 1F3FF ; fully-qualified # 💂🏿 guard: dark skin tone -1F482 200D 2642 FE0F ; fully-qualified # 💂‍♂️ man guard -1F482 200D 2642 ; non-fully-qualified # 💂‍♂ man guard -1F482 1F3FB 200D 2642 FE0F ; fully-qualified # 💂🏻‍♂️ man guard: light skin tone -1F482 1F3FB 200D 2642 ; non-fully-qualified # 💂🏻‍♂ man guard: light skin tone -1F482 1F3FC 200D 2642 FE0F ; fully-qualified # 💂🏼‍♂️ man guard: medium-light skin tone -1F482 1F3FC 200D 2642 ; non-fully-qualified # 💂🏼‍♂ man guard: medium-light skin tone -1F482 1F3FD 200D 2642 FE0F ; fully-qualified # 💂🏽‍♂️ man guard: medium skin tone -1F482 1F3FD 200D 2642 ; non-fully-qualified # 💂🏽‍♂ man guard: medium skin tone -1F482 1F3FE 200D 2642 FE0F ; fully-qualified # 💂🏾‍♂️ man guard: medium-dark skin tone -1F482 1F3FE 200D 2642 ; non-fully-qualified # 💂🏾‍♂ man guard: medium-dark skin tone -1F482 1F3FF 200D 2642 FE0F ; fully-qualified # 💂🏿‍♂️ man guard: dark skin tone -1F482 1F3FF 200D 2642 ; non-fully-qualified # 💂🏿‍♂ man guard: dark skin tone -1F482 200D 2640 FE0F ; fully-qualified # 💂‍♀️ woman guard -1F482 200D 2640 ; non-fully-qualified # 💂‍♀ woman guard -1F482 1F3FB 200D 2640 FE0F ; fully-qualified # 💂🏻‍♀️ woman guard: light skin tone -1F482 1F3FB 200D 2640 ; non-fully-qualified # 💂🏻‍♀ woman guard: light skin tone -1F482 1F3FC 200D 2640 FE0F ; fully-qualified # 💂🏼‍♀️ woman guard: medium-light skin tone -1F482 1F3FC 200D 2640 ; non-fully-qualified # 💂🏼‍♀ woman guard: medium-light skin tone -1F482 1F3FD 200D 2640 FE0F ; fully-qualified # 💂🏽‍♀️ woman guard: medium skin tone -1F482 1F3FD 200D 2640 ; non-fully-qualified # 💂🏽‍♀ woman guard: medium skin tone -1F482 1F3FE 200D 2640 FE0F ; fully-qualified # 💂🏾‍♀️ woman guard: medium-dark skin tone -1F482 1F3FE 200D 2640 ; non-fully-qualified # 💂🏾‍♀ woman guard: medium-dark skin tone -1F482 1F3FF 200D 2640 FE0F ; fully-qualified # 💂🏿‍♀️ woman guard: dark skin tone -1F482 1F3FF 200D 2640 ; non-fully-qualified # 💂🏿‍♀ woman guard: dark skin tone -1F477 ; fully-qualified # 👷 construction worker -1F477 1F3FB ; fully-qualified # 👷🏻 construction worker: light skin tone -1F477 1F3FC ; fully-qualified # 👷🏼 construction worker: medium-light skin tone -1F477 1F3FD ; fully-qualified # 👷🏽 construction worker: medium skin tone -1F477 1F3FE ; fully-qualified # 👷🏾 construction worker: medium-dark skin tone -1F477 1F3FF ; fully-qualified # 👷🏿 construction worker: dark skin tone -1F477 200D 2642 FE0F ; fully-qualified # 👷‍♂️ man construction worker -1F477 200D 2642 ; non-fully-qualified # 👷‍♂ man construction worker -1F477 1F3FB 200D 2642 FE0F ; fully-qualified # 👷🏻‍♂️ man construction worker: light skin tone -1F477 1F3FB 200D 2642 ; non-fully-qualified # 👷🏻‍♂ man construction worker: light skin tone -1F477 1F3FC 200D 2642 FE0F ; fully-qualified # 👷🏼‍♂️ man construction worker: medium-light skin tone -1F477 1F3FC 200D 2642 ; non-fully-qualified # 👷🏼‍♂ man construction worker: medium-light skin tone -1F477 1F3FD 200D 2642 FE0F ; fully-qualified # 👷🏽‍♂️ man construction worker: medium skin tone -1F477 1F3FD 200D 2642 ; non-fully-qualified # 👷🏽‍♂ man construction worker: medium skin tone -1F477 1F3FE 200D 2642 FE0F ; fully-qualified # 👷🏾‍♂️ man construction worker: medium-dark skin tone -1F477 1F3FE 200D 2642 ; non-fully-qualified # 👷🏾‍♂ man construction worker: medium-dark skin tone -1F477 1F3FF 200D 2642 FE0F ; fully-qualified # 👷🏿‍♂️ man construction worker: dark skin tone -1F477 1F3FF 200D 2642 ; non-fully-qualified # 👷🏿‍♂ man construction worker: dark skin tone -1F477 200D 2640 FE0F ; fully-qualified # 👷‍♀️ woman construction worker -1F477 200D 2640 ; non-fully-qualified # 👷‍♀ woman construction worker -1F477 1F3FB 200D 2640 FE0F ; fully-qualified # 👷🏻‍♀️ woman construction worker: light skin tone -1F477 1F3FB 200D 2640 ; non-fully-qualified # 👷🏻‍♀ woman construction worker: light skin tone -1F477 1F3FC 200D 2640 FE0F ; fully-qualified # 👷🏼‍♀️ woman construction worker: medium-light skin tone -1F477 1F3FC 200D 2640 ; non-fully-qualified # 👷🏼‍♀ woman construction worker: medium-light skin tone -1F477 1F3FD 200D 2640 FE0F ; fully-qualified # 👷🏽‍♀️ woman construction worker: medium skin tone -1F477 1F3FD 200D 2640 ; non-fully-qualified # 👷🏽‍♀ woman construction worker: medium skin tone -1F477 1F3FE 200D 2640 FE0F ; fully-qualified # 👷🏾‍♀️ woman construction worker: medium-dark skin tone -1F477 1F3FE 200D 2640 ; non-fully-qualified # 👷🏾‍♀ woman construction worker: medium-dark skin tone -1F477 1F3FF 200D 2640 FE0F ; fully-qualified # 👷🏿‍♀️ woman construction worker: dark skin tone -1F477 1F3FF 200D 2640 ; non-fully-qualified # 👷🏿‍♀ woman construction worker: dark skin tone -1F473 ; fully-qualified # 👳 person wearing turban -1F473 1F3FB ; fully-qualified # 👳🏻 person wearing turban: light skin tone -1F473 1F3FC ; fully-qualified # 👳🏼 person wearing turban: medium-light skin tone -1F473 1F3FD ; fully-qualified # 👳🏽 person wearing turban: medium skin tone -1F473 1F3FE ; fully-qualified # 👳🏾 person wearing turban: medium-dark skin tone -1F473 1F3FF ; fully-qualified # 👳🏿 person wearing turban: dark skin tone -1F473 200D 2642 FE0F ; fully-qualified # 👳‍♂️ man wearing turban -1F473 200D 2642 ; non-fully-qualified # 👳‍♂ man wearing turban -1F473 1F3FB 200D 2642 FE0F ; fully-qualified # 👳🏻‍♂️ man wearing turban: light skin tone -1F473 1F3FB 200D 2642 ; non-fully-qualified # 👳🏻‍♂ man wearing turban: light skin tone -1F473 1F3FC 200D 2642 FE0F ; fully-qualified # 👳🏼‍♂️ man wearing turban: medium-light skin tone -1F473 1F3FC 200D 2642 ; non-fully-qualified # 👳🏼‍♂ man wearing turban: medium-light skin tone -1F473 1F3FD 200D 2642 FE0F ; fully-qualified # 👳🏽‍♂️ man wearing turban: medium skin tone -1F473 1F3FD 200D 2642 ; non-fully-qualified # 👳🏽‍♂ man wearing turban: medium skin tone -1F473 1F3FE 200D 2642 FE0F ; fully-qualified # 👳🏾‍♂️ man wearing turban: medium-dark skin tone -1F473 1F3FE 200D 2642 ; non-fully-qualified # 👳🏾‍♂ man wearing turban: medium-dark skin tone -1F473 1F3FF 200D 2642 FE0F ; fully-qualified # 👳🏿‍♂️ man wearing turban: dark skin tone -1F473 1F3FF 200D 2642 ; non-fully-qualified # 👳🏿‍♂ man wearing turban: dark skin tone -1F473 200D 2640 FE0F ; fully-qualified # 👳‍♀️ woman wearing turban -1F473 200D 2640 ; non-fully-qualified # 👳‍♀ woman wearing turban -1F473 1F3FB 200D 2640 FE0F ; fully-qualified # 👳🏻‍♀️ woman wearing turban: light skin tone -1F473 1F3FB 200D 2640 ; non-fully-qualified # 👳🏻‍♀ woman wearing turban: light skin tone -1F473 1F3FC 200D 2640 FE0F ; fully-qualified # 👳🏼‍♀️ woman wearing turban: medium-light skin tone -1F473 1F3FC 200D 2640 ; non-fully-qualified # 👳🏼‍♀ woman wearing turban: medium-light skin tone -1F473 1F3FD 200D 2640 FE0F ; fully-qualified # 👳🏽‍♀️ woman wearing turban: medium skin tone -1F473 1F3FD 200D 2640 ; non-fully-qualified # 👳🏽‍♀ woman wearing turban: medium skin tone -1F473 1F3FE 200D 2640 FE0F ; fully-qualified # 👳🏾‍♀️ woman wearing turban: medium-dark skin tone -1F473 1F3FE 200D 2640 ; non-fully-qualified # 👳🏾‍♀ woman wearing turban: medium-dark skin tone -1F473 1F3FF 200D 2640 FE0F ; fully-qualified # 👳🏿‍♀️ woman wearing turban: dark skin tone -1F473 1F3FF 200D 2640 ; non-fully-qualified # 👳🏿‍♀ woman wearing turban: dark skin tone -1F471 ; fully-qualified # 👱 blond-haired person -1F471 1F3FB ; fully-qualified # 👱🏻 blond-haired person: light skin tone -1F471 1F3FC ; fully-qualified # 👱🏼 blond-haired person: medium-light skin tone -1F471 1F3FD ; fully-qualified # 👱🏽 blond-haired person: medium skin tone -1F471 1F3FE ; fully-qualified # 👱🏾 blond-haired person: medium-dark skin tone -1F471 1F3FF ; fully-qualified # 👱🏿 blond-haired person: dark skin tone -1F471 200D 2642 FE0F ; fully-qualified # 👱‍♂️ blond-haired man -1F471 200D 2642 ; non-fully-qualified # 👱‍♂ blond-haired man -1F471 1F3FB 200D 2642 FE0F ; fully-qualified # 👱🏻‍♂️ blond-haired man: light skin tone -1F471 1F3FB 200D 2642 ; non-fully-qualified # 👱🏻‍♂ blond-haired man: light skin tone -1F471 1F3FC 200D 2642 FE0F ; fully-qualified # 👱🏼‍♂️ blond-haired man: medium-light skin tone -1F471 1F3FC 200D 2642 ; non-fully-qualified # 👱🏼‍♂ blond-haired man: medium-light skin tone -1F471 1F3FD 200D 2642 FE0F ; fully-qualified # 👱🏽‍♂️ blond-haired man: medium skin tone -1F471 1F3FD 200D 2642 ; non-fully-qualified # 👱🏽‍♂ blond-haired man: medium skin tone -1F471 1F3FE 200D 2642 FE0F ; fully-qualified # 👱🏾‍♂️ blond-haired man: medium-dark skin tone -1F471 1F3FE 200D 2642 ; non-fully-qualified # 👱🏾‍♂ blond-haired man: medium-dark skin tone -1F471 1F3FF 200D 2642 FE0F ; fully-qualified # 👱🏿‍♂️ blond-haired man: dark skin tone -1F471 1F3FF 200D 2642 ; non-fully-qualified # 👱🏿‍♂ blond-haired man: dark skin tone -1F471 200D 2640 FE0F ; fully-qualified # 👱‍♀️ blond-haired woman -1F471 200D 2640 ; non-fully-qualified # 👱‍♀ blond-haired woman -1F471 1F3FB 200D 2640 FE0F ; fully-qualified # 👱🏻‍♀️ blond-haired woman: light skin tone -1F471 1F3FB 200D 2640 ; non-fully-qualified # 👱🏻‍♀ blond-haired woman: light skin tone -1F471 1F3FC 200D 2640 FE0F ; fully-qualified # 👱🏼‍♀️ blond-haired woman: medium-light skin tone -1F471 1F3FC 200D 2640 ; non-fully-qualified # 👱🏼‍♀ blond-haired woman: medium-light skin tone -1F471 1F3FD 200D 2640 FE0F ; fully-qualified # 👱🏽‍♀️ blond-haired woman: medium skin tone -1F471 1F3FD 200D 2640 ; non-fully-qualified # 👱🏽‍♀ blond-haired woman: medium skin tone -1F471 1F3FE 200D 2640 FE0F ; fully-qualified # 👱🏾‍♀️ blond-haired woman: medium-dark skin tone -1F471 1F3FE 200D 2640 ; non-fully-qualified # 👱🏾‍♀ blond-haired woman: medium-dark skin tone -1F471 1F3FF 200D 2640 FE0F ; fully-qualified # 👱🏿‍♀️ blond-haired woman: dark skin tone -1F471 1F3FF 200D 2640 ; non-fully-qualified # 👱🏿‍♀ blond-haired woman: dark skin tone -1F385 ; fully-qualified # 🎅 Santa Claus -1F385 1F3FB ; fully-qualified # 🎅🏻 Santa Claus: light skin tone -1F385 1F3FC ; fully-qualified # 🎅🏼 Santa Claus: medium-light skin tone -1F385 1F3FD ; fully-qualified # 🎅🏽 Santa Claus: medium skin tone -1F385 1F3FE ; fully-qualified # 🎅🏾 Santa Claus: medium-dark skin tone -1F385 1F3FF ; fully-qualified # 🎅🏿 Santa Claus: dark skin tone -1F936 ; fully-qualified # 🤶 Mrs. Claus -1F936 1F3FB ; fully-qualified # 🤶🏻 Mrs. Claus: light skin tone -1F936 1F3FC ; fully-qualified # 🤶🏼 Mrs. Claus: medium-light skin tone -1F936 1F3FD ; fully-qualified # 🤶🏽 Mrs. Claus: medium skin tone -1F936 1F3FE ; fully-qualified # 🤶🏾 Mrs. Claus: medium-dark skin tone -1F936 1F3FF ; fully-qualified # 🤶🏿 Mrs. Claus: dark skin tone -1F478 ; fully-qualified # 👸 princess -1F478 1F3FB ; fully-qualified # 👸🏻 princess: light skin tone -1F478 1F3FC ; fully-qualified # 👸🏼 princess: medium-light skin tone -1F478 1F3FD ; fully-qualified # 👸🏽 princess: medium skin tone -1F478 1F3FE ; fully-qualified # 👸🏾 princess: medium-dark skin tone -1F478 1F3FF ; fully-qualified # 👸🏿 princess: dark skin tone -1F934 ; fully-qualified # 🤴 prince -1F934 1F3FB ; fully-qualified # 🤴🏻 prince: light skin tone -1F934 1F3FC ; fully-qualified # 🤴🏼 prince: medium-light skin tone -1F934 1F3FD ; fully-qualified # 🤴🏽 prince: medium skin tone -1F934 1F3FE ; fully-qualified # 🤴🏾 prince: medium-dark skin tone -1F934 1F3FF ; fully-qualified # 🤴🏿 prince: dark skin tone -1F470 ; fully-qualified # 👰 bride with veil -1F470 1F3FB ; fully-qualified # 👰🏻 bride with veil: light skin tone -1F470 1F3FC ; fully-qualified # 👰🏼 bride with veil: medium-light skin tone -1F470 1F3FD ; fully-qualified # 👰🏽 bride with veil: medium skin tone -1F470 1F3FE ; fully-qualified # 👰🏾 bride with veil: medium-dark skin tone -1F470 1F3FF ; fully-qualified # 👰🏿 bride with veil: dark skin tone -1F935 ; fully-qualified # 🤵 man in tuxedo -1F935 1F3FB ; fully-qualified # 🤵🏻 man in tuxedo: light skin tone -1F935 1F3FC ; fully-qualified # 🤵🏼 man in tuxedo: medium-light skin tone -1F935 1F3FD ; fully-qualified # 🤵🏽 man in tuxedo: medium skin tone -1F935 1F3FE ; fully-qualified # 🤵🏾 man in tuxedo: medium-dark skin tone -1F935 1F3FF ; fully-qualified # 🤵🏿 man in tuxedo: dark skin tone -1F930 ; fully-qualified # 🤰 pregnant woman -1F930 1F3FB ; fully-qualified # 🤰🏻 pregnant woman: light skin tone -1F930 1F3FC ; fully-qualified # 🤰🏼 pregnant woman: medium-light skin tone -1F930 1F3FD ; fully-qualified # 🤰🏽 pregnant woman: medium skin tone -1F930 1F3FE ; fully-qualified # 🤰🏾 pregnant woman: medium-dark skin tone -1F930 1F3FF ; fully-qualified # 🤰🏿 pregnant woman: dark skin tone -1F472 ; fully-qualified # 👲 man with Chinese cap -1F472 1F3FB ; fully-qualified # 👲🏻 man with Chinese cap: light skin tone -1F472 1F3FC ; fully-qualified # 👲🏼 man with Chinese cap: medium-light skin tone -1F472 1F3FD ; fully-qualified # 👲🏽 man with Chinese cap: medium skin tone -1F472 1F3FE ; fully-qualified # 👲🏾 man with Chinese cap: medium-dark skin tone -1F472 1F3FF ; fully-qualified # 👲🏿 man with Chinese cap: dark skin tone - -# subgroup: person-gesture -1F64D ; fully-qualified # 🙍 person frowning -1F64D 1F3FB ; fully-qualified # 🙍🏻 person frowning: light skin tone -1F64D 1F3FC ; fully-qualified # 🙍🏼 person frowning: medium-light skin tone -1F64D 1F3FD ; fully-qualified # 🙍🏽 person frowning: medium skin tone -1F64D 1F3FE ; fully-qualified # 🙍🏾 person frowning: medium-dark skin tone -1F64D 1F3FF ; fully-qualified # 🙍🏿 person frowning: dark skin tone -1F64D 200D 2642 FE0F ; fully-qualified # 🙍‍♂️ man frowning -1F64D 200D 2642 ; non-fully-qualified # 🙍‍♂ man frowning -1F64D 1F3FB 200D 2642 FE0F ; fully-qualified # 🙍🏻‍♂️ man frowning: light skin tone -1F64D 1F3FB 200D 2642 ; non-fully-qualified # 🙍🏻‍♂ man frowning: light skin tone -1F64D 1F3FC 200D 2642 FE0F ; fully-qualified # 🙍🏼‍♂️ man frowning: medium-light skin tone -1F64D 1F3FC 200D 2642 ; non-fully-qualified # 🙍🏼‍♂ man frowning: medium-light skin tone -1F64D 1F3FD 200D 2642 FE0F ; fully-qualified # 🙍🏽‍♂️ man frowning: medium skin tone -1F64D 1F3FD 200D 2642 ; non-fully-qualified # 🙍🏽‍♂ man frowning: medium skin tone -1F64D 1F3FE 200D 2642 FE0F ; fully-qualified # 🙍🏾‍♂️ man frowning: medium-dark skin tone -1F64D 1F3FE 200D 2642 ; non-fully-qualified # 🙍🏾‍♂ man frowning: medium-dark skin tone -1F64D 1F3FF 200D 2642 FE0F ; fully-qualified # 🙍🏿‍♂️ man frowning: dark skin tone -1F64D 1F3FF 200D 2642 ; non-fully-qualified # 🙍🏿‍♂ man frowning: dark skin tone -1F64D 200D 2640 FE0F ; fully-qualified # 🙍‍♀️ woman frowning -1F64D 200D 2640 ; non-fully-qualified # 🙍‍♀ woman frowning -1F64D 1F3FB 200D 2640 FE0F ; fully-qualified # 🙍🏻‍♀️ woman frowning: light skin tone -1F64D 1F3FB 200D 2640 ; non-fully-qualified # 🙍🏻‍♀ woman frowning: light skin tone -1F64D 1F3FC 200D 2640 FE0F ; fully-qualified # 🙍🏼‍♀️ woman frowning: medium-light skin tone -1F64D 1F3FC 200D 2640 ; non-fully-qualified # 🙍🏼‍♀ woman frowning: medium-light skin tone -1F64D 1F3FD 200D 2640 FE0F ; fully-qualified # 🙍🏽‍♀️ woman frowning: medium skin tone -1F64D 1F3FD 200D 2640 ; non-fully-qualified # 🙍🏽‍♀ woman frowning: medium skin tone -1F64D 1F3FE 200D 2640 FE0F ; fully-qualified # 🙍🏾‍♀️ woman frowning: medium-dark skin tone -1F64D 1F3FE 200D 2640 ; non-fully-qualified # 🙍🏾‍♀ woman frowning: medium-dark skin tone -1F64D 1F3FF 200D 2640 FE0F ; fully-qualified # 🙍🏿‍♀️ woman frowning: dark skin tone -1F64D 1F3FF 200D 2640 ; non-fully-qualified # 🙍🏿‍♀ woman frowning: dark skin tone -1F64E ; fully-qualified # 🙎 person pouting -1F64E 1F3FB ; fully-qualified # 🙎🏻 person pouting: light skin tone -1F64E 1F3FC ; fully-qualified # 🙎🏼 person pouting: medium-light skin tone -1F64E 1F3FD ; fully-qualified # 🙎🏽 person pouting: medium skin tone -1F64E 1F3FE ; fully-qualified # 🙎🏾 person pouting: medium-dark skin tone -1F64E 1F3FF ; fully-qualified # 🙎🏿 person pouting: dark skin tone -1F64E 200D 2642 FE0F ; fully-qualified # 🙎‍♂️ man pouting -1F64E 200D 2642 ; non-fully-qualified # 🙎‍♂ man pouting -1F64E 1F3FB 200D 2642 FE0F ; fully-qualified # 🙎🏻‍♂️ man pouting: light skin tone -1F64E 1F3FB 200D 2642 ; non-fully-qualified # 🙎🏻‍♂ man pouting: light skin tone -1F64E 1F3FC 200D 2642 FE0F ; fully-qualified # 🙎🏼‍♂️ man pouting: medium-light skin tone -1F64E 1F3FC 200D 2642 ; non-fully-qualified # 🙎🏼‍♂ man pouting: medium-light skin tone -1F64E 1F3FD 200D 2642 FE0F ; fully-qualified # 🙎🏽‍♂️ man pouting: medium skin tone -1F64E 1F3FD 200D 2642 ; non-fully-qualified # 🙎🏽‍♂ man pouting: medium skin tone -1F64E 1F3FE 200D 2642 FE0F ; fully-qualified # 🙎🏾‍♂️ man pouting: medium-dark skin tone -1F64E 1F3FE 200D 2642 ; non-fully-qualified # 🙎🏾‍♂ man pouting: medium-dark skin tone -1F64E 1F3FF 200D 2642 FE0F ; fully-qualified # 🙎🏿‍♂️ man pouting: dark skin tone -1F64E 1F3FF 200D 2642 ; non-fully-qualified # 🙎🏿‍♂ man pouting: dark skin tone -1F64E 200D 2640 FE0F ; fully-qualified # 🙎‍♀️ woman pouting -1F64E 200D 2640 ; non-fully-qualified # 🙎‍♀ woman pouting -1F64E 1F3FB 200D 2640 FE0F ; fully-qualified # 🙎🏻‍♀️ woman pouting: light skin tone -1F64E 1F3FB 200D 2640 ; non-fully-qualified # 🙎🏻‍♀ woman pouting: light skin tone -1F64E 1F3FC 200D 2640 FE0F ; fully-qualified # 🙎🏼‍♀️ woman pouting: medium-light skin tone -1F64E 1F3FC 200D 2640 ; non-fully-qualified # 🙎🏼‍♀ woman pouting: medium-light skin tone -1F64E 1F3FD 200D 2640 FE0F ; fully-qualified # 🙎🏽‍♀️ woman pouting: medium skin tone -1F64E 1F3FD 200D 2640 ; non-fully-qualified # 🙎🏽‍♀ woman pouting: medium skin tone -1F64E 1F3FE 200D 2640 FE0F ; fully-qualified # 🙎🏾‍♀️ woman pouting: medium-dark skin tone -1F64E 1F3FE 200D 2640 ; non-fully-qualified # 🙎🏾‍♀ woman pouting: medium-dark skin tone -1F64E 1F3FF 200D 2640 FE0F ; fully-qualified # 🙎🏿‍♀️ woman pouting: dark skin tone -1F64E 1F3FF 200D 2640 ; non-fully-qualified # 🙎🏿‍♀ woman pouting: dark skin tone -1F645 ; fully-qualified # 🙅 person gesturing NO -1F645 1F3FB ; fully-qualified # 🙅🏻 person gesturing NO: light skin tone -1F645 1F3FC ; fully-qualified # 🙅🏼 person gesturing NO: medium-light skin tone -1F645 1F3FD ; fully-qualified # 🙅🏽 person gesturing NO: medium skin tone -1F645 1F3FE ; fully-qualified # 🙅🏾 person gesturing NO: medium-dark skin tone -1F645 1F3FF ; fully-qualified # 🙅🏿 person gesturing NO: dark skin tone -1F645 200D 2642 FE0F ; fully-qualified # 🙅‍♂️ man gesturing NO -1F645 200D 2642 ; non-fully-qualified # 🙅‍♂ man gesturing NO -1F645 1F3FB 200D 2642 FE0F ; fully-qualified # 🙅🏻‍♂️ man gesturing NO: light skin tone -1F645 1F3FB 200D 2642 ; non-fully-qualified # 🙅🏻‍♂ man gesturing NO: light skin tone -1F645 1F3FC 200D 2642 FE0F ; fully-qualified # 🙅🏼‍♂️ man gesturing NO: medium-light skin tone -1F645 1F3FC 200D 2642 ; non-fully-qualified # 🙅🏼‍♂ man gesturing NO: medium-light skin tone -1F645 1F3FD 200D 2642 FE0F ; fully-qualified # 🙅🏽‍♂️ man gesturing NO: medium skin tone -1F645 1F3FD 200D 2642 ; non-fully-qualified # 🙅🏽‍♂ man gesturing NO: medium skin tone -1F645 1F3FE 200D 2642 FE0F ; fully-qualified # 🙅🏾‍♂️ man gesturing NO: medium-dark skin tone -1F645 1F3FE 200D 2642 ; non-fully-qualified # 🙅🏾‍♂ man gesturing NO: medium-dark skin tone -1F645 1F3FF 200D 2642 FE0F ; fully-qualified # 🙅🏿‍♂️ man gesturing NO: dark skin tone -1F645 1F3FF 200D 2642 ; non-fully-qualified # 🙅🏿‍♂ man gesturing NO: dark skin tone -1F645 200D 2640 FE0F ; fully-qualified # 🙅‍♀️ woman gesturing NO -1F645 200D 2640 ; non-fully-qualified # 🙅‍♀ woman gesturing NO -1F645 1F3FB 200D 2640 FE0F ; fully-qualified # 🙅🏻‍♀️ woman gesturing NO: light skin tone -1F645 1F3FB 200D 2640 ; non-fully-qualified # 🙅🏻‍♀ woman gesturing NO: light skin tone -1F645 1F3FC 200D 2640 FE0F ; fully-qualified # 🙅🏼‍♀️ woman gesturing NO: medium-light skin tone -1F645 1F3FC 200D 2640 ; non-fully-qualified # 🙅🏼‍♀ woman gesturing NO: medium-light skin tone -1F645 1F3FD 200D 2640 FE0F ; fully-qualified # 🙅🏽‍♀️ woman gesturing NO: medium skin tone -1F645 1F3FD 200D 2640 ; non-fully-qualified # 🙅🏽‍♀ woman gesturing NO: medium skin tone -1F645 1F3FE 200D 2640 FE0F ; fully-qualified # 🙅🏾‍♀️ woman gesturing NO: medium-dark skin tone -1F645 1F3FE 200D 2640 ; non-fully-qualified # 🙅🏾‍♀ woman gesturing NO: medium-dark skin tone -1F645 1F3FF 200D 2640 FE0F ; fully-qualified # 🙅🏿‍♀️ woman gesturing NO: dark skin tone -1F645 1F3FF 200D 2640 ; non-fully-qualified # 🙅🏿‍♀ woman gesturing NO: dark skin tone -1F646 ; fully-qualified # 🙆 person gesturing OK -1F646 1F3FB ; fully-qualified # 🙆🏻 person gesturing OK: light skin tone -1F646 1F3FC ; fully-qualified # 🙆🏼 person gesturing OK: medium-light skin tone -1F646 1F3FD ; fully-qualified # 🙆🏽 person gesturing OK: medium skin tone -1F646 1F3FE ; fully-qualified # 🙆🏾 person gesturing OK: medium-dark skin tone -1F646 1F3FF ; fully-qualified # 🙆🏿 person gesturing OK: dark skin tone -1F646 200D 2642 FE0F ; fully-qualified # 🙆‍♂️ man gesturing OK -1F646 200D 2642 ; non-fully-qualified # 🙆‍♂ man gesturing OK -1F646 1F3FB 200D 2642 FE0F ; fully-qualified # 🙆🏻‍♂️ man gesturing OK: light skin tone -1F646 1F3FB 200D 2642 ; non-fully-qualified # 🙆🏻‍♂ man gesturing OK: light skin tone -1F646 1F3FC 200D 2642 FE0F ; fully-qualified # 🙆🏼‍♂️ man gesturing OK: medium-light skin tone -1F646 1F3FC 200D 2642 ; non-fully-qualified # 🙆🏼‍♂ man gesturing OK: medium-light skin tone -1F646 1F3FD 200D 2642 FE0F ; fully-qualified # 🙆🏽‍♂️ man gesturing OK: medium skin tone -1F646 1F3FD 200D 2642 ; non-fully-qualified # 🙆🏽‍♂ man gesturing OK: medium skin tone -1F646 1F3FE 200D 2642 FE0F ; fully-qualified # 🙆🏾‍♂️ man gesturing OK: medium-dark skin tone -1F646 1F3FE 200D 2642 ; non-fully-qualified # 🙆🏾‍♂ man gesturing OK: medium-dark skin tone -1F646 1F3FF 200D 2642 FE0F ; fully-qualified # 🙆🏿‍♂️ man gesturing OK: dark skin tone -1F646 1F3FF 200D 2642 ; non-fully-qualified # 🙆🏿‍♂ man gesturing OK: dark skin tone -1F646 200D 2640 FE0F ; fully-qualified # 🙆‍♀️ woman gesturing OK -1F646 200D 2640 ; non-fully-qualified # 🙆‍♀ woman gesturing OK -1F646 1F3FB 200D 2640 FE0F ; fully-qualified # 🙆🏻‍♀️ woman gesturing OK: light skin tone -1F646 1F3FB 200D 2640 ; non-fully-qualified # 🙆🏻‍♀ woman gesturing OK: light skin tone -1F646 1F3FC 200D 2640 FE0F ; fully-qualified # 🙆🏼‍♀️ woman gesturing OK: medium-light skin tone -1F646 1F3FC 200D 2640 ; non-fully-qualified # 🙆🏼‍♀ woman gesturing OK: medium-light skin tone -1F646 1F3FD 200D 2640 FE0F ; fully-qualified # 🙆🏽‍♀️ woman gesturing OK: medium skin tone -1F646 1F3FD 200D 2640 ; non-fully-qualified # 🙆🏽‍♀ woman gesturing OK: medium skin tone -1F646 1F3FE 200D 2640 FE0F ; fully-qualified # 🙆🏾‍♀️ woman gesturing OK: medium-dark skin tone -1F646 1F3FE 200D 2640 ; non-fully-qualified # 🙆🏾‍♀ woman gesturing OK: medium-dark skin tone -1F646 1F3FF 200D 2640 FE0F ; fully-qualified # 🙆🏿‍♀️ woman gesturing OK: dark skin tone -1F646 1F3FF 200D 2640 ; non-fully-qualified # 🙆🏿‍♀ woman gesturing OK: dark skin tone -1F481 ; fully-qualified # 💁 person tipping hand -1F481 1F3FB ; fully-qualified # 💁🏻 person tipping hand: light skin tone -1F481 1F3FC ; fully-qualified # 💁🏼 person tipping hand: medium-light skin tone -1F481 1F3FD ; fully-qualified # 💁🏽 person tipping hand: medium skin tone -1F481 1F3FE ; fully-qualified # 💁🏾 person tipping hand: medium-dark skin tone -1F481 1F3FF ; fully-qualified # 💁🏿 person tipping hand: dark skin tone -1F481 200D 2642 FE0F ; fully-qualified # 💁‍♂️ man tipping hand -1F481 200D 2642 ; non-fully-qualified # 💁‍♂ man tipping hand -1F481 1F3FB 200D 2642 FE0F ; fully-qualified # 💁🏻‍♂️ man tipping hand: light skin tone -1F481 1F3FB 200D 2642 ; non-fully-qualified # 💁🏻‍♂ man tipping hand: light skin tone -1F481 1F3FC 200D 2642 FE0F ; fully-qualified # 💁🏼‍♂️ man tipping hand: medium-light skin tone -1F481 1F3FC 200D 2642 ; non-fully-qualified # 💁🏼‍♂ man tipping hand: medium-light skin tone -1F481 1F3FD 200D 2642 FE0F ; fully-qualified # 💁🏽‍♂️ man tipping hand: medium skin tone -1F481 1F3FD 200D 2642 ; non-fully-qualified # 💁🏽‍♂ man tipping hand: medium skin tone -1F481 1F3FE 200D 2642 FE0F ; fully-qualified # 💁🏾‍♂️ man tipping hand: medium-dark skin tone -1F481 1F3FE 200D 2642 ; non-fully-qualified # 💁🏾‍♂ man tipping hand: medium-dark skin tone -1F481 1F3FF 200D 2642 FE0F ; fully-qualified # 💁🏿‍♂️ man tipping hand: dark skin tone -1F481 1F3FF 200D 2642 ; non-fully-qualified # 💁🏿‍♂ man tipping hand: dark skin tone -1F481 200D 2640 FE0F ; fully-qualified # 💁‍♀️ woman tipping hand -1F481 200D 2640 ; non-fully-qualified # 💁‍♀ woman tipping hand -1F481 1F3FB 200D 2640 FE0F ; fully-qualified # 💁🏻‍♀️ woman tipping hand: light skin tone -1F481 1F3FB 200D 2640 ; non-fully-qualified # 💁🏻‍♀ woman tipping hand: light skin tone -1F481 1F3FC 200D 2640 FE0F ; fully-qualified # 💁🏼‍♀️ woman tipping hand: medium-light skin tone -1F481 1F3FC 200D 2640 ; non-fully-qualified # 💁🏼‍♀ woman tipping hand: medium-light skin tone -1F481 1F3FD 200D 2640 FE0F ; fully-qualified # 💁🏽‍♀️ woman tipping hand: medium skin tone -1F481 1F3FD 200D 2640 ; non-fully-qualified # 💁🏽‍♀ woman tipping hand: medium skin tone -1F481 1F3FE 200D 2640 FE0F ; fully-qualified # 💁🏾‍♀️ woman tipping hand: medium-dark skin tone -1F481 1F3FE 200D 2640 ; non-fully-qualified # 💁🏾‍♀ woman tipping hand: medium-dark skin tone -1F481 1F3FF 200D 2640 FE0F ; fully-qualified # 💁🏿‍♀️ woman tipping hand: dark skin tone -1F481 1F3FF 200D 2640 ; non-fully-qualified # 💁🏿‍♀ woman tipping hand: dark skin tone -1F64B ; fully-qualified # 🙋 person raising hand -1F64B 1F3FB ; fully-qualified # 🙋🏻 person raising hand: light skin tone -1F64B 1F3FC ; fully-qualified # 🙋🏼 person raising hand: medium-light skin tone -1F64B 1F3FD ; fully-qualified # 🙋🏽 person raising hand: medium skin tone -1F64B 1F3FE ; fully-qualified # 🙋🏾 person raising hand: medium-dark skin tone -1F64B 1F3FF ; fully-qualified # 🙋🏿 person raising hand: dark skin tone -1F64B 200D 2642 FE0F ; fully-qualified # 🙋‍♂️ man raising hand -1F64B 200D 2642 ; non-fully-qualified # 🙋‍♂ man raising hand -1F64B 1F3FB 200D 2642 FE0F ; fully-qualified # 🙋🏻‍♂️ man raising hand: light skin tone -1F64B 1F3FB 200D 2642 ; non-fully-qualified # 🙋🏻‍♂ man raising hand: light skin tone -1F64B 1F3FC 200D 2642 FE0F ; fully-qualified # 🙋🏼‍♂️ man raising hand: medium-light skin tone -1F64B 1F3FC 200D 2642 ; non-fully-qualified # 🙋🏼‍♂ man raising hand: medium-light skin tone -1F64B 1F3FD 200D 2642 FE0F ; fully-qualified # 🙋🏽‍♂️ man raising hand: medium skin tone -1F64B 1F3FD 200D 2642 ; non-fully-qualified # 🙋🏽‍♂ man raising hand: medium skin tone -1F64B 1F3FE 200D 2642 FE0F ; fully-qualified # 🙋🏾‍♂️ man raising hand: medium-dark skin tone -1F64B 1F3FE 200D 2642 ; non-fully-qualified # 🙋🏾‍♂ man raising hand: medium-dark skin tone -1F64B 1F3FF 200D 2642 FE0F ; fully-qualified # 🙋🏿‍♂️ man raising hand: dark skin tone -1F64B 1F3FF 200D 2642 ; non-fully-qualified # 🙋🏿‍♂ man raising hand: dark skin tone -1F64B 200D 2640 FE0F ; fully-qualified # 🙋‍♀️ woman raising hand -1F64B 200D 2640 ; non-fully-qualified # 🙋‍♀ woman raising hand -1F64B 1F3FB 200D 2640 FE0F ; fully-qualified # 🙋🏻‍♀️ woman raising hand: light skin tone -1F64B 1F3FB 200D 2640 ; non-fully-qualified # 🙋🏻‍♀ woman raising hand: light skin tone -1F64B 1F3FC 200D 2640 FE0F ; fully-qualified # 🙋🏼‍♀️ woman raising hand: medium-light skin tone -1F64B 1F3FC 200D 2640 ; non-fully-qualified # 🙋🏼‍♀ woman raising hand: medium-light skin tone -1F64B 1F3FD 200D 2640 FE0F ; fully-qualified # 🙋🏽‍♀️ woman raising hand: medium skin tone -1F64B 1F3FD 200D 2640 ; non-fully-qualified # 🙋🏽‍♀ woman raising hand: medium skin tone -1F64B 1F3FE 200D 2640 FE0F ; fully-qualified # 🙋🏾‍♀️ woman raising hand: medium-dark skin tone -1F64B 1F3FE 200D 2640 ; non-fully-qualified # 🙋🏾‍♀ woman raising hand: medium-dark skin tone -1F64B 1F3FF 200D 2640 FE0F ; fully-qualified # 🙋🏿‍♀️ woman raising hand: dark skin tone -1F64B 1F3FF 200D 2640 ; non-fully-qualified # 🙋🏿‍♀ woman raising hand: dark skin tone -1F647 ; fully-qualified # 🙇 person bowing -1F647 1F3FB ; fully-qualified # 🙇🏻 person bowing: light skin tone -1F647 1F3FC ; fully-qualified # 🙇🏼 person bowing: medium-light skin tone -1F647 1F3FD ; fully-qualified # 🙇🏽 person bowing: medium skin tone -1F647 1F3FE ; fully-qualified # 🙇🏾 person bowing: medium-dark skin tone -1F647 1F3FF ; fully-qualified # 🙇🏿 person bowing: dark skin tone -1F647 200D 2642 FE0F ; fully-qualified # 🙇‍♂️ man bowing -1F647 200D 2642 ; non-fully-qualified # 🙇‍♂ man bowing -1F647 1F3FB 200D 2642 FE0F ; fully-qualified # 🙇🏻‍♂️ man bowing: light skin tone -1F647 1F3FB 200D 2642 ; non-fully-qualified # 🙇🏻‍♂ man bowing: light skin tone -1F647 1F3FC 200D 2642 FE0F ; fully-qualified # 🙇🏼‍♂️ man bowing: medium-light skin tone -1F647 1F3FC 200D 2642 ; non-fully-qualified # 🙇🏼‍♂ man bowing: medium-light skin tone -1F647 1F3FD 200D 2642 FE0F ; fully-qualified # 🙇🏽‍♂️ man bowing: medium skin tone -1F647 1F3FD 200D 2642 ; non-fully-qualified # 🙇🏽‍♂ man bowing: medium skin tone -1F647 1F3FE 200D 2642 FE0F ; fully-qualified # 🙇🏾‍♂️ man bowing: medium-dark skin tone -1F647 1F3FE 200D 2642 ; non-fully-qualified # 🙇🏾‍♂ man bowing: medium-dark skin tone -1F647 1F3FF 200D 2642 FE0F ; fully-qualified # 🙇🏿‍♂️ man bowing: dark skin tone -1F647 1F3FF 200D 2642 ; non-fully-qualified # 🙇🏿‍♂ man bowing: dark skin tone -1F647 200D 2640 FE0F ; fully-qualified # 🙇‍♀️ woman bowing -1F647 200D 2640 ; non-fully-qualified # 🙇‍♀ woman bowing -1F647 1F3FB 200D 2640 FE0F ; fully-qualified # 🙇🏻‍♀️ woman bowing: light skin tone -1F647 1F3FB 200D 2640 ; non-fully-qualified # 🙇🏻‍♀ woman bowing: light skin tone -1F647 1F3FC 200D 2640 FE0F ; fully-qualified # 🙇🏼‍♀️ woman bowing: medium-light skin tone -1F647 1F3FC 200D 2640 ; non-fully-qualified # 🙇🏼‍♀ woman bowing: medium-light skin tone -1F647 1F3FD 200D 2640 FE0F ; fully-qualified # 🙇🏽‍♀️ woman bowing: medium skin tone -1F647 1F3FD 200D 2640 ; non-fully-qualified # 🙇🏽‍♀ woman bowing: medium skin tone -1F647 1F3FE 200D 2640 FE0F ; fully-qualified # 🙇🏾‍♀️ woman bowing: medium-dark skin tone -1F647 1F3FE 200D 2640 ; non-fully-qualified # 🙇🏾‍♀ woman bowing: medium-dark skin tone -1F647 1F3FF 200D 2640 FE0F ; fully-qualified # 🙇🏿‍♀️ woman bowing: dark skin tone -1F647 1F3FF 200D 2640 ; non-fully-qualified # 🙇🏿‍♀ woman bowing: dark skin tone -1F926 ; fully-qualified # 🤦 person facepalming -1F926 1F3FB ; fully-qualified # 🤦🏻 person facepalming: light skin tone -1F926 1F3FC ; fully-qualified # 🤦🏼 person facepalming: medium-light skin tone -1F926 1F3FD ; fully-qualified # 🤦🏽 person facepalming: medium skin tone -1F926 1F3FE ; fully-qualified # 🤦🏾 person facepalming: medium-dark skin tone -1F926 1F3FF ; fully-qualified # 🤦🏿 person facepalming: dark skin tone -1F926 200D 2642 FE0F ; fully-qualified # 🤦‍♂️ man facepalming -1F926 200D 2642 ; non-fully-qualified # 🤦‍♂ man facepalming -1F926 1F3FB 200D 2642 FE0F ; fully-qualified # 🤦🏻‍♂️ man facepalming: light skin tone -1F926 1F3FB 200D 2642 ; non-fully-qualified # 🤦🏻‍♂ man facepalming: light skin tone -1F926 1F3FC 200D 2642 FE0F ; fully-qualified # 🤦🏼‍♂️ man facepalming: medium-light skin tone -1F926 1F3FC 200D 2642 ; non-fully-qualified # 🤦🏼‍♂ man facepalming: medium-light skin tone -1F926 1F3FD 200D 2642 FE0F ; fully-qualified # 🤦🏽‍♂️ man facepalming: medium skin tone -1F926 1F3FD 200D 2642 ; non-fully-qualified # 🤦🏽‍♂ man facepalming: medium skin tone -1F926 1F3FE 200D 2642 FE0F ; fully-qualified # 🤦🏾‍♂️ man facepalming: medium-dark skin tone -1F926 1F3FE 200D 2642 ; non-fully-qualified # 🤦🏾‍♂ man facepalming: medium-dark skin tone -1F926 1F3FF 200D 2642 FE0F ; fully-qualified # 🤦🏿‍♂️ man facepalming: dark skin tone -1F926 1F3FF 200D 2642 ; non-fully-qualified # 🤦🏿‍♂ man facepalming: dark skin tone -1F926 200D 2640 FE0F ; fully-qualified # 🤦‍♀️ woman facepalming -1F926 200D 2640 ; non-fully-qualified # 🤦‍♀ woman facepalming -1F926 1F3FB 200D 2640 FE0F ; fully-qualified # 🤦🏻‍♀️ woman facepalming: light skin tone -1F926 1F3FB 200D 2640 ; non-fully-qualified # 🤦🏻‍♀ woman facepalming: light skin tone -1F926 1F3FC 200D 2640 FE0F ; fully-qualified # 🤦🏼‍♀️ woman facepalming: medium-light skin tone -1F926 1F3FC 200D 2640 ; non-fully-qualified # 🤦🏼‍♀ woman facepalming: medium-light skin tone -1F926 1F3FD 200D 2640 FE0F ; fully-qualified # 🤦🏽‍♀️ woman facepalming: medium skin tone -1F926 1F3FD 200D 2640 ; non-fully-qualified # 🤦🏽‍♀ woman facepalming: medium skin tone -1F926 1F3FE 200D 2640 FE0F ; fully-qualified # 🤦🏾‍♀️ woman facepalming: medium-dark skin tone -1F926 1F3FE 200D 2640 ; non-fully-qualified # 🤦🏾‍♀ woman facepalming: medium-dark skin tone -1F926 1F3FF 200D 2640 FE0F ; fully-qualified # 🤦🏿‍♀️ woman facepalming: dark skin tone -1F926 1F3FF 200D 2640 ; non-fully-qualified # 🤦🏿‍♀ woman facepalming: dark skin tone -1F937 ; fully-qualified # 🤷 person shrugging -1F937 1F3FB ; fully-qualified # 🤷🏻 person shrugging: light skin tone -1F937 1F3FC ; fully-qualified # 🤷🏼 person shrugging: medium-light skin tone -1F937 1F3FD ; fully-qualified # 🤷🏽 person shrugging: medium skin tone -1F937 1F3FE ; fully-qualified # 🤷🏾 person shrugging: medium-dark skin tone -1F937 1F3FF ; fully-qualified # 🤷🏿 person shrugging: dark skin tone -1F937 200D 2642 FE0F ; fully-qualified # 🤷‍♂️ man shrugging -1F937 200D 2642 ; non-fully-qualified # 🤷‍♂ man shrugging -1F937 1F3FB 200D 2642 FE0F ; fully-qualified # 🤷🏻‍♂️ man shrugging: light skin tone -1F937 1F3FB 200D 2642 ; non-fully-qualified # 🤷🏻‍♂ man shrugging: light skin tone -1F937 1F3FC 200D 2642 FE0F ; fully-qualified # 🤷🏼‍♂️ man shrugging: medium-light skin tone -1F937 1F3FC 200D 2642 ; non-fully-qualified # 🤷🏼‍♂ man shrugging: medium-light skin tone -1F937 1F3FD 200D 2642 FE0F ; fully-qualified # 🤷🏽‍♂️ man shrugging: medium skin tone -1F937 1F3FD 200D 2642 ; non-fully-qualified # 🤷🏽‍♂ man shrugging: medium skin tone -1F937 1F3FE 200D 2642 FE0F ; fully-qualified # 🤷🏾‍♂️ man shrugging: medium-dark skin tone -1F937 1F3FE 200D 2642 ; non-fully-qualified # 🤷🏾‍♂ man shrugging: medium-dark skin tone -1F937 1F3FF 200D 2642 FE0F ; fully-qualified # 🤷🏿‍♂️ man shrugging: dark skin tone -1F937 1F3FF 200D 2642 ; non-fully-qualified # 🤷🏿‍♂ man shrugging: dark skin tone -1F937 200D 2640 FE0F ; fully-qualified # 🤷‍♀️ woman shrugging -1F937 200D 2640 ; non-fully-qualified # 🤷‍♀ woman shrugging -1F937 1F3FB 200D 2640 FE0F ; fully-qualified # 🤷🏻‍♀️ woman shrugging: light skin tone -1F937 1F3FB 200D 2640 ; non-fully-qualified # 🤷🏻‍♀ woman shrugging: light skin tone -1F937 1F3FC 200D 2640 FE0F ; fully-qualified # 🤷🏼‍♀️ woman shrugging: medium-light skin tone -1F937 1F3FC 200D 2640 ; non-fully-qualified # 🤷🏼‍♀ woman shrugging: medium-light skin tone -1F937 1F3FD 200D 2640 FE0F ; fully-qualified # 🤷🏽‍♀️ woman shrugging: medium skin tone -1F937 1F3FD 200D 2640 ; non-fully-qualified # 🤷🏽‍♀ woman shrugging: medium skin tone -1F937 1F3FE 200D 2640 FE0F ; fully-qualified # 🤷🏾‍♀️ woman shrugging: medium-dark skin tone -1F937 1F3FE 200D 2640 ; non-fully-qualified # 🤷🏾‍♀ woman shrugging: medium-dark skin tone -1F937 1F3FF 200D 2640 FE0F ; fully-qualified # 🤷🏿‍♀️ woman shrugging: dark skin tone -1F937 1F3FF 200D 2640 ; non-fully-qualified # 🤷🏿‍♀ woman shrugging: dark skin tone - -# subgroup: person-activity -1F486 ; fully-qualified # 💆 person getting massage -1F486 1F3FB ; fully-qualified # 💆🏻 person getting massage: light skin tone -1F486 1F3FC ; fully-qualified # 💆🏼 person getting massage: medium-light skin tone -1F486 1F3FD ; fully-qualified # 💆🏽 person getting massage: medium skin tone -1F486 1F3FE ; fully-qualified # 💆🏾 person getting massage: medium-dark skin tone -1F486 1F3FF ; fully-qualified # 💆🏿 person getting massage: dark skin tone -1F486 200D 2642 FE0F ; fully-qualified # 💆‍♂️ man getting massage -1F486 200D 2642 ; non-fully-qualified # 💆‍♂ man getting massage -1F486 1F3FB 200D 2642 FE0F ; fully-qualified # 💆🏻‍♂️ man getting massage: light skin tone -1F486 1F3FB 200D 2642 ; non-fully-qualified # 💆🏻‍♂ man getting massage: light skin tone -1F486 1F3FC 200D 2642 FE0F ; fully-qualified # 💆🏼‍♂️ man getting massage: medium-light skin tone -1F486 1F3FC 200D 2642 ; non-fully-qualified # 💆🏼‍♂ man getting massage: medium-light skin tone -1F486 1F3FD 200D 2642 FE0F ; fully-qualified # 💆🏽‍♂️ man getting massage: medium skin tone -1F486 1F3FD 200D 2642 ; non-fully-qualified # 💆🏽‍♂ man getting massage: medium skin tone -1F486 1F3FE 200D 2642 FE0F ; fully-qualified # 💆🏾‍♂️ man getting massage: medium-dark skin tone -1F486 1F3FE 200D 2642 ; non-fully-qualified # 💆🏾‍♂ man getting massage: medium-dark skin tone -1F486 1F3FF 200D 2642 FE0F ; fully-qualified # 💆🏿‍♂️ man getting massage: dark skin tone -1F486 1F3FF 200D 2642 ; non-fully-qualified # 💆🏿‍♂ man getting massage: dark skin tone -1F486 200D 2640 FE0F ; fully-qualified # 💆‍♀️ woman getting massage -1F486 200D 2640 ; non-fully-qualified # 💆‍♀ woman getting massage -1F486 1F3FB 200D 2640 FE0F ; fully-qualified # 💆🏻‍♀️ woman getting massage: light skin tone -1F486 1F3FB 200D 2640 ; non-fully-qualified # 💆🏻‍♀ woman getting massage: light skin tone -1F486 1F3FC 200D 2640 FE0F ; fully-qualified # 💆🏼‍♀️ woman getting massage: medium-light skin tone -1F486 1F3FC 200D 2640 ; non-fully-qualified # 💆🏼‍♀ woman getting massage: medium-light skin tone -1F486 1F3FD 200D 2640 FE0F ; fully-qualified # 💆🏽‍♀️ woman getting massage: medium skin tone -1F486 1F3FD 200D 2640 ; non-fully-qualified # 💆🏽‍♀ woman getting massage: medium skin tone -1F486 1F3FE 200D 2640 FE0F ; fully-qualified # 💆🏾‍♀️ woman getting massage: medium-dark skin tone -1F486 1F3FE 200D 2640 ; non-fully-qualified # 💆🏾‍♀ woman getting massage: medium-dark skin tone -1F486 1F3FF 200D 2640 FE0F ; fully-qualified # 💆🏿‍♀️ woman getting massage: dark skin tone -1F486 1F3FF 200D 2640 ; non-fully-qualified # 💆🏿‍♀ woman getting massage: dark skin tone -1F487 ; fully-qualified # 💇 person getting haircut -1F487 1F3FB ; fully-qualified # 💇🏻 person getting haircut: light skin tone -1F487 1F3FC ; fully-qualified # 💇🏼 person getting haircut: medium-light skin tone -1F487 1F3FD ; fully-qualified # 💇🏽 person getting haircut: medium skin tone -1F487 1F3FE ; fully-qualified # 💇🏾 person getting haircut: medium-dark skin tone -1F487 1F3FF ; fully-qualified # 💇🏿 person getting haircut: dark skin tone -1F487 200D 2642 FE0F ; fully-qualified # 💇‍♂️ man getting haircut -1F487 200D 2642 ; non-fully-qualified # 💇‍♂ man getting haircut -1F487 1F3FB 200D 2642 FE0F ; fully-qualified # 💇🏻‍♂️ man getting haircut: light skin tone -1F487 1F3FB 200D 2642 ; non-fully-qualified # 💇🏻‍♂ man getting haircut: light skin tone -1F487 1F3FC 200D 2642 FE0F ; fully-qualified # 💇🏼‍♂️ man getting haircut: medium-light skin tone -1F487 1F3FC 200D 2642 ; non-fully-qualified # 💇🏼‍♂ man getting haircut: medium-light skin tone -1F487 1F3FD 200D 2642 FE0F ; fully-qualified # 💇🏽‍♂️ man getting haircut: medium skin tone -1F487 1F3FD 200D 2642 ; non-fully-qualified # 💇🏽‍♂ man getting haircut: medium skin tone -1F487 1F3FE 200D 2642 FE0F ; fully-qualified # 💇🏾‍♂️ man getting haircut: medium-dark skin tone -1F487 1F3FE 200D 2642 ; non-fully-qualified # 💇🏾‍♂ man getting haircut: medium-dark skin tone -1F487 1F3FF 200D 2642 FE0F ; fully-qualified # 💇🏿‍♂️ man getting haircut: dark skin tone -1F487 1F3FF 200D 2642 ; non-fully-qualified # 💇🏿‍♂ man getting haircut: dark skin tone -1F487 200D 2640 FE0F ; fully-qualified # 💇‍♀️ woman getting haircut -1F487 200D 2640 ; non-fully-qualified # 💇‍♀ woman getting haircut -1F487 1F3FB 200D 2640 FE0F ; fully-qualified # 💇🏻‍♀️ woman getting haircut: light skin tone -1F487 1F3FB 200D 2640 ; non-fully-qualified # 💇🏻‍♀ woman getting haircut: light skin tone -1F487 1F3FC 200D 2640 FE0F ; fully-qualified # 💇🏼‍♀️ woman getting haircut: medium-light skin tone -1F487 1F3FC 200D 2640 ; non-fully-qualified # 💇🏼‍♀ woman getting haircut: medium-light skin tone -1F487 1F3FD 200D 2640 FE0F ; fully-qualified # 💇🏽‍♀️ woman getting haircut: medium skin tone -1F487 1F3FD 200D 2640 ; non-fully-qualified # 💇🏽‍♀ woman getting haircut: medium skin tone -1F487 1F3FE 200D 2640 FE0F ; fully-qualified # 💇🏾‍♀️ woman getting haircut: medium-dark skin tone -1F487 1F3FE 200D 2640 ; non-fully-qualified # 💇🏾‍♀ woman getting haircut: medium-dark skin tone -1F487 1F3FF 200D 2640 FE0F ; fully-qualified # 💇🏿‍♀️ woman getting haircut: dark skin tone -1F487 1F3FF 200D 2640 ; non-fully-qualified # 💇🏿‍♀ woman getting haircut: dark skin tone -1F6B6 ; fully-qualified # 🚶 person walking -1F6B6 1F3FB ; fully-qualified # 🚶🏻 person walking: light skin tone -1F6B6 1F3FC ; fully-qualified # 🚶🏼 person walking: medium-light skin tone -1F6B6 1F3FD ; fully-qualified # 🚶🏽 person walking: medium skin tone -1F6B6 1F3FE ; fully-qualified # 🚶🏾 person walking: medium-dark skin tone -1F6B6 1F3FF ; fully-qualified # 🚶🏿 person walking: dark skin tone -1F6B6 200D 2642 FE0F ; fully-qualified # 🚶‍♂️ man walking -1F6B6 200D 2642 ; non-fully-qualified # 🚶‍♂ man walking -1F6B6 1F3FB 200D 2642 FE0F ; fully-qualified # 🚶🏻‍♂️ man walking: light skin tone -1F6B6 1F3FB 200D 2642 ; non-fully-qualified # 🚶🏻‍♂ man walking: light skin tone -1F6B6 1F3FC 200D 2642 FE0F ; fully-qualified # 🚶🏼‍♂️ man walking: medium-light skin tone -1F6B6 1F3FC 200D 2642 ; non-fully-qualified # 🚶🏼‍♂ man walking: medium-light skin tone -1F6B6 1F3FD 200D 2642 FE0F ; fully-qualified # 🚶🏽‍♂️ man walking: medium skin tone -1F6B6 1F3FD 200D 2642 ; non-fully-qualified # 🚶🏽‍♂ man walking: medium skin tone -1F6B6 1F3FE 200D 2642 FE0F ; fully-qualified # 🚶🏾‍♂️ man walking: medium-dark skin tone -1F6B6 1F3FE 200D 2642 ; non-fully-qualified # 🚶🏾‍♂ man walking: medium-dark skin tone -1F6B6 1F3FF 200D 2642 FE0F ; fully-qualified # 🚶🏿‍♂️ man walking: dark skin tone -1F6B6 1F3FF 200D 2642 ; non-fully-qualified # 🚶🏿‍♂ man walking: dark skin tone -1F6B6 200D 2640 FE0F ; fully-qualified # 🚶‍♀️ woman walking -1F6B6 200D 2640 ; non-fully-qualified # 🚶‍♀ woman walking -1F6B6 1F3FB 200D 2640 FE0F ; fully-qualified # 🚶🏻‍♀️ woman walking: light skin tone -1F6B6 1F3FB 200D 2640 ; non-fully-qualified # 🚶🏻‍♀ woman walking: light skin tone -1F6B6 1F3FC 200D 2640 FE0F ; fully-qualified # 🚶🏼‍♀️ woman walking: medium-light skin tone -1F6B6 1F3FC 200D 2640 ; non-fully-qualified # 🚶🏼‍♀ woman walking: medium-light skin tone -1F6B6 1F3FD 200D 2640 FE0F ; fully-qualified # 🚶🏽‍♀️ woman walking: medium skin tone -1F6B6 1F3FD 200D 2640 ; non-fully-qualified # 🚶🏽‍♀ woman walking: medium skin tone -1F6B6 1F3FE 200D 2640 FE0F ; fully-qualified # 🚶🏾‍♀️ woman walking: medium-dark skin tone -1F6B6 1F3FE 200D 2640 ; non-fully-qualified # 🚶🏾‍♀ woman walking: medium-dark skin tone -1F6B6 1F3FF 200D 2640 FE0F ; fully-qualified # 🚶🏿‍♀️ woman walking: dark skin tone -1F6B6 1F3FF 200D 2640 ; non-fully-qualified # 🚶🏿‍♀ woman walking: dark skin tone -1F3C3 ; fully-qualified # 🏃 person running -1F3C3 1F3FB ; fully-qualified # 🏃🏻 person running: light skin tone -1F3C3 1F3FC ; fully-qualified # 🏃🏼 person running: medium-light skin tone -1F3C3 1F3FD ; fully-qualified # 🏃🏽 person running: medium skin tone -1F3C3 1F3FE ; fully-qualified # 🏃🏾 person running: medium-dark skin tone -1F3C3 1F3FF ; fully-qualified # 🏃🏿 person running: dark skin tone -1F3C3 200D 2642 FE0F ; fully-qualified # 🏃‍♂️ man running -1F3C3 200D 2642 ; non-fully-qualified # 🏃‍♂ man running -1F3C3 1F3FB 200D 2642 FE0F ; fully-qualified # 🏃🏻‍♂️ man running: light skin tone -1F3C3 1F3FB 200D 2642 ; non-fully-qualified # 🏃🏻‍♂ man running: light skin tone -1F3C3 1F3FC 200D 2642 FE0F ; fully-qualified # 🏃🏼‍♂️ man running: medium-light skin tone -1F3C3 1F3FC 200D 2642 ; non-fully-qualified # 🏃🏼‍♂ man running: medium-light skin tone -1F3C3 1F3FD 200D 2642 FE0F ; fully-qualified # 🏃🏽‍♂️ man running: medium skin tone -1F3C3 1F3FD 200D 2642 ; non-fully-qualified # 🏃🏽‍♂ man running: medium skin tone -1F3C3 1F3FE 200D 2642 FE0F ; fully-qualified # 🏃🏾‍♂️ man running: medium-dark skin tone -1F3C3 1F3FE 200D 2642 ; non-fully-qualified # 🏃🏾‍♂ man running: medium-dark skin tone -1F3C3 1F3FF 200D 2642 FE0F ; fully-qualified # 🏃🏿‍♂️ man running: dark skin tone -1F3C3 1F3FF 200D 2642 ; non-fully-qualified # 🏃🏿‍♂ man running: dark skin tone -1F3C3 200D 2640 FE0F ; fully-qualified # 🏃‍♀️ woman running -1F3C3 200D 2640 ; non-fully-qualified # 🏃‍♀ woman running -1F3C3 1F3FB 200D 2640 FE0F ; fully-qualified # 🏃🏻‍♀️ woman running: light skin tone -1F3C3 1F3FB 200D 2640 ; non-fully-qualified # 🏃🏻‍♀ woman running: light skin tone -1F3C3 1F3FC 200D 2640 FE0F ; fully-qualified # 🏃🏼‍♀️ woman running: medium-light skin tone -1F3C3 1F3FC 200D 2640 ; non-fully-qualified # 🏃🏼‍♀ woman running: medium-light skin tone -1F3C3 1F3FD 200D 2640 FE0F ; fully-qualified # 🏃🏽‍♀️ woman running: medium skin tone -1F3C3 1F3FD 200D 2640 ; non-fully-qualified # 🏃🏽‍♀ woman running: medium skin tone -1F3C3 1F3FE 200D 2640 FE0F ; fully-qualified # 🏃🏾‍♀️ woman running: medium-dark skin tone -1F3C3 1F3FE 200D 2640 ; non-fully-qualified # 🏃🏾‍♀ woman running: medium-dark skin tone -1F3C3 1F3FF 200D 2640 FE0F ; fully-qualified # 🏃🏿‍♀️ woman running: dark skin tone -1F3C3 1F3FF 200D 2640 ; non-fully-qualified # 🏃🏿‍♀ woman running: dark skin tone -1F483 ; fully-qualified # 💃 woman dancing -1F483 1F3FB ; fully-qualified # 💃🏻 woman dancing: light skin tone -1F483 1F3FC ; fully-qualified # 💃🏼 woman dancing: medium-light skin tone -1F483 1F3FD ; fully-qualified # 💃🏽 woman dancing: medium skin tone -1F483 1F3FE ; fully-qualified # 💃🏾 woman dancing: medium-dark skin tone -1F483 1F3FF ; fully-qualified # 💃🏿 woman dancing: dark skin tone -1F57A ; fully-qualified # 🕺 man dancing -1F57A 1F3FB ; fully-qualified # 🕺🏻 man dancing: light skin tone -1F57A 1F3FC ; fully-qualified # 🕺🏼 man dancing: medium-light skin tone -1F57A 1F3FD ; fully-qualified # 🕺🏽 man dancing: medium skin tone -1F57A 1F3FE ; fully-qualified # 🕺🏾 man dancing: medium-dark skin tone -1F57A 1F3FF ; fully-qualified # 🕺🏿 man dancing: dark skin tone -1F46F ; fully-qualified # 👯 people with bunny ears partying -1F46F 200D 2642 FE0F ; fully-qualified # 👯‍♂️ men with bunny ears partying -1F46F 200D 2642 ; non-fully-qualified # 👯‍♂ men with bunny ears partying -1F46F 200D 2640 FE0F ; fully-qualified # 👯‍♀️ women with bunny ears partying -1F46F 200D 2640 ; non-fully-qualified # 👯‍♀ women with bunny ears partying -1F574 FE0F ; fully-qualified # 🕴️ man in business suit levitating -1F574 1F3FB ; fully-qualified # 🕴🏻 man in business suit levitating: light skin tone -1F574 1F3FC ; fully-qualified # 🕴🏼 man in business suit levitating: medium-light skin tone -1F574 1F3FD ; fully-qualified # 🕴🏽 man in business suit levitating: medium skin tone -1F574 1F3FE ; fully-qualified # 🕴🏾 man in business suit levitating: medium-dark skin tone -1F574 1F3FF ; fully-qualified # 🕴🏿 man in business suit levitating: dark skin tone -1F5E3 FE0F ; fully-qualified # 🗣️ speaking head -1F464 ; fully-qualified # 👤 bust in silhouette -1F465 ; fully-qualified # 👥 busts in silhouette - -# subgroup: person-sport -1F93A ; fully-qualified # 🤺 person fencing -1F3C7 ; fully-qualified # 🏇 horse racing -1F3C7 1F3FB ; fully-qualified # 🏇🏻 horse racing: light skin tone -1F3C7 1F3FC ; fully-qualified # 🏇🏼 horse racing: medium-light skin tone -1F3C7 1F3FD ; fully-qualified # 🏇🏽 horse racing: medium skin tone -1F3C7 1F3FE ; fully-qualified # 🏇🏾 horse racing: medium-dark skin tone -1F3C7 1F3FF ; fully-qualified # 🏇🏿 horse racing: dark skin tone -26F7 FE0F ; fully-qualified # ⛷️ skier -1F3C2 ; fully-qualified # 🏂 snowboarder -1F3C2 1F3FB ; fully-qualified # 🏂🏻 snowboarder: light skin tone -1F3C2 1F3FC ; fully-qualified # 🏂🏼 snowboarder: medium-light skin tone -1F3C2 1F3FD ; fully-qualified # 🏂🏽 snowboarder: medium skin tone -1F3C2 1F3FE ; fully-qualified # 🏂🏾 snowboarder: medium-dark skin tone -1F3C2 1F3FF ; fully-qualified # 🏂🏿 snowboarder: dark skin tone -1F3CC FE0F ; fully-qualified # 🏌️ person golfing -1F3CC 1F3FB ; fully-qualified # 🏌🏻 person golfing: light skin tone -1F3CC 1F3FC ; fully-qualified # 🏌🏼 person golfing: medium-light skin tone -1F3CC 1F3FD ; fully-qualified # 🏌🏽 person golfing: medium skin tone -1F3CC 1F3FE ; fully-qualified # 🏌🏾 person golfing: medium-dark skin tone -1F3CC 1F3FF ; fully-qualified # 🏌🏿 person golfing: dark skin tone -1F3CC FE0F 200D 2642 FE0F ; fully-qualified # 🏌️‍♂️ man golfing -1F3CC 200D 2642 ; non-fully-qualified # 🏌‍♂ man golfing -1F3CC FE0F 200D 2642 ; non-fully-qualified # 🏌️‍♂ man golfing -1F3CC 200D 2642 FE0F ; non-fully-qualified # 🏌‍♂️ man golfing -1F3CC 1F3FB 200D 2642 FE0F ; fully-qualified # 🏌🏻‍♂️ man golfing: light skin tone -1F3CC 1F3FB 200D 2642 ; non-fully-qualified # 🏌🏻‍♂ man golfing: light skin tone -1F3CC 1F3FC 200D 2642 FE0F ; fully-qualified # 🏌🏼‍♂️ man golfing: medium-light skin tone -1F3CC 1F3FC 200D 2642 ; non-fully-qualified # 🏌🏼‍♂ man golfing: medium-light skin tone -1F3CC 1F3FD 200D 2642 FE0F ; fully-qualified # 🏌🏽‍♂️ man golfing: medium skin tone -1F3CC 1F3FD 200D 2642 ; non-fully-qualified # 🏌🏽‍♂ man golfing: medium skin tone -1F3CC 1F3FE 200D 2642 FE0F ; fully-qualified # 🏌🏾‍♂️ man golfing: medium-dark skin tone -1F3CC 1F3FE 200D 2642 ; non-fully-qualified # 🏌🏾‍♂ man golfing: medium-dark skin tone -1F3CC 1F3FF 200D 2642 FE0F ; fully-qualified # 🏌🏿‍♂️ man golfing: dark skin tone -1F3CC 1F3FF 200D 2642 ; non-fully-qualified # 🏌🏿‍♂ man golfing: dark skin tone -1F3CC FE0F 200D 2640 FE0F ; fully-qualified # 🏌️‍♀️ woman golfing -1F3CC 200D 2640 ; non-fully-qualified # 🏌‍♀ woman golfing -1F3CC FE0F 200D 2640 ; non-fully-qualified # 🏌️‍♀ woman golfing -1F3CC 200D 2640 FE0F ; non-fully-qualified # 🏌‍♀️ woman golfing -1F3CC 1F3FB 200D 2640 FE0F ; fully-qualified # 🏌🏻‍♀️ woman golfing: light skin tone -1F3CC 1F3FB 200D 2640 ; non-fully-qualified # 🏌🏻‍♀ woman golfing: light skin tone -1F3CC 1F3FC 200D 2640 FE0F ; fully-qualified # 🏌🏼‍♀️ woman golfing: medium-light skin tone -1F3CC 1F3FC 200D 2640 ; non-fully-qualified # 🏌🏼‍♀ woman golfing: medium-light skin tone -1F3CC 1F3FD 200D 2640 FE0F ; fully-qualified # 🏌🏽‍♀️ woman golfing: medium skin tone -1F3CC 1F3FD 200D 2640 ; non-fully-qualified # 🏌🏽‍♀ woman golfing: medium skin tone -1F3CC 1F3FE 200D 2640 FE0F ; fully-qualified # 🏌🏾‍♀️ woman golfing: medium-dark skin tone -1F3CC 1F3FE 200D 2640 ; non-fully-qualified # 🏌🏾‍♀ woman golfing: medium-dark skin tone -1F3CC 1F3FF 200D 2640 FE0F ; fully-qualified # 🏌🏿‍♀️ woman golfing: dark skin tone -1F3CC 1F3FF 200D 2640 ; non-fully-qualified # 🏌🏿‍♀ woman golfing: dark skin tone -1F3C4 ; fully-qualified # 🏄 person surfing -1F3C4 1F3FB ; fully-qualified # 🏄🏻 person surfing: light skin tone -1F3C4 1F3FC ; fully-qualified # 🏄🏼 person surfing: medium-light skin tone -1F3C4 1F3FD ; fully-qualified # 🏄🏽 person surfing: medium skin tone -1F3C4 1F3FE ; fully-qualified # 🏄🏾 person surfing: medium-dark skin tone -1F3C4 1F3FF ; fully-qualified # 🏄🏿 person surfing: dark skin tone -1F3C4 200D 2642 FE0F ; fully-qualified # 🏄‍♂️ man surfing -1F3C4 200D 2642 ; non-fully-qualified # 🏄‍♂ man surfing -1F3C4 1F3FB 200D 2642 FE0F ; fully-qualified # 🏄🏻‍♂️ man surfing: light skin tone -1F3C4 1F3FB 200D 2642 ; non-fully-qualified # 🏄🏻‍♂ man surfing: light skin tone -1F3C4 1F3FC 200D 2642 FE0F ; fully-qualified # 🏄🏼‍♂️ man surfing: medium-light skin tone -1F3C4 1F3FC 200D 2642 ; non-fully-qualified # 🏄🏼‍♂ man surfing: medium-light skin tone -1F3C4 1F3FD 200D 2642 FE0F ; fully-qualified # 🏄🏽‍♂️ man surfing: medium skin tone -1F3C4 1F3FD 200D 2642 ; non-fully-qualified # 🏄🏽‍♂ man surfing: medium skin tone -1F3C4 1F3FE 200D 2642 FE0F ; fully-qualified # 🏄🏾‍♂️ man surfing: medium-dark skin tone -1F3C4 1F3FE 200D 2642 ; non-fully-qualified # 🏄🏾‍♂ man surfing: medium-dark skin tone -1F3C4 1F3FF 200D 2642 FE0F ; fully-qualified # 🏄🏿‍♂️ man surfing: dark skin tone -1F3C4 1F3FF 200D 2642 ; non-fully-qualified # 🏄🏿‍♂ man surfing: dark skin tone -1F3C4 200D 2640 FE0F ; fully-qualified # 🏄‍♀️ woman surfing -1F3C4 200D 2640 ; non-fully-qualified # 🏄‍♀ woman surfing -1F3C4 1F3FB 200D 2640 FE0F ; fully-qualified # 🏄🏻‍♀️ woman surfing: light skin tone -1F3C4 1F3FB 200D 2640 ; non-fully-qualified # 🏄🏻‍♀ woman surfing: light skin tone -1F3C4 1F3FC 200D 2640 FE0F ; fully-qualified # 🏄🏼‍♀️ woman surfing: medium-light skin tone -1F3C4 1F3FC 200D 2640 ; non-fully-qualified # 🏄🏼‍♀ woman surfing: medium-light skin tone -1F3C4 1F3FD 200D 2640 FE0F ; fully-qualified # 🏄🏽‍♀️ woman surfing: medium skin tone -1F3C4 1F3FD 200D 2640 ; non-fully-qualified # 🏄🏽‍♀ woman surfing: medium skin tone -1F3C4 1F3FE 200D 2640 FE0F ; fully-qualified # 🏄🏾‍♀️ woman surfing: medium-dark skin tone -1F3C4 1F3FE 200D 2640 ; non-fully-qualified # 🏄🏾‍♀ woman surfing: medium-dark skin tone -1F3C4 1F3FF 200D 2640 FE0F ; fully-qualified # 🏄🏿‍♀️ woman surfing: dark skin tone -1F3C4 1F3FF 200D 2640 ; non-fully-qualified # 🏄🏿‍♀ woman surfing: dark skin tone -1F6A3 ; fully-qualified # 🚣 person rowing boat -1F6A3 1F3FB ; fully-qualified # 🚣🏻 person rowing boat: light skin tone -1F6A3 1F3FC ; fully-qualified # 🚣🏼 person rowing boat: medium-light skin tone -1F6A3 1F3FD ; fully-qualified # 🚣🏽 person rowing boat: medium skin tone -1F6A3 1F3FE ; fully-qualified # 🚣🏾 person rowing boat: medium-dark skin tone -1F6A3 1F3FF ; fully-qualified # 🚣🏿 person rowing boat: dark skin tone -1F6A3 200D 2642 FE0F ; fully-qualified # 🚣‍♂️ man rowing boat -1F6A3 200D 2642 ; non-fully-qualified # 🚣‍♂ man rowing boat -1F6A3 1F3FB 200D 2642 FE0F ; fully-qualified # 🚣🏻‍♂️ man rowing boat: light skin tone -1F6A3 1F3FB 200D 2642 ; non-fully-qualified # 🚣🏻‍♂ man rowing boat: light skin tone -1F6A3 1F3FC 200D 2642 FE0F ; fully-qualified # 🚣🏼‍♂️ man rowing boat: medium-light skin tone -1F6A3 1F3FC 200D 2642 ; non-fully-qualified # 🚣🏼‍♂ man rowing boat: medium-light skin tone -1F6A3 1F3FD 200D 2642 FE0F ; fully-qualified # 🚣🏽‍♂️ man rowing boat: medium skin tone -1F6A3 1F3FD 200D 2642 ; non-fully-qualified # 🚣🏽‍♂ man rowing boat: medium skin tone -1F6A3 1F3FE 200D 2642 FE0F ; fully-qualified # 🚣🏾‍♂️ man rowing boat: medium-dark skin tone -1F6A3 1F3FE 200D 2642 ; non-fully-qualified # 🚣🏾‍♂ man rowing boat: medium-dark skin tone -1F6A3 1F3FF 200D 2642 FE0F ; fully-qualified # 🚣🏿‍♂️ man rowing boat: dark skin tone -1F6A3 1F3FF 200D 2642 ; non-fully-qualified # 🚣🏿‍♂ man rowing boat: dark skin tone -1F6A3 200D 2640 FE0F ; fully-qualified # 🚣‍♀️ woman rowing boat -1F6A3 200D 2640 ; non-fully-qualified # 🚣‍♀ woman rowing boat -1F6A3 1F3FB 200D 2640 FE0F ; fully-qualified # 🚣🏻‍♀️ woman rowing boat: light skin tone -1F6A3 1F3FB 200D 2640 ; non-fully-qualified # 🚣🏻‍♀ woman rowing boat: light skin tone -1F6A3 1F3FC 200D 2640 FE0F ; fully-qualified # 🚣🏼‍♀️ woman rowing boat: medium-light skin tone -1F6A3 1F3FC 200D 2640 ; non-fully-qualified # 🚣🏼‍♀ woman rowing boat: medium-light skin tone -1F6A3 1F3FD 200D 2640 FE0F ; fully-qualified # 🚣🏽‍♀️ woman rowing boat: medium skin tone -1F6A3 1F3FD 200D 2640 ; non-fully-qualified # 🚣🏽‍♀ woman rowing boat: medium skin tone -1F6A3 1F3FE 200D 2640 FE0F ; fully-qualified # 🚣🏾‍♀️ woman rowing boat: medium-dark skin tone -1F6A3 1F3FE 200D 2640 ; non-fully-qualified # 🚣🏾‍♀ woman rowing boat: medium-dark skin tone -1F6A3 1F3FF 200D 2640 FE0F ; fully-qualified # 🚣🏿‍♀️ woman rowing boat: dark skin tone -1F6A3 1F3FF 200D 2640 ; non-fully-qualified # 🚣🏿‍♀ woman rowing boat: dark skin tone -1F3CA ; fully-qualified # 🏊 person swimming -1F3CA 1F3FB ; fully-qualified # 🏊🏻 person swimming: light skin tone -1F3CA 1F3FC ; fully-qualified # 🏊🏼 person swimming: medium-light skin tone -1F3CA 1F3FD ; fully-qualified # 🏊🏽 person swimming: medium skin tone -1F3CA 1F3FE ; fully-qualified # 🏊🏾 person swimming: medium-dark skin tone -1F3CA 1F3FF ; fully-qualified # 🏊🏿 person swimming: dark skin tone -1F3CA 200D 2642 FE0F ; fully-qualified # 🏊‍♂️ man swimming -1F3CA 200D 2642 ; non-fully-qualified # 🏊‍♂ man swimming -1F3CA 1F3FB 200D 2642 FE0F ; fully-qualified # 🏊🏻‍♂️ man swimming: light skin tone -1F3CA 1F3FB 200D 2642 ; non-fully-qualified # 🏊🏻‍♂ man swimming: light skin tone -1F3CA 1F3FC 200D 2642 FE0F ; fully-qualified # 🏊🏼‍♂️ man swimming: medium-light skin tone -1F3CA 1F3FC 200D 2642 ; non-fully-qualified # 🏊🏼‍♂ man swimming: medium-light skin tone -1F3CA 1F3FD 200D 2642 FE0F ; fully-qualified # 🏊🏽‍♂️ man swimming: medium skin tone -1F3CA 1F3FD 200D 2642 ; non-fully-qualified # 🏊🏽‍♂ man swimming: medium skin tone -1F3CA 1F3FE 200D 2642 FE0F ; fully-qualified # 🏊🏾‍♂️ man swimming: medium-dark skin tone -1F3CA 1F3FE 200D 2642 ; non-fully-qualified # 🏊🏾‍♂ man swimming: medium-dark skin tone -1F3CA 1F3FF 200D 2642 FE0F ; fully-qualified # 🏊🏿‍♂️ man swimming: dark skin tone -1F3CA 1F3FF 200D 2642 ; non-fully-qualified # 🏊🏿‍♂ man swimming: dark skin tone -1F3CA 200D 2640 FE0F ; fully-qualified # 🏊‍♀️ woman swimming -1F3CA 200D 2640 ; non-fully-qualified # 🏊‍♀ woman swimming -1F3CA 1F3FB 200D 2640 FE0F ; fully-qualified # 🏊🏻‍♀️ woman swimming: light skin tone -1F3CA 1F3FB 200D 2640 ; non-fully-qualified # 🏊🏻‍♀ woman swimming: light skin tone -1F3CA 1F3FC 200D 2640 FE0F ; fully-qualified # 🏊🏼‍♀️ woman swimming: medium-light skin tone -1F3CA 1F3FC 200D 2640 ; non-fully-qualified # 🏊🏼‍♀ woman swimming: medium-light skin tone -1F3CA 1F3FD 200D 2640 FE0F ; fully-qualified # 🏊🏽‍♀️ woman swimming: medium skin tone -1F3CA 1F3FD 200D 2640 ; non-fully-qualified # 🏊🏽‍♀ woman swimming: medium skin tone -1F3CA 1F3FE 200D 2640 FE0F ; fully-qualified # 🏊🏾‍♀️ woman swimming: medium-dark skin tone -1F3CA 1F3FE 200D 2640 ; non-fully-qualified # 🏊🏾‍♀ woman swimming: medium-dark skin tone -1F3CA 1F3FF 200D 2640 FE0F ; fully-qualified # 🏊🏿‍♀️ woman swimming: dark skin tone -1F3CA 1F3FF 200D 2640 ; non-fully-qualified # 🏊🏿‍♀ woman swimming: dark skin tone -26F9 FE0F ; fully-qualified # ⛹️ person bouncing ball -26F9 1F3FB ; fully-qualified # ⛹🏻 person bouncing ball: light skin tone -26F9 1F3FC ; fully-qualified # ⛹🏼 person bouncing ball: medium-light skin tone -26F9 1F3FD ; fully-qualified # ⛹🏽 person bouncing ball: medium skin tone -26F9 1F3FE ; fully-qualified # ⛹🏾 person bouncing ball: medium-dark skin tone -26F9 1F3FF ; fully-qualified # ⛹🏿 person bouncing ball: dark skin tone -26F9 FE0F 200D 2642 FE0F ; fully-qualified # ⛹️‍♂️ man bouncing ball -26F9 200D 2642 ; non-fully-qualified # ⛹‍♂ man bouncing ball -26F9 FE0F 200D 2642 ; non-fully-qualified # ⛹️‍♂ man bouncing ball -26F9 200D 2642 FE0F ; non-fully-qualified # ⛹‍♂️ man bouncing ball -26F9 1F3FB 200D 2642 FE0F ; fully-qualified # ⛹🏻‍♂️ man bouncing ball: light skin tone -26F9 1F3FB 200D 2642 ; non-fully-qualified # ⛹🏻‍♂ man bouncing ball: light skin tone -26F9 1F3FC 200D 2642 FE0F ; fully-qualified # ⛹🏼‍♂️ man bouncing ball: medium-light skin tone -26F9 1F3FC 200D 2642 ; non-fully-qualified # ⛹🏼‍♂ man bouncing ball: medium-light skin tone -26F9 1F3FD 200D 2642 FE0F ; fully-qualified # ⛹🏽‍♂️ man bouncing ball: medium skin tone -26F9 1F3FD 200D 2642 ; non-fully-qualified # ⛹🏽‍♂ man bouncing ball: medium skin tone -26F9 1F3FE 200D 2642 FE0F ; fully-qualified # ⛹🏾‍♂️ man bouncing ball: medium-dark skin tone -26F9 1F3FE 200D 2642 ; non-fully-qualified # ⛹🏾‍♂ man bouncing ball: medium-dark skin tone -26F9 1F3FF 200D 2642 FE0F ; fully-qualified # ⛹🏿‍♂️ man bouncing ball: dark skin tone -26F9 1F3FF 200D 2642 ; non-fully-qualified # ⛹🏿‍♂ man bouncing ball: dark skin tone -26F9 FE0F 200D 2640 FE0F ; fully-qualified # ⛹️‍♀️ woman bouncing ball -26F9 200D 2640 ; non-fully-qualified # ⛹‍♀ woman bouncing ball -26F9 FE0F 200D 2640 ; non-fully-qualified # ⛹️‍♀ woman bouncing ball -26F9 200D 2640 FE0F ; non-fully-qualified # ⛹‍♀️ woman bouncing ball -26F9 1F3FB 200D 2640 FE0F ; fully-qualified # ⛹🏻‍♀️ woman bouncing ball: light skin tone -26F9 1F3FB 200D 2640 ; non-fully-qualified # ⛹🏻‍♀ woman bouncing ball: light skin tone -26F9 1F3FC 200D 2640 FE0F ; fully-qualified # ⛹🏼‍♀️ woman bouncing ball: medium-light skin tone -26F9 1F3FC 200D 2640 ; non-fully-qualified # ⛹🏼‍♀ woman bouncing ball: medium-light skin tone -26F9 1F3FD 200D 2640 FE0F ; fully-qualified # ⛹🏽‍♀️ woman bouncing ball: medium skin tone -26F9 1F3FD 200D 2640 ; non-fully-qualified # ⛹🏽‍♀ woman bouncing ball: medium skin tone -26F9 1F3FE 200D 2640 FE0F ; fully-qualified # ⛹🏾‍♀️ woman bouncing ball: medium-dark skin tone -26F9 1F3FE 200D 2640 ; non-fully-qualified # ⛹🏾‍♀ woman bouncing ball: medium-dark skin tone -26F9 1F3FF 200D 2640 FE0F ; fully-qualified # ⛹🏿‍♀️ woman bouncing ball: dark skin tone -26F9 1F3FF 200D 2640 ; non-fully-qualified # ⛹🏿‍♀ woman bouncing ball: dark skin tone -1F3CB FE0F ; fully-qualified # 🏋️ person lifting weights -1F3CB 1F3FB ; fully-qualified # 🏋🏻 person lifting weights: light skin tone -1F3CB 1F3FC ; fully-qualified # 🏋🏼 person lifting weights: medium-light skin tone -1F3CB 1F3FD ; fully-qualified # 🏋🏽 person lifting weights: medium skin tone -1F3CB 1F3FE ; fully-qualified # 🏋🏾 person lifting weights: medium-dark skin tone -1F3CB 1F3FF ; fully-qualified # 🏋🏿 person lifting weights: dark skin tone -1F3CB FE0F 200D 2642 FE0F ; fully-qualified # 🏋️‍♂️ man lifting weights -1F3CB 200D 2642 ; non-fully-qualified # 🏋‍♂ man lifting weights -1F3CB FE0F 200D 2642 ; non-fully-qualified # 🏋️‍♂ man lifting weights -1F3CB 200D 2642 FE0F ; non-fully-qualified # 🏋‍♂️ man lifting weights -1F3CB 1F3FB 200D 2642 FE0F ; fully-qualified # 🏋🏻‍♂️ man lifting weights: light skin tone -1F3CB 1F3FB 200D 2642 ; non-fully-qualified # 🏋🏻‍♂ man lifting weights: light skin tone -1F3CB 1F3FC 200D 2642 FE0F ; fully-qualified # 🏋🏼‍♂️ man lifting weights: medium-light skin tone -1F3CB 1F3FC 200D 2642 ; non-fully-qualified # 🏋🏼‍♂ man lifting weights: medium-light skin tone -1F3CB 1F3FD 200D 2642 FE0F ; fully-qualified # 🏋🏽‍♂️ man lifting weights: medium skin tone -1F3CB 1F3FD 200D 2642 ; non-fully-qualified # 🏋🏽‍♂ man lifting weights: medium skin tone -1F3CB 1F3FE 200D 2642 FE0F ; fully-qualified # 🏋🏾‍♂️ man lifting weights: medium-dark skin tone -1F3CB 1F3FE 200D 2642 ; non-fully-qualified # 🏋🏾‍♂ man lifting weights: medium-dark skin tone -1F3CB 1F3FF 200D 2642 FE0F ; fully-qualified # 🏋🏿‍♂️ man lifting weights: dark skin tone -1F3CB 1F3FF 200D 2642 ; non-fully-qualified # 🏋🏿‍♂ man lifting weights: dark skin tone -1F3CB FE0F 200D 2640 FE0F ; fully-qualified # 🏋️‍♀️ woman lifting weights -1F3CB 200D 2640 ; non-fully-qualified # 🏋‍♀ woman lifting weights -1F3CB FE0F 200D 2640 ; non-fully-qualified # 🏋️‍♀ woman lifting weights -1F3CB 200D 2640 FE0F ; non-fully-qualified # 🏋‍♀️ woman lifting weights -1F3CB 1F3FB 200D 2640 FE0F ; fully-qualified # 🏋🏻‍♀️ woman lifting weights: light skin tone -1F3CB 1F3FB 200D 2640 ; non-fully-qualified # 🏋🏻‍♀ woman lifting weights: light skin tone -1F3CB 1F3FC 200D 2640 FE0F ; fully-qualified # 🏋🏼‍♀️ woman lifting weights: medium-light skin tone -1F3CB 1F3FC 200D 2640 ; non-fully-qualified # 🏋🏼‍♀ woman lifting weights: medium-light skin tone -1F3CB 1F3FD 200D 2640 FE0F ; fully-qualified # 🏋🏽‍♀️ woman lifting weights: medium skin tone -1F3CB 1F3FD 200D 2640 ; non-fully-qualified # 🏋🏽‍♀ woman lifting weights: medium skin tone -1F3CB 1F3FE 200D 2640 FE0F ; fully-qualified # 🏋🏾‍♀️ woman lifting weights: medium-dark skin tone -1F3CB 1F3FE 200D 2640 ; non-fully-qualified # 🏋🏾‍♀ woman lifting weights: medium-dark skin tone -1F3CB 1F3FF 200D 2640 FE0F ; fully-qualified # 🏋🏿‍♀️ woman lifting weights: dark skin tone -1F3CB 1F3FF 200D 2640 ; non-fully-qualified # 🏋🏿‍♀ woman lifting weights: dark skin tone -1F6B4 ; fully-qualified # 🚴 person biking -1F6B4 1F3FB ; fully-qualified # 🚴🏻 person biking: light skin tone -1F6B4 1F3FC ; fully-qualified # 🚴🏼 person biking: medium-light skin tone -1F6B4 1F3FD ; fully-qualified # 🚴🏽 person biking: medium skin tone -1F6B4 1F3FE ; fully-qualified # 🚴🏾 person biking: medium-dark skin tone -1F6B4 1F3FF ; fully-qualified # 🚴🏿 person biking: dark skin tone -1F6B4 200D 2642 FE0F ; fully-qualified # 🚴‍♂️ man biking -1F6B4 200D 2642 ; non-fully-qualified # 🚴‍♂ man biking -1F6B4 1F3FB 200D 2642 FE0F ; fully-qualified # 🚴🏻‍♂️ man biking: light skin tone -1F6B4 1F3FB 200D 2642 ; non-fully-qualified # 🚴🏻‍♂ man biking: light skin tone -1F6B4 1F3FC 200D 2642 FE0F ; fully-qualified # 🚴🏼‍♂️ man biking: medium-light skin tone -1F6B4 1F3FC 200D 2642 ; non-fully-qualified # 🚴🏼‍♂ man biking: medium-light skin tone -1F6B4 1F3FD 200D 2642 FE0F ; fully-qualified # 🚴🏽‍♂️ man biking: medium skin tone -1F6B4 1F3FD 200D 2642 ; non-fully-qualified # 🚴🏽‍♂ man biking: medium skin tone -1F6B4 1F3FE 200D 2642 FE0F ; fully-qualified # 🚴🏾‍♂️ man biking: medium-dark skin tone -1F6B4 1F3FE 200D 2642 ; non-fully-qualified # 🚴🏾‍♂ man biking: medium-dark skin tone -1F6B4 1F3FF 200D 2642 FE0F ; fully-qualified # 🚴🏿‍♂️ man biking: dark skin tone -1F6B4 1F3FF 200D 2642 ; non-fully-qualified # 🚴🏿‍♂ man biking: dark skin tone -1F6B4 200D 2640 FE0F ; fully-qualified # 🚴‍♀️ woman biking -1F6B4 200D 2640 ; non-fully-qualified # 🚴‍♀ woman biking -1F6B4 1F3FB 200D 2640 FE0F ; fully-qualified # 🚴🏻‍♀️ woman biking: light skin tone -1F6B4 1F3FB 200D 2640 ; non-fully-qualified # 🚴🏻‍♀ woman biking: light skin tone -1F6B4 1F3FC 200D 2640 FE0F ; fully-qualified # 🚴🏼‍♀️ woman biking: medium-light skin tone -1F6B4 1F3FC 200D 2640 ; non-fully-qualified # 🚴🏼‍♀ woman biking: medium-light skin tone -1F6B4 1F3FD 200D 2640 FE0F ; fully-qualified # 🚴🏽‍♀️ woman biking: medium skin tone -1F6B4 1F3FD 200D 2640 ; non-fully-qualified # 🚴🏽‍♀ woman biking: medium skin tone -1F6B4 1F3FE 200D 2640 FE0F ; fully-qualified # 🚴🏾‍♀️ woman biking: medium-dark skin tone -1F6B4 1F3FE 200D 2640 ; non-fully-qualified # 🚴🏾‍♀ woman biking: medium-dark skin tone -1F6B4 1F3FF 200D 2640 FE0F ; fully-qualified # 🚴🏿‍♀️ woman biking: dark skin tone -1F6B4 1F3FF 200D 2640 ; non-fully-qualified # 🚴🏿‍♀ woman biking: dark skin tone -1F6B5 ; fully-qualified # 🚵 person mountain biking -1F6B5 1F3FB ; fully-qualified # 🚵🏻 person mountain biking: light skin tone -1F6B5 1F3FC ; fully-qualified # 🚵🏼 person mountain biking: medium-light skin tone -1F6B5 1F3FD ; fully-qualified # 🚵🏽 person mountain biking: medium skin tone -1F6B5 1F3FE ; fully-qualified # 🚵🏾 person mountain biking: medium-dark skin tone -1F6B5 1F3FF ; fully-qualified # 🚵🏿 person mountain biking: dark skin tone -1F6B5 200D 2642 FE0F ; fully-qualified # 🚵‍♂️ man mountain biking -1F6B5 200D 2642 ; non-fully-qualified # 🚵‍♂ man mountain biking -1F6B5 1F3FB 200D 2642 FE0F ; fully-qualified # 🚵🏻‍♂️ man mountain biking: light skin tone -1F6B5 1F3FB 200D 2642 ; non-fully-qualified # 🚵🏻‍♂ man mountain biking: light skin tone -1F6B5 1F3FC 200D 2642 FE0F ; fully-qualified # 🚵🏼‍♂️ man mountain biking: medium-light skin tone -1F6B5 1F3FC 200D 2642 ; non-fully-qualified # 🚵🏼‍♂ man mountain biking: medium-light skin tone -1F6B5 1F3FD 200D 2642 FE0F ; fully-qualified # 🚵🏽‍♂️ man mountain biking: medium skin tone -1F6B5 1F3FD 200D 2642 ; non-fully-qualified # 🚵🏽‍♂ man mountain biking: medium skin tone -1F6B5 1F3FE 200D 2642 FE0F ; fully-qualified # 🚵🏾‍♂️ man mountain biking: medium-dark skin tone -1F6B5 1F3FE 200D 2642 ; non-fully-qualified # 🚵🏾‍♂ man mountain biking: medium-dark skin tone -1F6B5 1F3FF 200D 2642 FE0F ; fully-qualified # 🚵🏿‍♂️ man mountain biking: dark skin tone -1F6B5 1F3FF 200D 2642 ; non-fully-qualified # 🚵🏿‍♂ man mountain biking: dark skin tone -1F6B5 200D 2640 FE0F ; fully-qualified # 🚵‍♀️ woman mountain biking -1F6B5 200D 2640 ; non-fully-qualified # 🚵‍♀ woman mountain biking -1F6B5 1F3FB 200D 2640 FE0F ; fully-qualified # 🚵🏻‍♀️ woman mountain biking: light skin tone -1F6B5 1F3FB 200D 2640 ; non-fully-qualified # 🚵🏻‍♀ woman mountain biking: light skin tone -1F6B5 1F3FC 200D 2640 FE0F ; fully-qualified # 🚵🏼‍♀️ woman mountain biking: medium-light skin tone -1F6B5 1F3FC 200D 2640 ; non-fully-qualified # 🚵🏼‍♀ woman mountain biking: medium-light skin tone -1F6B5 1F3FD 200D 2640 FE0F ; fully-qualified # 🚵🏽‍♀️ woman mountain biking: medium skin tone -1F6B5 1F3FD 200D 2640 ; non-fully-qualified # 🚵🏽‍♀ woman mountain biking: medium skin tone -1F6B5 1F3FE 200D 2640 FE0F ; fully-qualified # 🚵🏾‍♀️ woman mountain biking: medium-dark skin tone -1F6B5 1F3FE 200D 2640 ; non-fully-qualified # 🚵🏾‍♀ woman mountain biking: medium-dark skin tone -1F6B5 1F3FF 200D 2640 FE0F ; fully-qualified # 🚵🏿‍♀️ woman mountain biking: dark skin tone -1F6B5 1F3FF 200D 2640 ; non-fully-qualified # 🚵🏿‍♀ woman mountain biking: dark skin tone -1F3CE FE0F ; fully-qualified # 🏎️ racing car -1F3CD FE0F ; fully-qualified # 🏍️ motorcycle -1F938 ; fully-qualified # 🤸 person cartwheeling -1F938 1F3FB ; fully-qualified # 🤸🏻 person cartwheeling: light skin tone -1F938 1F3FC ; fully-qualified # 🤸🏼 person cartwheeling: medium-light skin tone -1F938 1F3FD ; fully-qualified # 🤸🏽 person cartwheeling: medium skin tone -1F938 1F3FE ; fully-qualified # 🤸🏾 person cartwheeling: medium-dark skin tone -1F938 1F3FF ; fully-qualified # 🤸🏿 person cartwheeling: dark skin tone -1F938 200D 2642 FE0F ; fully-qualified # 🤸‍♂️ man cartwheeling -1F938 200D 2642 ; non-fully-qualified # 🤸‍♂ man cartwheeling -1F938 1F3FB 200D 2642 FE0F ; fully-qualified # 🤸🏻‍♂️ man cartwheeling: light skin tone -1F938 1F3FB 200D 2642 ; non-fully-qualified # 🤸🏻‍♂ man cartwheeling: light skin tone -1F938 1F3FC 200D 2642 FE0F ; fully-qualified # 🤸🏼‍♂️ man cartwheeling: medium-light skin tone -1F938 1F3FC 200D 2642 ; non-fully-qualified # 🤸🏼‍♂ man cartwheeling: medium-light skin tone -1F938 1F3FD 200D 2642 FE0F ; fully-qualified # 🤸🏽‍♂️ man cartwheeling: medium skin tone -1F938 1F3FD 200D 2642 ; non-fully-qualified # 🤸🏽‍♂ man cartwheeling: medium skin tone -1F938 1F3FE 200D 2642 FE0F ; fully-qualified # 🤸🏾‍♂️ man cartwheeling: medium-dark skin tone -1F938 1F3FE 200D 2642 ; non-fully-qualified # 🤸🏾‍♂ man cartwheeling: medium-dark skin tone -1F938 1F3FF 200D 2642 FE0F ; fully-qualified # 🤸🏿‍♂️ man cartwheeling: dark skin tone -1F938 1F3FF 200D 2642 ; non-fully-qualified # 🤸🏿‍♂ man cartwheeling: dark skin tone -1F938 200D 2640 FE0F ; fully-qualified # 🤸‍♀️ woman cartwheeling -1F938 200D 2640 ; non-fully-qualified # 🤸‍♀ woman cartwheeling -1F938 1F3FB 200D 2640 FE0F ; fully-qualified # 🤸🏻‍♀️ woman cartwheeling: light skin tone -1F938 1F3FB 200D 2640 ; non-fully-qualified # 🤸🏻‍♀ woman cartwheeling: light skin tone -1F938 1F3FC 200D 2640 FE0F ; fully-qualified # 🤸🏼‍♀️ woman cartwheeling: medium-light skin tone -1F938 1F3FC 200D 2640 ; non-fully-qualified # 🤸🏼‍♀ woman cartwheeling: medium-light skin tone -1F938 1F3FD 200D 2640 FE0F ; fully-qualified # 🤸🏽‍♀️ woman cartwheeling: medium skin tone -1F938 1F3FD 200D 2640 ; non-fully-qualified # 🤸🏽‍♀ woman cartwheeling: medium skin tone -1F938 1F3FE 200D 2640 FE0F ; fully-qualified # 🤸🏾‍♀️ woman cartwheeling: medium-dark skin tone -1F938 1F3FE 200D 2640 ; non-fully-qualified # 🤸🏾‍♀ woman cartwheeling: medium-dark skin tone -1F938 1F3FF 200D 2640 FE0F ; fully-qualified # 🤸🏿‍♀️ woman cartwheeling: dark skin tone -1F938 1F3FF 200D 2640 ; non-fully-qualified # 🤸🏿‍♀ woman cartwheeling: dark skin tone -1F93C ; fully-qualified # 🤼 people wrestling -1F93C 200D 2642 FE0F ; fully-qualified # 🤼‍♂️ men wrestling -1F93C 200D 2642 ; non-fully-qualified # 🤼‍♂ men wrestling -1F93C 200D 2640 FE0F ; fully-qualified # 🤼‍♀️ women wrestling -1F93C 200D 2640 ; non-fully-qualified # 🤼‍♀ women wrestling -1F93D ; fully-qualified # 🤽 person playing water polo -1F93D 1F3FB ; fully-qualified # 🤽🏻 person playing water polo: light skin tone -1F93D 1F3FC ; fully-qualified # 🤽🏼 person playing water polo: medium-light skin tone -1F93D 1F3FD ; fully-qualified # 🤽🏽 person playing water polo: medium skin tone -1F93D 1F3FE ; fully-qualified # 🤽🏾 person playing water polo: medium-dark skin tone -1F93D 1F3FF ; fully-qualified # 🤽🏿 person playing water polo: dark skin tone -1F93D 200D 2642 FE0F ; fully-qualified # 🤽‍♂️ man playing water polo -1F93D 200D 2642 ; non-fully-qualified # 🤽‍♂ man playing water polo -1F93D 1F3FB 200D 2642 FE0F ; fully-qualified # 🤽🏻‍♂️ man playing water polo: light skin tone -1F93D 1F3FB 200D 2642 ; non-fully-qualified # 🤽🏻‍♂ man playing water polo: light skin tone -1F93D 1F3FC 200D 2642 FE0F ; fully-qualified # 🤽🏼‍♂️ man playing water polo: medium-light skin tone -1F93D 1F3FC 200D 2642 ; non-fully-qualified # 🤽🏼‍♂ man playing water polo: medium-light skin tone -1F93D 1F3FD 200D 2642 FE0F ; fully-qualified # 🤽🏽‍♂️ man playing water polo: medium skin tone -1F93D 1F3FD 200D 2642 ; non-fully-qualified # 🤽🏽‍♂ man playing water polo: medium skin tone -1F93D 1F3FE 200D 2642 FE0F ; fully-qualified # 🤽🏾‍♂️ man playing water polo: medium-dark skin tone -1F93D 1F3FE 200D 2642 ; non-fully-qualified # 🤽🏾‍♂ man playing water polo: medium-dark skin tone -1F93D 1F3FF 200D 2642 FE0F ; fully-qualified # 🤽🏿‍♂️ man playing water polo: dark skin tone -1F93D 1F3FF 200D 2642 ; non-fully-qualified # 🤽🏿‍♂ man playing water polo: dark skin tone -1F93D 200D 2640 FE0F ; fully-qualified # 🤽‍♀️ woman playing water polo -1F93D 200D 2640 ; non-fully-qualified # 🤽‍♀ woman playing water polo -1F93D 1F3FB 200D 2640 FE0F ; fully-qualified # 🤽🏻‍♀️ woman playing water polo: light skin tone -1F93D 1F3FB 200D 2640 ; non-fully-qualified # 🤽🏻‍♀ woman playing water polo: light skin tone -1F93D 1F3FC 200D 2640 FE0F ; fully-qualified # 🤽🏼‍♀️ woman playing water polo: medium-light skin tone -1F93D 1F3FC 200D 2640 ; non-fully-qualified # 🤽🏼‍♀ woman playing water polo: medium-light skin tone -1F93D 1F3FD 200D 2640 FE0F ; fully-qualified # 🤽🏽‍♀️ woman playing water polo: medium skin tone -1F93D 1F3FD 200D 2640 ; non-fully-qualified # 🤽🏽‍♀ woman playing water polo: medium skin tone -1F93D 1F3FE 200D 2640 FE0F ; fully-qualified # 🤽🏾‍♀️ woman playing water polo: medium-dark skin tone -1F93D 1F3FE 200D 2640 ; non-fully-qualified # 🤽🏾‍♀ woman playing water polo: medium-dark skin tone -1F93D 1F3FF 200D 2640 FE0F ; fully-qualified # 🤽🏿‍♀️ woman playing water polo: dark skin tone -1F93D 1F3FF 200D 2640 ; non-fully-qualified # 🤽🏿‍♀ woman playing water polo: dark skin tone -1F93E ; fully-qualified # 🤾 person playing handball -1F93E 1F3FB ; fully-qualified # 🤾🏻 person playing handball: light skin tone -1F93E 1F3FC ; fully-qualified # 🤾🏼 person playing handball: medium-light skin tone -1F93E 1F3FD ; fully-qualified # 🤾🏽 person playing handball: medium skin tone -1F93E 1F3FE ; fully-qualified # 🤾🏾 person playing handball: medium-dark skin tone -1F93E 1F3FF ; fully-qualified # 🤾🏿 person playing handball: dark skin tone -1F93E 200D 2642 FE0F ; fully-qualified # 🤾‍♂️ man playing handball -1F93E 200D 2642 ; non-fully-qualified # 🤾‍♂ man playing handball -1F93E 1F3FB 200D 2642 FE0F ; fully-qualified # 🤾🏻‍♂️ man playing handball: light skin tone -1F93E 1F3FB 200D 2642 ; non-fully-qualified # 🤾🏻‍♂ man playing handball: light skin tone -1F93E 1F3FC 200D 2642 FE0F ; fully-qualified # 🤾🏼‍♂️ man playing handball: medium-light skin tone -1F93E 1F3FC 200D 2642 ; non-fully-qualified # 🤾🏼‍♂ man playing handball: medium-light skin tone -1F93E 1F3FD 200D 2642 FE0F ; fully-qualified # 🤾🏽‍♂️ man playing handball: medium skin tone -1F93E 1F3FD 200D 2642 ; non-fully-qualified # 🤾🏽‍♂ man playing handball: medium skin tone -1F93E 1F3FE 200D 2642 FE0F ; fully-qualified # 🤾🏾‍♂️ man playing handball: medium-dark skin tone -1F93E 1F3FE 200D 2642 ; non-fully-qualified # 🤾🏾‍♂ man playing handball: medium-dark skin tone -1F93E 1F3FF 200D 2642 FE0F ; fully-qualified # 🤾🏿‍♂️ man playing handball: dark skin tone -1F93E 1F3FF 200D 2642 ; non-fully-qualified # 🤾🏿‍♂ man playing handball: dark skin tone -1F93E 200D 2640 FE0F ; fully-qualified # 🤾‍♀️ woman playing handball -1F93E 200D 2640 ; non-fully-qualified # 🤾‍♀ woman playing handball -1F93E 1F3FB 200D 2640 FE0F ; fully-qualified # 🤾🏻‍♀️ woman playing handball: light skin tone -1F93E 1F3FB 200D 2640 ; non-fully-qualified # 🤾🏻‍♀ woman playing handball: light skin tone -1F93E 1F3FC 200D 2640 FE0F ; fully-qualified # 🤾🏼‍♀️ woman playing handball: medium-light skin tone -1F93E 1F3FC 200D 2640 ; non-fully-qualified # 🤾🏼‍♀ woman playing handball: medium-light skin tone -1F93E 1F3FD 200D 2640 FE0F ; fully-qualified # 🤾🏽‍♀️ woman playing handball: medium skin tone -1F93E 1F3FD 200D 2640 ; non-fully-qualified # 🤾🏽‍♀ woman playing handball: medium skin tone -1F93E 1F3FE 200D 2640 FE0F ; fully-qualified # 🤾🏾‍♀️ woman playing handball: medium-dark skin tone -1F93E 1F3FE 200D 2640 ; non-fully-qualified # 🤾🏾‍♀ woman playing handball: medium-dark skin tone -1F93E 1F3FF 200D 2640 FE0F ; fully-qualified # 🤾🏿‍♀️ woman playing handball: dark skin tone -1F93E 1F3FF 200D 2640 ; non-fully-qualified # 🤾🏿‍♀ woman playing handball: dark skin tone -1F939 ; fully-qualified # 🤹 person juggling -1F939 1F3FB ; fully-qualified # 🤹🏻 person juggling: light skin tone -1F939 1F3FC ; fully-qualified # 🤹🏼 person juggling: medium-light skin tone -1F939 1F3FD ; fully-qualified # 🤹🏽 person juggling: medium skin tone -1F939 1F3FE ; fully-qualified # 🤹🏾 person juggling: medium-dark skin tone -1F939 1F3FF ; fully-qualified # 🤹🏿 person juggling: dark skin tone -1F939 200D 2642 FE0F ; fully-qualified # 🤹‍♂️ man juggling -1F939 200D 2642 ; non-fully-qualified # 🤹‍♂ man juggling -1F939 1F3FB 200D 2642 FE0F ; fully-qualified # 🤹🏻‍♂️ man juggling: light skin tone -1F939 1F3FB 200D 2642 ; non-fully-qualified # 🤹🏻‍♂ man juggling: light skin tone -1F939 1F3FC 200D 2642 FE0F ; fully-qualified # 🤹🏼‍♂️ man juggling: medium-light skin tone -1F939 1F3FC 200D 2642 ; non-fully-qualified # 🤹🏼‍♂ man juggling: medium-light skin tone -1F939 1F3FD 200D 2642 FE0F ; fully-qualified # 🤹🏽‍♂️ man juggling: medium skin tone -1F939 1F3FD 200D 2642 ; non-fully-qualified # 🤹🏽‍♂ man juggling: medium skin tone -1F939 1F3FE 200D 2642 FE0F ; fully-qualified # 🤹🏾‍♂️ man juggling: medium-dark skin tone -1F939 1F3FE 200D 2642 ; non-fully-qualified # 🤹🏾‍♂ man juggling: medium-dark skin tone -1F939 1F3FF 200D 2642 FE0F ; fully-qualified # 🤹🏿‍♂️ man juggling: dark skin tone -1F939 1F3FF 200D 2642 ; non-fully-qualified # 🤹🏿‍♂ man juggling: dark skin tone -1F939 200D 2640 FE0F ; fully-qualified # 🤹‍♀️ woman juggling -1F939 200D 2640 ; non-fully-qualified # 🤹‍♀ woman juggling -1F939 1F3FB 200D 2640 FE0F ; fully-qualified # 🤹🏻‍♀️ woman juggling: light skin tone -1F939 1F3FB 200D 2640 ; non-fully-qualified # 🤹🏻‍♀ woman juggling: light skin tone -1F939 1F3FC 200D 2640 FE0F ; fully-qualified # 🤹🏼‍♀️ woman juggling: medium-light skin tone -1F939 1F3FC 200D 2640 ; non-fully-qualified # 🤹🏼‍♀ woman juggling: medium-light skin tone -1F939 1F3FD 200D 2640 FE0F ; fully-qualified # 🤹🏽‍♀️ woman juggling: medium skin tone -1F939 1F3FD 200D 2640 ; non-fully-qualified # 🤹🏽‍♀ woman juggling: medium skin tone -1F939 1F3FE 200D 2640 FE0F ; fully-qualified # 🤹🏾‍♀️ woman juggling: medium-dark skin tone -1F939 1F3FE 200D 2640 ; non-fully-qualified # 🤹🏾‍♀ woman juggling: medium-dark skin tone -1F939 1F3FF 200D 2640 FE0F ; fully-qualified # 🤹🏿‍♀️ woman juggling: dark skin tone -1F939 1F3FF 200D 2640 ; non-fully-qualified # 🤹🏿‍♀ woman juggling: dark skin tone - -# subgroup: family -1F46B ; fully-qualified # 👫 man and woman holding hands -1F46C ; fully-qualified # 👬 two men holding hands -1F46D ; fully-qualified # 👭 two women holding hands -1F48F ; fully-qualified # 💏 kiss -1F469 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👩‍❤️‍💋‍👨 kiss: woman, man -1F469 200D 2764 200D 1F48B 200D 1F468 ; non-fully-qualified # 👩‍❤‍💋‍👨 kiss: woman, man -1F468 200D 2764 FE0F 200D 1F48B 200D 1F468 ; fully-qualified # 👨‍❤️‍💋‍👨 kiss: man, man -1F468 200D 2764 200D 1F48B 200D 1F468 ; non-fully-qualified # 👨‍❤‍💋‍👨 kiss: man, man -1F469 200D 2764 FE0F 200D 1F48B 200D 1F469 ; fully-qualified # 👩‍❤️‍💋‍👩 kiss: woman, woman -1F469 200D 2764 200D 1F48B 200D 1F469 ; non-fully-qualified # 👩‍❤‍💋‍👩 kiss: woman, woman -1F491 ; fully-qualified # 💑 couple with heart -1F469 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👩‍❤️‍👨 couple with heart: woman, man -1F469 200D 2764 200D 1F468 ; non-fully-qualified # 👩‍❤‍👨 couple with heart: woman, man -1F468 200D 2764 FE0F 200D 1F468 ; fully-qualified # 👨‍❤️‍👨 couple with heart: man, man -1F468 200D 2764 200D 1F468 ; non-fully-qualified # 👨‍❤‍👨 couple with heart: man, man -1F469 200D 2764 FE0F 200D 1F469 ; fully-qualified # 👩‍❤️‍👩 couple with heart: woman, woman -1F469 200D 2764 200D 1F469 ; non-fully-qualified # 👩‍❤‍👩 couple with heart: woman, woman -1F46A ; fully-qualified # 👪 family -1F468 200D 1F469 200D 1F466 ; fully-qualified # 👨‍👩‍👦 family: man, woman, boy -1F468 200D 1F469 200D 1F467 ; fully-qualified # 👨‍👩‍👧 family: man, woman, girl -1F468 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👩‍👧‍👦 family: man, woman, girl, boy -1F468 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👩‍👦‍👦 family: man, woman, boy, boy -1F468 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👩‍👧‍👧 family: man, woman, girl, girl -1F468 200D 1F468 200D 1F466 ; fully-qualified # 👨‍👨‍👦 family: man, man, boy -1F468 200D 1F468 200D 1F467 ; fully-qualified # 👨‍👨‍👧 family: man, man, girl -1F468 200D 1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👨‍👧‍👦 family: man, man, girl, boy -1F468 200D 1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👨‍👦‍👦 family: man, man, boy, boy -1F468 200D 1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👨‍👧‍👧 family: man, man, girl, girl -1F469 200D 1F469 200D 1F466 ; fully-qualified # 👩‍👩‍👦 family: woman, woman, boy -1F469 200D 1F469 200D 1F467 ; fully-qualified # 👩‍👩‍👧 family: woman, woman, girl -1F469 200D 1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩‍👩‍👧‍👦 family: woman, woman, girl, boy -1F469 200D 1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩‍👩‍👦‍👦 family: woman, woman, boy, boy -1F469 200D 1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩‍👩‍👧‍👧 family: woman, woman, girl, girl -1F468 200D 1F466 ; fully-qualified # 👨‍👦 family: man, boy -1F468 200D 1F466 200D 1F466 ; fully-qualified # 👨‍👦‍👦 family: man, boy, boy -1F468 200D 1F467 ; fully-qualified # 👨‍👧 family: man, girl -1F468 200D 1F467 200D 1F466 ; fully-qualified # 👨‍👧‍👦 family: man, girl, boy -1F468 200D 1F467 200D 1F467 ; fully-qualified # 👨‍👧‍👧 family: man, girl, girl -1F469 200D 1F466 ; fully-qualified # 👩‍👦 family: woman, boy -1F469 200D 1F466 200D 1F466 ; fully-qualified # 👩‍👦‍👦 family: woman, boy, boy -1F469 200D 1F467 ; fully-qualified # 👩‍👧 family: woman, girl -1F469 200D 1F467 200D 1F466 ; fully-qualified # 👩‍👧‍👦 family: woman, girl, boy -1F469 200D 1F467 200D 1F467 ; fully-qualified # 👩‍👧‍👧 family: woman, girl, girl - -# subgroup: body -1F4AA ; fully-qualified # 💪 flexed biceps -1F4AA 1F3FB ; fully-qualified # 💪🏻 flexed biceps: light skin tone -1F4AA 1F3FC ; fully-qualified # 💪🏼 flexed biceps: medium-light skin tone -1F4AA 1F3FD ; fully-qualified # 💪🏽 flexed biceps: medium skin tone -1F4AA 1F3FE ; fully-qualified # 💪🏾 flexed biceps: medium-dark skin tone -1F4AA 1F3FF ; fully-qualified # 💪🏿 flexed biceps: dark skin tone -1F933 ; fully-qualified # 🤳 selfie -1F933 1F3FB ; fully-qualified # 🤳🏻 selfie: light skin tone -1F933 1F3FC ; fully-qualified # 🤳🏼 selfie: medium-light skin tone -1F933 1F3FD ; fully-qualified # 🤳🏽 selfie: medium skin tone -1F933 1F3FE ; fully-qualified # 🤳🏾 selfie: medium-dark skin tone -1F933 1F3FF ; fully-qualified # 🤳🏿 selfie: dark skin tone -1F448 ; fully-qualified # 👈 backhand index pointing left -1F448 1F3FB ; fully-qualified # 👈🏻 backhand index pointing left: light skin tone -1F448 1F3FC ; fully-qualified # 👈🏼 backhand index pointing left: medium-light skin tone -1F448 1F3FD ; fully-qualified # 👈🏽 backhand index pointing left: medium skin tone -1F448 1F3FE ; fully-qualified # 👈🏾 backhand index pointing left: medium-dark skin tone -1F448 1F3FF ; fully-qualified # 👈🏿 backhand index pointing left: dark skin tone -1F449 ; fully-qualified # 👉 backhand index pointing right -1F449 1F3FB ; fully-qualified # 👉🏻 backhand index pointing right: light skin tone -1F449 1F3FC ; fully-qualified # 👉🏼 backhand index pointing right: medium-light skin tone -1F449 1F3FD ; fully-qualified # 👉🏽 backhand index pointing right: medium skin tone -1F449 1F3FE ; fully-qualified # 👉🏾 backhand index pointing right: medium-dark skin tone -1F449 1F3FF ; fully-qualified # 👉🏿 backhand index pointing right: dark skin tone -261D FE0F ; fully-qualified # ☝️ index pointing up -261D 1F3FB ; fully-qualified # ☝🏻 index pointing up: light skin tone -261D 1F3FC ; fully-qualified # ☝🏼 index pointing up: medium-light skin tone -261D 1F3FD ; fully-qualified # ☝🏽 index pointing up: medium skin tone -261D 1F3FE ; fully-qualified # ☝🏾 index pointing up: medium-dark skin tone -261D 1F3FF ; fully-qualified # ☝🏿 index pointing up: dark skin tone -1F446 ; fully-qualified # 👆 backhand index pointing up -1F446 1F3FB ; fully-qualified # 👆🏻 backhand index pointing up: light skin tone -1F446 1F3FC ; fully-qualified # 👆🏼 backhand index pointing up: medium-light skin tone -1F446 1F3FD ; fully-qualified # 👆🏽 backhand index pointing up: medium skin tone -1F446 1F3FE ; fully-qualified # 👆🏾 backhand index pointing up: medium-dark skin tone -1F446 1F3FF ; fully-qualified # 👆🏿 backhand index pointing up: dark skin tone -1F595 ; fully-qualified # 🖕 middle finger -1F595 1F3FB ; fully-qualified # 🖕🏻 middle finger: light skin tone -1F595 1F3FC ; fully-qualified # 🖕🏼 middle finger: medium-light skin tone -1F595 1F3FD ; fully-qualified # 🖕🏽 middle finger: medium skin tone -1F595 1F3FE ; fully-qualified # 🖕🏾 middle finger: medium-dark skin tone -1F595 1F3FF ; fully-qualified # 🖕🏿 middle finger: dark skin tone -1F447 ; fully-qualified # 👇 backhand index pointing down -1F447 1F3FB ; fully-qualified # 👇🏻 backhand index pointing down: light skin tone -1F447 1F3FC ; fully-qualified # 👇🏼 backhand index pointing down: medium-light skin tone -1F447 1F3FD ; fully-qualified # 👇🏽 backhand index pointing down: medium skin tone -1F447 1F3FE ; fully-qualified # 👇🏾 backhand index pointing down: medium-dark skin tone -1F447 1F3FF ; fully-qualified # 👇🏿 backhand index pointing down: dark skin tone -270C FE0F ; fully-qualified # ✌️ victory hand -270C 1F3FB ; fully-qualified # ✌🏻 victory hand: light skin tone -270C 1F3FC ; fully-qualified # ✌🏼 victory hand: medium-light skin tone -270C 1F3FD ; fully-qualified # ✌🏽 victory hand: medium skin tone -270C 1F3FE ; fully-qualified # ✌🏾 victory hand: medium-dark skin tone -270C 1F3FF ; fully-qualified # ✌🏿 victory hand: dark skin tone -1F91E ; fully-qualified # 🤞 crossed fingers -1F91E 1F3FB ; fully-qualified # 🤞🏻 crossed fingers: light skin tone -1F91E 1F3FC ; fully-qualified # 🤞🏼 crossed fingers: medium-light skin tone -1F91E 1F3FD ; fully-qualified # 🤞🏽 crossed fingers: medium skin tone -1F91E 1F3FE ; fully-qualified # 🤞🏾 crossed fingers: medium-dark skin tone -1F91E 1F3FF ; fully-qualified # 🤞🏿 crossed fingers: dark skin tone -1F596 ; fully-qualified # 🖖 vulcan salute -1F596 1F3FB ; fully-qualified # 🖖🏻 vulcan salute: light skin tone -1F596 1F3FC ; fully-qualified # 🖖🏼 vulcan salute: medium-light skin tone -1F596 1F3FD ; fully-qualified # 🖖🏽 vulcan salute: medium skin tone -1F596 1F3FE ; fully-qualified # 🖖🏾 vulcan salute: medium-dark skin tone -1F596 1F3FF ; fully-qualified # 🖖🏿 vulcan salute: dark skin tone -1F918 ; fully-qualified # 🤘 sign of the horns -1F918 1F3FB ; fully-qualified # 🤘🏻 sign of the horns: light skin tone -1F918 1F3FC ; fully-qualified # 🤘🏼 sign of the horns: medium-light skin tone -1F918 1F3FD ; fully-qualified # 🤘🏽 sign of the horns: medium skin tone -1F918 1F3FE ; fully-qualified # 🤘🏾 sign of the horns: medium-dark skin tone -1F918 1F3FF ; fully-qualified # 🤘🏿 sign of the horns: dark skin tone -1F919 ; fully-qualified # 🤙 call me hand -1F919 1F3FB ; fully-qualified # 🤙🏻 call me hand: light skin tone -1F919 1F3FC ; fully-qualified # 🤙🏼 call me hand: medium-light skin tone -1F919 1F3FD ; fully-qualified # 🤙🏽 call me hand: medium skin tone -1F919 1F3FE ; fully-qualified # 🤙🏾 call me hand: medium-dark skin tone -1F919 1F3FF ; fully-qualified # 🤙🏿 call me hand: dark skin tone -1F590 FE0F ; fully-qualified # 🖐️ raised hand with fingers splayed -1F590 1F3FB ; fully-qualified # 🖐🏻 raised hand with fingers splayed: light skin tone -1F590 1F3FC ; fully-qualified # 🖐🏼 raised hand with fingers splayed: medium-light skin tone -1F590 1F3FD ; fully-qualified # 🖐🏽 raised hand with fingers splayed: medium skin tone -1F590 1F3FE ; fully-qualified # 🖐🏾 raised hand with fingers splayed: medium-dark skin tone -1F590 1F3FF ; fully-qualified # 🖐🏿 raised hand with fingers splayed: dark skin tone -270B ; fully-qualified # ✋ raised hand -270B 1F3FB ; fully-qualified # ✋🏻 raised hand: light skin tone -270B 1F3FC ; fully-qualified # ✋🏼 raised hand: medium-light skin tone -270B 1F3FD ; fully-qualified # ✋🏽 raised hand: medium skin tone -270B 1F3FE ; fully-qualified # ✋🏾 raised hand: medium-dark skin tone -270B 1F3FF ; fully-qualified # ✋🏿 raised hand: dark skin tone -1F44C ; fully-qualified # 👌 OK hand -1F44C 1F3FB ; fully-qualified # 👌🏻 OK hand: light skin tone -1F44C 1F3FC ; fully-qualified # 👌🏼 OK hand: medium-light skin tone -1F44C 1F3FD ; fully-qualified # 👌🏽 OK hand: medium skin tone -1F44C 1F3FE ; fully-qualified # 👌🏾 OK hand: medium-dark skin tone -1F44C 1F3FF ; fully-qualified # 👌🏿 OK hand: dark skin tone -1F44D ; fully-qualified # 👍 thumbs up -1F44D 1F3FB ; fully-qualified # 👍🏻 thumbs up: light skin tone -1F44D 1F3FC ; fully-qualified # 👍🏼 thumbs up: medium-light skin tone -1F44D 1F3FD ; fully-qualified # 👍🏽 thumbs up: medium skin tone -1F44D 1F3FE ; fully-qualified # 👍🏾 thumbs up: medium-dark skin tone -1F44D 1F3FF ; fully-qualified # 👍🏿 thumbs up: dark skin tone -1F44E ; fully-qualified # 👎 thumbs down -1F44E 1F3FB ; fully-qualified # 👎🏻 thumbs down: light skin tone -1F44E 1F3FC ; fully-qualified # 👎🏼 thumbs down: medium-light skin tone -1F44E 1F3FD ; fully-qualified # 👎🏽 thumbs down: medium skin tone -1F44E 1F3FE ; fully-qualified # 👎🏾 thumbs down: medium-dark skin tone -1F44E 1F3FF ; fully-qualified # 👎🏿 thumbs down: dark skin tone -270A ; fully-qualified # ✊ raised fist -270A 1F3FB ; fully-qualified # ✊🏻 raised fist: light skin tone -270A 1F3FC ; fully-qualified # ✊🏼 raised fist: medium-light skin tone -270A 1F3FD ; fully-qualified # ✊🏽 raised fist: medium skin tone -270A 1F3FE ; fully-qualified # ✊🏾 raised fist: medium-dark skin tone -270A 1F3FF ; fully-qualified # ✊🏿 raised fist: dark skin tone -1F44A ; fully-qualified # 👊 oncoming fist -1F44A 1F3FB ; fully-qualified # 👊🏻 oncoming fist: light skin tone -1F44A 1F3FC ; fully-qualified # 👊🏼 oncoming fist: medium-light skin tone -1F44A 1F3FD ; fully-qualified # 👊🏽 oncoming fist: medium skin tone -1F44A 1F3FE ; fully-qualified # 👊🏾 oncoming fist: medium-dark skin tone -1F44A 1F3FF ; fully-qualified # 👊🏿 oncoming fist: dark skin tone -1F91B ; fully-qualified # 🤛 left-facing fist -1F91B 1F3FB ; fully-qualified # 🤛🏻 left-facing fist: light skin tone -1F91B 1F3FC ; fully-qualified # 🤛🏼 left-facing fist: medium-light skin tone -1F91B 1F3FD ; fully-qualified # 🤛🏽 left-facing fist: medium skin tone -1F91B 1F3FE ; fully-qualified # 🤛🏾 left-facing fist: medium-dark skin tone -1F91B 1F3FF ; fully-qualified # 🤛🏿 left-facing fist: dark skin tone -1F91C ; fully-qualified # 🤜 right-facing fist -1F91C 1F3FB ; fully-qualified # 🤜🏻 right-facing fist: light skin tone -1F91C 1F3FC ; fully-qualified # 🤜🏼 right-facing fist: medium-light skin tone -1F91C 1F3FD ; fully-qualified # 🤜🏽 right-facing fist: medium skin tone -1F91C 1F3FE ; fully-qualified # 🤜🏾 right-facing fist: medium-dark skin tone -1F91C 1F3FF ; fully-qualified # 🤜🏿 right-facing fist: dark skin tone -1F91A ; fully-qualified # 🤚 raised back of hand -1F91A 1F3FB ; fully-qualified # 🤚🏻 raised back of hand: light skin tone -1F91A 1F3FC ; fully-qualified # 🤚🏼 raised back of hand: medium-light skin tone -1F91A 1F3FD ; fully-qualified # 🤚🏽 raised back of hand: medium skin tone -1F91A 1F3FE ; fully-qualified # 🤚🏾 raised back of hand: medium-dark skin tone -1F91A 1F3FF ; fully-qualified # 🤚🏿 raised back of hand: dark skin tone -1F44B ; fully-qualified # 👋 waving hand -1F44B 1F3FB ; fully-qualified # 👋🏻 waving hand: light skin tone -1F44B 1F3FC ; fully-qualified # 👋🏼 waving hand: medium-light skin tone -1F44B 1F3FD ; fully-qualified # 👋🏽 waving hand: medium skin tone -1F44B 1F3FE ; fully-qualified # 👋🏾 waving hand: medium-dark skin tone -1F44B 1F3FF ; fully-qualified # 👋🏿 waving hand: dark skin tone -1F44F ; fully-qualified # 👏 clapping hands -1F44F 1F3FB ; fully-qualified # 👏🏻 clapping hands: light skin tone -1F44F 1F3FC ; fully-qualified # 👏🏼 clapping hands: medium-light skin tone -1F44F 1F3FD ; fully-qualified # 👏🏽 clapping hands: medium skin tone -1F44F 1F3FE ; fully-qualified # 👏🏾 clapping hands: medium-dark skin tone -1F44F 1F3FF ; fully-qualified # 👏🏿 clapping hands: dark skin tone -270D FE0F ; fully-qualified # ✍️ writing hand -270D 1F3FB ; fully-qualified # ✍🏻 writing hand: light skin tone -270D 1F3FC ; fully-qualified # ✍🏼 writing hand: medium-light skin tone -270D 1F3FD ; fully-qualified # ✍🏽 writing hand: medium skin tone -270D 1F3FE ; fully-qualified # ✍🏾 writing hand: medium-dark skin tone -270D 1F3FF ; fully-qualified # ✍🏿 writing hand: dark skin tone -1F450 ; fully-qualified # 👐 open hands -1F450 1F3FB ; fully-qualified # 👐🏻 open hands: light skin tone -1F450 1F3FC ; fully-qualified # 👐🏼 open hands: medium-light skin tone -1F450 1F3FD ; fully-qualified # 👐🏽 open hands: medium skin tone -1F450 1F3FE ; fully-qualified # 👐🏾 open hands: medium-dark skin tone -1F450 1F3FF ; fully-qualified # 👐🏿 open hands: dark skin tone -1F64C ; fully-qualified # 🙌 raising hands -1F64C 1F3FB ; fully-qualified # 🙌🏻 raising hands: light skin tone -1F64C 1F3FC ; fully-qualified # 🙌🏼 raising hands: medium-light skin tone -1F64C 1F3FD ; fully-qualified # 🙌🏽 raising hands: medium skin tone -1F64C 1F3FE ; fully-qualified # 🙌🏾 raising hands: medium-dark skin tone -1F64C 1F3FF ; fully-qualified # 🙌🏿 raising hands: dark skin tone -1F64F ; fully-qualified # 🙏 folded hands -1F64F 1F3FB ; fully-qualified # 🙏🏻 folded hands: light skin tone -1F64F 1F3FC ; fully-qualified # 🙏🏼 folded hands: medium-light skin tone -1F64F 1F3FD ; fully-qualified # 🙏🏽 folded hands: medium skin tone -1F64F 1F3FE ; fully-qualified # 🙏🏾 folded hands: medium-dark skin tone -1F64F 1F3FF ; fully-qualified # 🙏🏿 folded hands: dark skin tone -1F91D ; fully-qualified # 🤝 handshake -1F485 ; fully-qualified # 💅 nail polish -1F485 1F3FB ; fully-qualified # 💅🏻 nail polish: light skin tone -1F485 1F3FC ; fully-qualified # 💅🏼 nail polish: medium-light skin tone -1F485 1F3FD ; fully-qualified # 💅🏽 nail polish: medium skin tone -1F485 1F3FE ; fully-qualified # 💅🏾 nail polish: medium-dark skin tone -1F485 1F3FF ; fully-qualified # 💅🏿 nail polish: dark skin tone -1F442 ; fully-qualified # 👂 ear -1F442 1F3FB ; fully-qualified # 👂🏻 ear: light skin tone -1F442 1F3FC ; fully-qualified # 👂🏼 ear: medium-light skin tone -1F442 1F3FD ; fully-qualified # 👂🏽 ear: medium skin tone -1F442 1F3FE ; fully-qualified # 👂🏾 ear: medium-dark skin tone -1F442 1F3FF ; fully-qualified # 👂🏿 ear: dark skin tone -1F443 ; fully-qualified # 👃 nose -1F443 1F3FB ; fully-qualified # 👃🏻 nose: light skin tone -1F443 1F3FC ; fully-qualified # 👃🏼 nose: medium-light skin tone -1F443 1F3FD ; fully-qualified # 👃🏽 nose: medium skin tone -1F443 1F3FE ; fully-qualified # 👃🏾 nose: medium-dark skin tone -1F443 1F3FF ; fully-qualified # 👃🏿 nose: dark skin tone -1F463 ; fully-qualified # 👣 footprints -1F440 ; fully-qualified # 👀 eyes -1F441 FE0F ; fully-qualified # 👁️ eye -1F441 FE0F 200D 1F5E8 FE0F ; fully-qualified # 👁️‍🗨️ eye in speech bubble -1F441 200D 1F5E8 ; non-fully-qualified # 👁‍🗨 eye in speech bubble -1F441 FE0F 200D 1F5E8 ; non-fully-qualified # 👁️‍🗨 eye in speech bubble -1F441 200D 1F5E8 FE0F ; non-fully-qualified # 👁‍🗨️ eye in speech bubble -1F445 ; fully-qualified # 👅 tongue -1F444 ; fully-qualified # 👄 mouth - -# subgroup: emotion -1F48B ; fully-qualified # 💋 kiss mark -1F498 ; fully-qualified # 💘 heart with arrow -2764 FE0F ; fully-qualified # ❤️ red heart -1F493 ; fully-qualified # 💓 beating heart -1F494 ; fully-qualified # 💔 broken heart -1F495 ; fully-qualified # 💕 two hearts -1F496 ; fully-qualified # 💖 sparkling heart -1F497 ; fully-qualified # 💗 growing heart -1F499 ; fully-qualified # 💙 blue heart -1F49A ; fully-qualified # 💚 green heart -1F49B ; fully-qualified # 💛 yellow heart -1F49C ; fully-qualified # 💜 purple heart -1F5A4 ; fully-qualified # 🖤 black heart -1F49D ; fully-qualified # 💝 heart with ribbon -1F49E ; fully-qualified # 💞 revolving hearts -1F49F ; fully-qualified # 💟 heart decoration -2763 FE0F ; fully-qualified # ❣️ heavy heart exclamation -1F48C ; fully-qualified # 💌 love letter -1F4A4 ; fully-qualified # 💤 zzz -1F4A2 ; fully-qualified # 💢 anger symbol -1F4A3 ; fully-qualified # 💣 bomb -1F4A5 ; fully-qualified # 💥 collision -1F4A6 ; fully-qualified # 💦 sweat droplets -1F4A8 ; fully-qualified # 💨 dashing away -1F4AB ; fully-qualified # 💫 dizzy -1F4AC ; fully-qualified # 💬 speech balloon -1F5E8 FE0F ; fully-qualified # 🗨️ left speech bubble -1F5EF FE0F ; fully-qualified # 🗯️ right anger bubble -1F4AD ; fully-qualified # 💭 thought balloon -1F573 FE0F ; fully-qualified # 🕳️ hole - -# subgroup: clothing -1F453 ; fully-qualified # 👓 glasses -1F576 FE0F ; fully-qualified # 🕶️ sunglasses -1F454 ; fully-qualified # 👔 necktie -1F455 ; fully-qualified # 👕 t-shirt -1F456 ; fully-qualified # 👖 jeans -1F457 ; fully-qualified # 👗 dress -1F458 ; fully-qualified # 👘 kimono -1F459 ; fully-qualified # 👙 bikini -1F45A ; fully-qualified # 👚 woman’s clothes -1F45B ; fully-qualified # 👛 purse -1F45C ; fully-qualified # 👜 handbag -1F45D ; fully-qualified # 👝 clutch bag -1F6CD FE0F ; fully-qualified # 🛍️ shopping bags -1F392 ; fully-qualified # 🎒 school backpack -1F45E ; fully-qualified # 👞 man’s shoe -1F45F ; fully-qualified # 👟 running shoe -1F460 ; fully-qualified # 👠 high-heeled shoe -1F461 ; fully-qualified # 👡 woman’s sandal -1F462 ; fully-qualified # 👢 woman’s boot -1F451 ; fully-qualified # 👑 crown -1F452 ; fully-qualified # 👒 woman’s hat -1F3A9 ; fully-qualified # 🎩 top hat -1F393 ; fully-qualified # 🎓 graduation cap -26D1 FE0F ; fully-qualified # ⛑️ rescue worker’s helmet -1F4FF ; fully-qualified # 📿 prayer beads -1F484 ; fully-qualified # 💄 lipstick -1F48D ; fully-qualified # 💍 ring -1F48E ; fully-qualified # 💎 gem stone - -# Smileys & People subtotal: 1281 -# Smileys & People subtotal: 391 w/o modifiers - -# group: Animals & Nature - -# subgroup: animal-mammal -1F435 ; fully-qualified # 🐵 monkey face -1F412 ; fully-qualified # 🐒 monkey -1F98D ; fully-qualified # 🦍 gorilla -1F436 ; fully-qualified # 🐶 dog face -1F415 ; fully-qualified # 🐕 dog -1F429 ; fully-qualified # 🐩 poodle -1F43A ; fully-qualified # 🐺 wolf face -1F98A ; fully-qualified # 🦊 fox face -1F431 ; fully-qualified # 🐱 cat face -1F408 ; fully-qualified # 🐈 cat -1F981 ; fully-qualified # 🦁 lion face -1F42F ; fully-qualified # 🐯 tiger face -1F405 ; fully-qualified # 🐅 tiger -1F406 ; fully-qualified # 🐆 leopard -1F434 ; fully-qualified # 🐴 horse face -1F40E ; fully-qualified # 🐎 horse -1F98C ; fully-qualified # 🦌 deer -1F984 ; fully-qualified # 🦄 unicorn face -1F42E ; fully-qualified # 🐮 cow face -1F402 ; fully-qualified # 🐂 ox -1F403 ; fully-qualified # 🐃 water buffalo -1F404 ; fully-qualified # 🐄 cow -1F437 ; fully-qualified # 🐷 pig face -1F416 ; fully-qualified # 🐖 pig -1F417 ; fully-qualified # 🐗 boar -1F43D ; fully-qualified # 🐽 pig nose -1F40F ; fully-qualified # 🐏 ram -1F411 ; fully-qualified # 🐑 sheep -1F410 ; fully-qualified # 🐐 goat -1F42A ; fully-qualified # 🐪 camel -1F42B ; fully-qualified # 🐫 two-hump camel -1F418 ; fully-qualified # 🐘 elephant -1F98F ; fully-qualified # 🦏 rhinoceros -1F42D ; fully-qualified # 🐭 mouse face -1F401 ; fully-qualified # 🐁 mouse -1F400 ; fully-qualified # 🐀 rat -1F439 ; fully-qualified # 🐹 hamster face -1F430 ; fully-qualified # 🐰 rabbit face -1F407 ; fully-qualified # 🐇 rabbit -1F43F FE0F ; fully-qualified # 🐿️ chipmunk -1F987 ; fully-qualified # 🦇 bat -1F43B ; fully-qualified # 🐻 bear face -1F428 ; fully-qualified # 🐨 koala -1F43C ; fully-qualified # 🐼 panda face -1F43E ; fully-qualified # 🐾 paw prints - -# subgroup: animal-bird -1F983 ; fully-qualified # 🦃 turkey -1F414 ; fully-qualified # 🐔 chicken -1F413 ; fully-qualified # 🐓 rooster -1F423 ; fully-qualified # 🐣 hatching chick -1F424 ; fully-qualified # 🐤 baby chick -1F425 ; fully-qualified # 🐥 front-facing baby chick -1F426 ; fully-qualified # 🐦 bird -1F427 ; fully-qualified # 🐧 penguin -1F54A FE0F ; fully-qualified # 🕊️ dove -1F985 ; fully-qualified # 🦅 eagle -1F986 ; fully-qualified # 🦆 duck -1F989 ; fully-qualified # 🦉 owl - -# subgroup: animal-amphibian -1F438 ; fully-qualified # 🐸 frog face - -# subgroup: animal-reptile -1F40A ; fully-qualified # 🐊 crocodile -1F422 ; fully-qualified # 🐢 turtle -1F98E ; fully-qualified # 🦎 lizard -1F40D ; fully-qualified # 🐍 snake -1F432 ; fully-qualified # 🐲 dragon face -1F409 ; fully-qualified # 🐉 dragon - -# subgroup: animal-marine -1F433 ; fully-qualified # 🐳 spouting whale -1F40B ; fully-qualified # 🐋 whale -1F42C ; fully-qualified # 🐬 dolphin -1F41F ; fully-qualified # 🐟 fish -1F420 ; fully-qualified # 🐠 tropical fish -1F421 ; fully-qualified # 🐡 blowfish -1F988 ; fully-qualified # 🦈 shark -1F419 ; fully-qualified # 🐙 octopus -1F41A ; fully-qualified # 🐚 spiral shell -1F980 ; fully-qualified # 🦀 crab -1F990 ; fully-qualified # 🦐 shrimp -1F991 ; fully-qualified # 🦑 squid - -# subgroup: animal-bug -1F98B ; fully-qualified # 🦋 butterfly -1F40C ; fully-qualified # 🐌 snail -1F41B ; fully-qualified # 🐛 bug -1F41C ; fully-qualified # 🐜 ant -1F41D ; fully-qualified # 🐝 honeybee -1F41E ; fully-qualified # 🐞 lady beetle -1F577 FE0F ; fully-qualified # 🕷️ spider -1F578 FE0F ; fully-qualified # 🕸️ spider web -1F982 ; fully-qualified # 🦂 scorpion - -# subgroup: plant-flower -1F490 ; fully-qualified # 💐 bouquet -1F338 ; fully-qualified # 🌸 cherry blossom -1F4AE ; fully-qualified # 💮 white flower -1F3F5 FE0F ; fully-qualified # 🏵️ rosette -1F339 ; fully-qualified # 🌹 rose -1F940 ; fully-qualified # 🥀 wilted flower -1F33A ; fully-qualified # 🌺 hibiscus -1F33B ; fully-qualified # 🌻 sunflower -1F33C ; fully-qualified # 🌼 blossom -1F337 ; fully-qualified # 🌷 tulip - -# subgroup: plant-other -1F331 ; fully-qualified # 🌱 seedling -1F332 ; fully-qualified # 🌲 evergreen tree -1F333 ; fully-qualified # 🌳 deciduous tree -1F334 ; fully-qualified # 🌴 palm tree -1F335 ; fully-qualified # 🌵 cactus -1F33E ; fully-qualified # 🌾 sheaf of rice -1F33F ; fully-qualified # 🌿 herb -2618 FE0F ; fully-qualified # ☘️ shamrock -1F340 ; fully-qualified # 🍀 four leaf clover -1F341 ; fully-qualified # 🍁 maple leaf -1F342 ; fully-qualified # 🍂 fallen leaf -1F343 ; fully-qualified # 🍃 leaf fluttering in wind - -# Animals & Nature subtotal: 107 -# Animals & Nature subtotal: 107 w/o modifiers - -# group: Food & Drink - -# subgroup: food-fruit -1F347 ; fully-qualified # 🍇 grapes -1F348 ; fully-qualified # 🍈 melon -1F349 ; fully-qualified # 🍉 watermelon -1F34A ; fully-qualified # 🍊 tangerine -1F34B ; fully-qualified # 🍋 lemon -1F34C ; fully-qualified # 🍌 banana -1F34D ; fully-qualified # 🍍 pineapple -1F34E ; fully-qualified # 🍎 red apple -1F34F ; fully-qualified # 🍏 green apple -1F350 ; fully-qualified # 🍐 pear -1F351 ; fully-qualified # 🍑 peach -1F352 ; fully-qualified # 🍒 cherries -1F353 ; fully-qualified # 🍓 strawberry -1F95D ; fully-qualified # 🥝 kiwi fruit -1F345 ; fully-qualified # 🍅 tomato - -# subgroup: food-vegetable -1F951 ; fully-qualified # 🥑 avocado -1F346 ; fully-qualified # 🍆 eggplant -1F954 ; fully-qualified # 🥔 potato -1F955 ; fully-qualified # 🥕 carrot -1F33D ; fully-qualified # 🌽 ear of corn -1F336 FE0F ; fully-qualified # 🌶️ hot pepper -1F952 ; fully-qualified # 🥒 cucumber -1F344 ; fully-qualified # 🍄 mushroom -1F95C ; fully-qualified # 🥜 peanuts -1F330 ; fully-qualified # 🌰 chestnut - -# subgroup: food-prepared -1F35E ; fully-qualified # 🍞 bread -1F950 ; fully-qualified # 🥐 croissant -1F956 ; fully-qualified # 🥖 baguette bread -1F95E ; fully-qualified # 🥞 pancakes -1F9C0 ; fully-qualified # 🧀 cheese wedge -1F356 ; fully-qualified # 🍖 meat on bone -1F357 ; fully-qualified # 🍗 poultry leg -1F953 ; fully-qualified # 🥓 bacon -1F354 ; fully-qualified # 🍔 hamburger -1F35F ; fully-qualified # 🍟 french fries -1F355 ; fully-qualified # 🍕 pizza -1F32D ; fully-qualified # 🌭 hot dog -1F32E ; fully-qualified # 🌮 taco -1F32F ; fully-qualified # 🌯 burrito -1F959 ; fully-qualified # 🥙 stuffed flatbread -1F95A ; fully-qualified # 🥚 egg -1F373 ; fully-qualified # 🍳 cooking -1F958 ; fully-qualified # 🥘 shallow pan of food -1F372 ; fully-qualified # 🍲 pot of food -1F957 ; fully-qualified # 🥗 green salad -1F37F ; fully-qualified # 🍿 popcorn - -# subgroup: food-asian -1F371 ; fully-qualified # 🍱 bento box -1F358 ; fully-qualified # 🍘 rice cracker -1F359 ; fully-qualified # 🍙 rice ball -1F35A ; fully-qualified # 🍚 cooked rice -1F35B ; fully-qualified # 🍛 curry rice -1F35C ; fully-qualified # 🍜 steaming bowl -1F35D ; fully-qualified # 🍝 spaghetti -1F360 ; fully-qualified # 🍠 roasted sweet potato -1F362 ; fully-qualified # 🍢 oden -1F363 ; fully-qualified # 🍣 sushi -1F364 ; fully-qualified # 🍤 fried shrimp -1F365 ; fully-qualified # 🍥 fish cake with swirl -1F361 ; fully-qualified # 🍡 dango - -# subgroup: food-sweet -1F366 ; fully-qualified # 🍦 soft ice cream -1F367 ; fully-qualified # 🍧 shaved ice -1F368 ; fully-qualified # 🍨 ice cream -1F369 ; fully-qualified # 🍩 doughnut -1F36A ; fully-qualified # 🍪 cookie -1F382 ; fully-qualified # 🎂 birthday cake -1F370 ; fully-qualified # 🍰 shortcake -1F36B ; fully-qualified # 🍫 chocolate bar -1F36C ; fully-qualified # 🍬 candy -1F36D ; fully-qualified # 🍭 lollipop -1F36E ; fully-qualified # 🍮 custard -1F36F ; fully-qualified # 🍯 honey pot - -# subgroup: drink -1F37C ; fully-qualified # 🍼 baby bottle -1F95B ; fully-qualified # 🥛 glass of milk -2615 ; fully-qualified # ☕ hot beverage -1F375 ; fully-qualified # 🍵 teacup without handle -1F376 ; fully-qualified # 🍶 sake -1F37E ; fully-qualified # 🍾 bottle with popping cork -1F377 ; fully-qualified # 🍷 wine glass -1F378 ; fully-qualified # 🍸 cocktail glass -1F379 ; fully-qualified # 🍹 tropical drink -1F37A ; fully-qualified # 🍺 beer mug -1F37B ; fully-qualified # 🍻 clinking beer mugs -1F942 ; fully-qualified # 🥂 clinking glasses -1F943 ; fully-qualified # 🥃 tumbler glass - -# subgroup: dishware -1F37D FE0F ; fully-qualified # 🍽️ fork and knife with plate -1F374 ; fully-qualified # 🍴 fork and knife -1F944 ; fully-qualified # 🥄 spoon -1F52A ; fully-qualified # 🔪 kitchen knife -1F3FA ; fully-qualified # 🏺 amphora - -# Food & Drink subtotal: 89 -# Food & Drink subtotal: 89 w/o modifiers - -# group: Travel & Places - -# subgroup: place-map -1F30D ; fully-qualified # 🌍 globe showing Europe-Africa -1F30E ; fully-qualified # 🌎 globe showing Americas -1F30F ; fully-qualified # 🌏 globe showing Asia-Australia -1F310 ; fully-qualified # 🌐 globe with meridians -1F5FA FE0F ; fully-qualified # 🗺️ world map -1F5FE ; fully-qualified # 🗾 map of Japan - -# subgroup: place-geographic -1F3D4 FE0F ; fully-qualified # 🏔️ snow-capped mountain -26F0 FE0F ; fully-qualified # ⛰️ mountain -1F30B ; fully-qualified # 🌋 volcano -1F5FB ; fully-qualified # 🗻 mount fuji -1F3D5 FE0F ; fully-qualified # 🏕️ camping -1F3D6 FE0F ; fully-qualified # 🏖️ beach with umbrella -1F3DC FE0F ; fully-qualified # 🏜️ desert -1F3DD FE0F ; fully-qualified # 🏝️ desert island -1F3DE FE0F ; fully-qualified # 🏞️ national park - -# subgroup: place-building -1F3DF FE0F ; fully-qualified # 🏟️ stadium -1F3DB FE0F ; fully-qualified # 🏛️ classical building -1F3D7 FE0F ; fully-qualified # 🏗️ building construction -1F3D8 FE0F ; fully-qualified # 🏘️ house -1F3D9 FE0F ; fully-qualified # 🏙️ cityscape -1F3DA FE0F ; fully-qualified # 🏚️ derelict house -1F3E0 ; fully-qualified # 🏠 house -1F3E1 ; fully-qualified # 🏡 house with garden -1F3E2 ; fully-qualified # 🏢 office building -1F3E3 ; fully-qualified # 🏣 Japanese post office -1F3E4 ; fully-qualified # 🏤 post office -1F3E5 ; fully-qualified # 🏥 hospital -1F3E6 ; fully-qualified # 🏦 bank -1F3E8 ; fully-qualified # 🏨 hotel -1F3E9 ; fully-qualified # 🏩 love hotel -1F3EA ; fully-qualified # 🏪 convenience store -1F3EB ; fully-qualified # 🏫 school -1F3EC ; fully-qualified # 🏬 department store -1F3ED ; fully-qualified # 🏭 factory -1F3EF ; fully-qualified # 🏯 Japanese castle -1F3F0 ; fully-qualified # 🏰 castle -1F492 ; fully-qualified # 💒 wedding -1F5FC ; fully-qualified # 🗼 Tokyo tower -1F5FD ; fully-qualified # 🗽 Statue of Liberty - -# subgroup: place-religious -26EA ; fully-qualified # ⛪ church -1F54C ; fully-qualified # 🕌 mosque -1F54D ; fully-qualified # 🕍 synagogue -26E9 FE0F ; fully-qualified # ⛩️ shinto shrine -1F54B ; fully-qualified # 🕋 kaaba - -# subgroup: place-other -26F2 ; fully-qualified # ⛲ fountain -26FA ; fully-qualified # ⛺ tent -1F301 ; fully-qualified # 🌁 foggy -1F303 ; fully-qualified # 🌃 night with stars -1F304 ; fully-qualified # 🌄 sunrise over mountains -1F305 ; fully-qualified # 🌅 sunrise -1F306 ; fully-qualified # 🌆 cityscape at dusk -1F307 ; fully-qualified # 🌇 sunset -1F309 ; fully-qualified # 🌉 bridge at night -2668 FE0F ; fully-qualified # ♨️ hot springs -1F30C ; fully-qualified # 🌌 milky way -1F3A0 ; fully-qualified # 🎠 carousel horse -1F3A1 ; fully-qualified # 🎡 ferris wheel -1F3A2 ; fully-qualified # 🎢 roller coaster -1F488 ; fully-qualified # 💈 barber pole -1F3AA ; fully-qualified # 🎪 circus tent -1F3AD ; fully-qualified # 🎭 performing arts -1F5BC FE0F ; fully-qualified # 🖼️ framed picture -1F3A8 ; fully-qualified # 🎨 artist palette -1F3B0 ; fully-qualified # 🎰 slot machine - -# subgroup: transport-ground -1F682 ; fully-qualified # 🚂 locomotive -1F683 ; fully-qualified # 🚃 railway car -1F684 ; fully-qualified # 🚄 high-speed train -1F685 ; fully-qualified # 🚅 high-speed train with bullet nose -1F686 ; fully-qualified # 🚆 train -1F687 ; fully-qualified # 🚇 metro -1F688 ; fully-qualified # 🚈 light rail -1F689 ; fully-qualified # 🚉 station -1F68A ; fully-qualified # 🚊 tram -1F69D ; fully-qualified # 🚝 monorail -1F69E ; fully-qualified # 🚞 mountain railway -1F68B ; fully-qualified # 🚋 tram car -1F68C ; fully-qualified # 🚌 bus -1F68D ; fully-qualified # 🚍 oncoming bus -1F68E ; fully-qualified # 🚎 trolleybus -1F690 ; fully-qualified # 🚐 minibus -1F691 ; fully-qualified # 🚑 ambulance -1F692 ; fully-qualified # 🚒 fire engine -1F693 ; fully-qualified # 🚓 police car -1F694 ; fully-qualified # 🚔 oncoming police car -1F695 ; fully-qualified # 🚕 taxi -1F696 ; fully-qualified # 🚖 oncoming taxi -1F697 ; fully-qualified # 🚗 automobile -1F698 ; fully-qualified # 🚘 oncoming automobile -1F699 ; fully-qualified # 🚙 sport utility vehicle -1F69A ; fully-qualified # 🚚 delivery truck -1F69B ; fully-qualified # 🚛 articulated lorry -1F69C ; fully-qualified # 🚜 tractor -1F6B2 ; fully-qualified # 🚲 bicycle -1F6F4 ; fully-qualified # 🛴 kick scooter -1F6F5 ; fully-qualified # 🛵 motor scooter -1F68F ; fully-qualified # 🚏 bus stop -1F6E3 FE0F ; fully-qualified # 🛣️ motorway -1F6E4 FE0F ; fully-qualified # 🛤️ railway track -26FD ; fully-qualified # ⛽ fuel pump -1F6A8 ; fully-qualified # 🚨 police car light -1F6A5 ; fully-qualified # 🚥 horizontal traffic light -1F6A6 ; fully-qualified # 🚦 vertical traffic light -1F6A7 ; fully-qualified # 🚧 construction -1F6D1 ; fully-qualified # 🛑 stop sign - -# subgroup: transport-water -2693 ; fully-qualified # ⚓ anchor -26F5 ; fully-qualified # ⛵ sailboat -1F6F6 ; fully-qualified # 🛶 canoe -1F6A4 ; fully-qualified # 🚤 speedboat -1F6F3 FE0F ; fully-qualified # 🛳️ passenger ship -26F4 FE0F ; fully-qualified # ⛴️ ferry -1F6E5 FE0F ; fully-qualified # 🛥️ motor boat -1F6A2 ; fully-qualified # 🚢 ship - -# subgroup: transport-air -2708 FE0F ; fully-qualified # ✈️ airplane -1F6E9 FE0F ; fully-qualified # 🛩️ small airplane -1F6EB ; fully-qualified # 🛫 airplane departure -1F6EC ; fully-qualified # 🛬 airplane arrival -1F4BA ; fully-qualified # 💺 seat -1F681 ; fully-qualified # 🚁 helicopter -1F69F ; fully-qualified # 🚟 suspension railway -1F6A0 ; fully-qualified # 🚠 mountain cableway -1F6A1 ; fully-qualified # 🚡 aerial tramway -1F680 ; fully-qualified # 🚀 rocket -1F6F0 FE0F ; fully-qualified # 🛰️ satellite - -# subgroup: hotel -1F6CE FE0F ; fully-qualified # 🛎️ bellhop bell -1F6AA ; fully-qualified # 🚪 door -1F6CC ; fully-qualified # 🛌 person in bed -1F6CC 1F3FB ; fully-qualified # 🛌🏻 person in bed: light skin tone -1F6CC 1F3FC ; fully-qualified # 🛌🏼 person in bed: medium-light skin tone -1F6CC 1F3FD ; fully-qualified # 🛌🏽 person in bed: medium skin tone -1F6CC 1F3FE ; fully-qualified # 🛌🏾 person in bed: medium-dark skin tone -1F6CC 1F3FF ; fully-qualified # 🛌🏿 person in bed: dark skin tone -1F6CF FE0F ; fully-qualified # 🛏️ bed -1F6CB FE0F ; fully-qualified # 🛋️ couch and lamp -1F6BD ; fully-qualified # 🚽 toilet -1F6BF ; fully-qualified # 🚿 shower -1F6C0 ; fully-qualified # 🛀 person taking bath -1F6C0 1F3FB ; fully-qualified # 🛀🏻 person taking bath: light skin tone -1F6C0 1F3FC ; fully-qualified # 🛀🏼 person taking bath: medium-light skin tone -1F6C0 1F3FD ; fully-qualified # 🛀🏽 person taking bath: medium skin tone -1F6C0 1F3FE ; fully-qualified # 🛀🏾 person taking bath: medium-dark skin tone -1F6C0 1F3FF ; fully-qualified # 🛀🏿 person taking bath: dark skin tone -1F6C1 ; fully-qualified # 🛁 bathtub - -# subgroup: time -231B ; fully-qualified # ⌛ hourglass -23F3 ; fully-qualified # ⏳ hourglass with flowing sand -231A ; fully-qualified # ⌚ watch -23F0 ; fully-qualified # ⏰ alarm clock -23F1 FE0F ; fully-qualified # ⏱️ stopwatch -23F2 FE0F ; fully-qualified # ⏲️ timer clock -1F570 FE0F ; fully-qualified # 🕰️ mantelpiece clock -1F55B ; fully-qualified # 🕛 twelve o’clock -1F567 ; fully-qualified # 🕧 twelve-thirty -1F550 ; fully-qualified # 🕐 one o’clock -1F55C ; fully-qualified # 🕜 one-thirty -1F551 ; fully-qualified # 🕑 two o’clock -1F55D ; fully-qualified # 🕝 two-thirty -1F552 ; fully-qualified # 🕒 three o’clock -1F55E ; fully-qualified # 🕞 three-thirty -1F553 ; fully-qualified # 🕓 four o’clock -1F55F ; fully-qualified # 🕟 four-thirty -1F554 ; fully-qualified # 🕔 five o’clock -1F560 ; fully-qualified # 🕠 five-thirty -1F555 ; fully-qualified # 🕕 six o’clock -1F561 ; fully-qualified # 🕡 six-thirty -1F556 ; fully-qualified # 🕖 seven o’clock -1F562 ; fully-qualified # 🕢 seven-thirty -1F557 ; fully-qualified # 🕗 eight o’clock -1F563 ; fully-qualified # 🕣 eight-thirty -1F558 ; fully-qualified # 🕘 nine o’clock -1F564 ; fully-qualified # 🕤 nine-thirty -1F559 ; fully-qualified # 🕙 ten o’clock -1F565 ; fully-qualified # 🕥 ten-thirty -1F55A ; fully-qualified # 🕚 eleven o’clock -1F566 ; fully-qualified # 🕦 eleven-thirty - -# subgroup: sky & weather -1F311 ; fully-qualified # 🌑 new moon -1F312 ; fully-qualified # 🌒 waxing crescent moon -1F313 ; fully-qualified # 🌓 first quarter moon -1F314 ; fully-qualified # 🌔 waxing gibbous moon -1F315 ; fully-qualified # 🌕 full moon -1F316 ; fully-qualified # 🌖 waning gibbous moon -1F317 ; fully-qualified # 🌗 last quarter moon -1F318 ; fully-qualified # 🌘 waning crescent moon -1F319 ; fully-qualified # 🌙 crescent moon -1F31A ; fully-qualified # 🌚 new moon face -1F31B ; fully-qualified # 🌛 first quarter moon with face -1F31C ; fully-qualified # 🌜 last quarter moon with face -1F321 FE0F ; fully-qualified # 🌡️ thermometer -2600 FE0F ; fully-qualified # ☀️ sun -1F31D ; fully-qualified # 🌝 full moon with face -1F31E ; fully-qualified # 🌞 sun with face -2B50 ; fully-qualified # ⭐ white medium star -1F31F ; fully-qualified # 🌟 glowing star -1F320 ; fully-qualified # 🌠 shooting star -2601 FE0F ; fully-qualified # ☁️ cloud -26C5 ; fully-qualified # ⛅ sun behind cloud -26C8 FE0F ; fully-qualified # ⛈️ cloud with lightning and rain -1F324 FE0F ; fully-qualified # 🌤️ sun behind small cloud -1F325 FE0F ; fully-qualified # 🌥️ sun behind large cloud -1F326 FE0F ; fully-qualified # 🌦️ sun behind rain cloud -1F327 FE0F ; fully-qualified # 🌧️ cloud with rain -1F328 FE0F ; fully-qualified # 🌨️ cloud with snow -1F329 FE0F ; fully-qualified # 🌩️ cloud with lightning -1F32A FE0F ; fully-qualified # 🌪️ tornado -1F32B FE0F ; fully-qualified # 🌫️ fog -1F32C FE0F ; fully-qualified # 🌬️ wind face -1F300 ; fully-qualified # 🌀 cyclone -1F308 ; fully-qualified # 🌈 rainbow -1F302 ; fully-qualified # 🌂 closed umbrella -2602 FE0F ; fully-qualified # ☂️ umbrella -2614 ; fully-qualified # ☔ umbrella with rain drops -26F1 FE0F ; fully-qualified # ⛱️ umbrella on ground -26A1 ; fully-qualified # ⚡ high voltage -2744 FE0F ; fully-qualified # ❄️ snowflake -2603 FE0F ; fully-qualified # ☃️ snowman -26C4 ; fully-qualified # ⛄ snowman without snow -2604 FE0F ; fully-qualified # ☄️ comet -1F525 ; fully-qualified # 🔥 fire -1F4A7 ; fully-qualified # 💧 droplet -1F30A ; fully-qualified # 🌊 water wave - -# Travel & Places subtotal: 218 -# Travel & Places subtotal: 208 w/o modifiers - -# group: Activities - -# subgroup: event -1F383 ; fully-qualified # 🎃 jack-o-lantern -1F384 ; fully-qualified # 🎄 Christmas tree -1F386 ; fully-qualified # 🎆 fireworks -1F387 ; fully-qualified # 🎇 sparkler -2728 ; fully-qualified # ✨ sparkles -1F388 ; fully-qualified # 🎈 balloon -1F389 ; fully-qualified # 🎉 party popper -1F38A ; fully-qualified # 🎊 confetti ball -1F38B ; fully-qualified # 🎋 tanabata tree -1F38D ; fully-qualified # 🎍 pine decoration -1F38E ; fully-qualified # 🎎 Japanese dolls -1F38F ; fully-qualified # 🎏 carp streamer -1F390 ; fully-qualified # 🎐 wind chime -1F391 ; fully-qualified # 🎑 moon viewing ceremony -1F380 ; fully-qualified # 🎀 ribbon -1F381 ; fully-qualified # 🎁 wrapped gift -1F397 FE0F ; fully-qualified # 🎗️ reminder ribbon -1F39F FE0F ; fully-qualified # 🎟️ admission tickets -1F3AB ; fully-qualified # 🎫 ticket - -# subgroup: award-medal -1F396 FE0F ; fully-qualified # 🎖️ military medal -1F3C6 ; fully-qualified # 🏆 trophy -1F3C5 ; fully-qualified # 🏅 sports medal -1F947 ; fully-qualified # 🥇 1st place medal -1F948 ; fully-qualified # 🥈 2nd place medal -1F949 ; fully-qualified # 🥉 3rd place medal - -# subgroup: sport -26BD ; fully-qualified # ⚽ soccer ball -26BE ; fully-qualified # ⚾ baseball -1F3C0 ; fully-qualified # 🏀 basketball -1F3D0 ; fully-qualified # 🏐 volleyball -1F3C8 ; fully-qualified # 🏈 american football -1F3C9 ; fully-qualified # 🏉 rugby football -1F3BE ; fully-qualified # 🎾 tennis -1F3B1 ; fully-qualified # 🎱 pool 8 ball -1F3B3 ; fully-qualified # 🎳 bowling -1F3CF ; fully-qualified # 🏏 cricket -1F3D1 ; fully-qualified # 🏑 field hockey -1F3D2 ; fully-qualified # 🏒 ice hockey -1F3D3 ; fully-qualified # 🏓 ping pong -1F3F8 ; fully-qualified # 🏸 badminton -1F94A ; fully-qualified # 🥊 boxing glove -1F94B ; fully-qualified # 🥋 martial arts uniform -1F945 ; fully-qualified # 🥅 goal net -1F3AF ; fully-qualified # 🎯 direct hit -26F3 ; fully-qualified # ⛳ flag in hole -26F8 FE0F ; fully-qualified # ⛸️ ice skate -1F3A3 ; fully-qualified # 🎣 fishing pole -1F3BD ; fully-qualified # 🎽 running shirt -1F3BF ; fully-qualified # 🎿 skis - -# subgroup: game -1F3AE ; fully-qualified # 🎮 video game -1F579 FE0F ; fully-qualified # 🕹️ joystick -1F3B2 ; fully-qualified # 🎲 game die -2660 FE0F ; fully-qualified # ♠️ spade suit -2665 FE0F ; fully-qualified # ♥️ heart suit -2666 FE0F ; fully-qualified # ♦️ diamond suit -2663 FE0F ; fully-qualified # ♣️ club suit -1F0CF ; fully-qualified # 🃏 joker -1F004 ; fully-qualified # 🀄 mahjong red dragon -1F3B4 ; fully-qualified # 🎴 flower playing cards - -# Activities subtotal: 58 -# Activities subtotal: 58 w/o modifiers - -# group: Objects - -# subgroup: sound -1F507 ; fully-qualified # 🔇 muted speaker -1F508 ; fully-qualified # 🔈 speaker low volume -1F509 ; fully-qualified # 🔉 speaker medium volume -1F50A ; fully-qualified # 🔊 speaker high volume -1F4E2 ; fully-qualified # 📢 loudspeaker -1F4E3 ; fully-qualified # 📣 megaphone -1F4EF ; fully-qualified # 📯 postal horn -1F514 ; fully-qualified # 🔔 bell -1F515 ; fully-qualified # 🔕 bell with slash - -# subgroup: music -1F3BC ; fully-qualified # 🎼 musical score -1F3B5 ; fully-qualified # 🎵 musical note -1F3B6 ; fully-qualified # 🎶 musical notes -1F399 FE0F ; fully-qualified # 🎙️ studio microphone -1F39A FE0F ; fully-qualified # 🎚️ level slider -1F39B FE0F ; fully-qualified # 🎛️ control knobs -1F3A4 ; fully-qualified # 🎤 microphone -1F3A7 ; fully-qualified # 🎧 headphone -1F4FB ; fully-qualified # 📻 radio - -# subgroup: musical-instrument -1F3B7 ; fully-qualified # 🎷 saxophone -1F3B8 ; fully-qualified # 🎸 guitar -1F3B9 ; fully-qualified # 🎹 musical keyboard -1F3BA ; fully-qualified # 🎺 trumpet -1F3BB ; fully-qualified # 🎻 violin -1F941 ; fully-qualified # 🥁 drum - -# subgroup: phone -1F4F1 ; fully-qualified # 📱 mobile phone -1F4F2 ; fully-qualified # 📲 mobile phone with arrow -260E FE0F ; fully-qualified # ☎️ telephone -1F4DE ; fully-qualified # 📞 telephone receiver -1F4DF ; fully-qualified # 📟 pager -1F4E0 ; fully-qualified # 📠 fax machine - -# subgroup: computer -1F50B ; fully-qualified # 🔋 battery -1F50C ; fully-qualified # 🔌 electric plug -1F4BB ; fully-qualified # 💻 laptop computer -1F5A5 FE0F ; fully-qualified # 🖥️ desktop computer -1F5A8 FE0F ; fully-qualified # 🖨️ printer -2328 FE0F ; fully-qualified # ⌨️ keyboard -1F5B1 FE0F ; fully-qualified # 🖱️ computer mouse -1F5B2 FE0F ; fully-qualified # 🖲️ trackball -1F4BD ; fully-qualified # 💽 computer disk -1F4BE ; fully-qualified # 💾 floppy disk -1F4BF ; fully-qualified # 💿 optical disk -1F4C0 ; fully-qualified # 📀 dvd - -# subgroup: light & video -1F3A5 ; fully-qualified # 🎥 movie camera -1F39E FE0F ; fully-qualified # 🎞️ film frames -1F4FD FE0F ; fully-qualified # 📽️ film projector -1F3AC ; fully-qualified # 🎬 clapper board -1F4FA ; fully-qualified # 📺 television -1F4F7 ; fully-qualified # 📷 camera -1F4F8 ; fully-qualified # 📸 camera with flash -1F4F9 ; fully-qualified # 📹 video camera -1F4FC ; fully-qualified # 📼 videocassette -1F50D ; fully-qualified # 🔍 left-pointing magnifying glass -1F50E ; fully-qualified # 🔎 right-pointing magnifying glass -1F52C ; fully-qualified # 🔬 microscope -1F52D ; fully-qualified # 🔭 telescope -1F4E1 ; fully-qualified # 📡 satellite antenna -1F56F FE0F ; fully-qualified # 🕯️ candle -1F4A1 ; fully-qualified # 💡 light bulb -1F526 ; fully-qualified # 🔦 flashlight -1F3EE ; fully-qualified # 🏮 red paper lantern - -# subgroup: book-paper -1F4D4 ; fully-qualified # 📔 notebook with decorative cover -1F4D5 ; fully-qualified # 📕 closed book -1F4D6 ; fully-qualified # 📖 open book -1F4D7 ; fully-qualified # 📗 green book -1F4D8 ; fully-qualified # 📘 blue book -1F4D9 ; fully-qualified # 📙 orange book -1F4DA ; fully-qualified # 📚 books -1F4D3 ; fully-qualified # 📓 notebook -1F4D2 ; fully-qualified # 📒 ledger -1F4C3 ; fully-qualified # 📃 page with curl -1F4DC ; fully-qualified # 📜 scroll -1F4C4 ; fully-qualified # 📄 page facing up -1F4F0 ; fully-qualified # 📰 newspaper -1F5DE FE0F ; fully-qualified # 🗞️ rolled-up newspaper -1F4D1 ; fully-qualified # 📑 bookmark tabs -1F516 ; fully-qualified # 🔖 bookmark -1F3F7 FE0F ; fully-qualified # 🏷️ label - -# subgroup: money -1F4B0 ; fully-qualified # 💰 money bag -1F4B4 ; fully-qualified # 💴 yen banknote -1F4B5 ; fully-qualified # 💵 dollar banknote -1F4B6 ; fully-qualified # 💶 euro banknote -1F4B7 ; fully-qualified # 💷 pound banknote -1F4B8 ; fully-qualified # 💸 money with wings -1F4B3 ; fully-qualified # 💳 credit card -1F4B9 ; fully-qualified # 💹 chart increasing with yen -1F4B1 ; fully-qualified # 💱 currency exchange -1F4B2 ; fully-qualified # 💲 heavy dollar sign - -# subgroup: mail -2709 FE0F ; fully-qualified # ✉️ envelope -1F4E7 ; fully-qualified # 📧 e-mail -1F4E8 ; fully-qualified # 📨 incoming envelope -1F4E9 ; fully-qualified # 📩 envelope with arrow -1F4E4 ; fully-qualified # 📤 outbox tray -1F4E5 ; fully-qualified # 📥 inbox tray -1F4E6 ; fully-qualified # 📦 package -1F4EB ; fully-qualified # 📫 closed mailbox with raised flag -1F4EA ; fully-qualified # 📪 closed mailbox with lowered flag -1F4EC ; fully-qualified # 📬 open mailbox with raised flag -1F4ED ; fully-qualified # 📭 open mailbox with lowered flag -1F4EE ; fully-qualified # 📮 postbox -1F5F3 FE0F ; fully-qualified # 🗳️ ballot box with ballot - -# subgroup: writing -270F FE0F ; fully-qualified # ✏️ pencil -2712 FE0F ; fully-qualified # ✒️ black nib -1F58B FE0F ; fully-qualified # 🖋️ fountain pen -1F58A FE0F ; fully-qualified # 🖊️ pen -1F58C FE0F ; fully-qualified # 🖌️ paintbrush -1F58D FE0F ; fully-qualified # 🖍️ crayon -1F4DD ; fully-qualified # 📝 memo - -# subgroup: office -1F4BC ; fully-qualified # 💼 briefcase -1F4C1 ; fully-qualified # 📁 file folder -1F4C2 ; fully-qualified # 📂 open file folder -1F5C2 FE0F ; fully-qualified # 🗂️ card index dividers -1F4C5 ; fully-qualified # 📅 calendar -1F4C6 ; fully-qualified # 📆 tear-off calendar -1F5D2 FE0F ; fully-qualified # 🗒️ spiral notepad -1F5D3 FE0F ; fully-qualified # 🗓️ spiral calendar -1F4C7 ; fully-qualified # 📇 card index -1F4C8 ; fully-qualified # 📈 chart increasing -1F4C9 ; fully-qualified # 📉 chart decreasing -1F4CA ; fully-qualified # 📊 bar chart -1F4CB ; fully-qualified # 📋 clipboard -1F4CC ; fully-qualified # 📌 pushpin -1F4CD ; fully-qualified # 📍 round pushpin -1F4CE ; fully-qualified # 📎 paperclip -1F587 FE0F ; fully-qualified # 🖇️ linked paperclips -1F4CF ; fully-qualified # 📏 straight ruler -1F4D0 ; fully-qualified # 📐 triangular ruler -2702 FE0F ; fully-qualified # ✂️ scissors -1F5C3 FE0F ; fully-qualified # 🗃️ card file box -1F5C4 FE0F ; fully-qualified # 🗄️ file cabinet -1F5D1 FE0F ; fully-qualified # 🗑️ wastebasket - -# subgroup: lock -1F512 ; fully-qualified # 🔒 locked -1F513 ; fully-qualified # 🔓 unlocked -1F50F ; fully-qualified # 🔏 locked with pen -1F510 ; fully-qualified # 🔐 locked with key -1F511 ; fully-qualified # 🔑 key -1F5DD FE0F ; fully-qualified # 🗝️ old key - -# subgroup: tool -1F528 ; fully-qualified # 🔨 hammer -26CF FE0F ; fully-qualified # ⛏️ pick -2692 FE0F ; fully-qualified # ⚒️ hammer and pick -1F6E0 FE0F ; fully-qualified # 🛠️ hammer and wrench -1F5E1 FE0F ; fully-qualified # 🗡️ dagger -2694 FE0F ; fully-qualified # ⚔️ crossed swords -1F52B ; fully-qualified # 🔫 pistol -1F3F9 ; fully-qualified # 🏹 bow and arrow -1F6E1 FE0F ; fully-qualified # 🛡️ shield -1F527 ; fully-qualified # 🔧 wrench -1F529 ; fully-qualified # 🔩 nut and bolt -2699 FE0F ; fully-qualified # ⚙️ gear -1F5DC FE0F ; fully-qualified # 🗜️ clamp -2697 FE0F ; fully-qualified # ⚗️ alembic -2696 FE0F ; fully-qualified # ⚖️ balance scale -1F517 ; fully-qualified # 🔗 link -26D3 FE0F ; fully-qualified # ⛓️ chains - -# subgroup: medical -1F489 ; fully-qualified # 💉 syringe -1F48A ; fully-qualified # 💊 pill - -# subgroup: other-object -1F6AC ; fully-qualified # 🚬 cigarette -26B0 FE0F ; fully-qualified # ⚰️ coffin -26B1 FE0F ; fully-qualified # ⚱️ funeral urn -1F5FF ; fully-qualified # 🗿 moai -1F6E2 FE0F ; fully-qualified # 🛢️ oil drum -1F52E ; fully-qualified # 🔮 crystal ball -1F6D2 ; fully-qualified # 🛒 shopping cart - -# Objects subtotal: 162 -# Objects subtotal: 162 w/o modifiers - -# group: Symbols - -# subgroup: transport-sign -1F3E7 ; fully-qualified # 🏧 ATM sign -1F6AE ; fully-qualified # 🚮 litter in bin sign -1F6B0 ; fully-qualified # 🚰 potable water -267F ; fully-qualified # ♿ wheelchair symbol -1F6B9 ; fully-qualified # 🚹 men’s room -1F6BA ; fully-qualified # 🚺 women’s room -1F6BB ; fully-qualified # 🚻 restroom -1F6BC ; fully-qualified # 🚼 baby symbol -1F6BE ; fully-qualified # 🚾 water closet -1F6C2 ; fully-qualified # 🛂 passport control -1F6C3 ; fully-qualified # 🛃 customs -1F6C4 ; fully-qualified # 🛄 baggage claim -1F6C5 ; fully-qualified # 🛅 left luggage - -# subgroup: warning -26A0 FE0F ; fully-qualified # ⚠️ warning -1F6B8 ; fully-qualified # 🚸 children crossing -26D4 ; fully-qualified # ⛔ no entry -1F6AB ; fully-qualified # 🚫 prohibited -1F6B3 ; fully-qualified # 🚳 no bicycles -1F6AD ; fully-qualified # 🚭 no smoking -1F6AF ; fully-qualified # 🚯 no littering -1F6B1 ; fully-qualified # 🚱 non-potable water -1F6B7 ; fully-qualified # 🚷 no pedestrians -1F4F5 ; fully-qualified # 📵 no mobile phones -1F51E ; fully-qualified # 🔞 no one under eighteen -2622 FE0F ; fully-qualified # ☢️ radioactive -2623 FE0F ; fully-qualified # ☣️ biohazard - -# subgroup: arrow -2B06 FE0F ; fully-qualified # ⬆️ up arrow -2197 FE0F ; fully-qualified # ↗️ up-right arrow -27A1 FE0F ; fully-qualified # ➡️ right arrow -2198 FE0F ; fully-qualified # ↘️ down-right arrow -2B07 FE0F ; fully-qualified # ⬇️ down arrow -2199 FE0F ; fully-qualified # ↙️ down-left arrow -2B05 FE0F ; fully-qualified # ⬅️ left arrow -2196 FE0F ; fully-qualified # ↖️ up-left arrow -2195 FE0F ; fully-qualified # ↕️ up-down arrow -2194 FE0F ; fully-qualified # ↔️ left-right arrow -21A9 FE0F ; fully-qualified # ↩️ right arrow curving left -21AA FE0F ; fully-qualified # ↪️ left arrow curving right -2934 FE0F ; fully-qualified # ⤴️ right arrow curving up -2935 FE0F ; fully-qualified # ⤵️ right arrow curving down -1F503 ; fully-qualified # 🔃 clockwise vertical arrows -1F504 ; fully-qualified # 🔄 anticlockwise arrows button -1F519 ; fully-qualified # 🔙 BACK arrow -1F51A ; fully-qualified # 🔚 END arrow -1F51B ; fully-qualified # 🔛 ON! arrow -1F51C ; fully-qualified # 🔜 SOON arrow -1F51D ; fully-qualified # 🔝 TOP arrow - -# subgroup: religion -1F6D0 ; fully-qualified # 🛐 place of worship -269B FE0F ; fully-qualified # ⚛️ atom symbol -1F549 FE0F ; fully-qualified # 🕉️ om -2721 FE0F ; fully-qualified # ✡️ star of David -2638 FE0F ; fully-qualified # ☸️ wheel of dharma -262F FE0F ; fully-qualified # ☯️ yin yang -271D FE0F ; fully-qualified # ✝️ latin cross -2626 FE0F ; fully-qualified # ☦️ orthodox cross -262A FE0F ; fully-qualified # ☪️ star and crescent -262E FE0F ; fully-qualified # ☮️ peace symbol -1F54E ; fully-qualified # 🕎 menorah -1F52F ; fully-qualified # 🔯 dotted six-pointed star - -# subgroup: zodiac -2648 ; fully-qualified # ♈ Aries -2649 ; fully-qualified # ♉ Taurus -264A ; fully-qualified # ♊ Gemini -264B ; fully-qualified # ♋ Cancer -264C ; fully-qualified # ♌ Leo -264D ; fully-qualified # ♍ Virgo -264E ; fully-qualified # ♎ Libra -264F ; fully-qualified # ♏ Scorpius -2650 ; fully-qualified # ♐ Sagittarius -2651 ; fully-qualified # ♑ Capricorn -2652 ; fully-qualified # ♒ Aquarius -2653 ; fully-qualified # ♓ Pisces -26CE ; fully-qualified # ⛎ Ophiuchus - -# subgroup: av-symbol -1F500 ; fully-qualified # 🔀 shuffle tracks button -1F501 ; fully-qualified # 🔁 repeat button -1F502 ; fully-qualified # 🔂 repeat single button -25B6 FE0F ; fully-qualified # ▶️ play button -23E9 ; fully-qualified # ⏩ fast-forward button -23ED FE0F ; fully-qualified # ⏭️ next track button -23EF FE0F ; fully-qualified # ⏯️ play or pause button -25C0 FE0F ; fully-qualified # ◀️ reverse button -23EA ; fully-qualified # ⏪ fast reverse button -23EE FE0F ; fully-qualified # ⏮️ last track button -1F53C ; fully-qualified # 🔼 up button -23EB ; fully-qualified # ⏫ fast up button -1F53D ; fully-qualified # 🔽 down button -23EC ; fully-qualified # ⏬ fast down button -23F8 FE0F ; fully-qualified # ⏸️ pause button -23F9 FE0F ; fully-qualified # ⏹️ stop button -23FA FE0F ; fully-qualified # ⏺️ record button -23CF FE0F ; fully-qualified # ⏏️ eject button -1F3A6 ; fully-qualified # 🎦 cinema -1F505 ; fully-qualified # 🔅 dim button -1F506 ; fully-qualified # 🔆 bright button -1F4F6 ; fully-qualified # 📶 antenna bars -1F4F3 ; fully-qualified # 📳 vibration mode -1F4F4 ; fully-qualified # 📴 mobile phone off - -# subgroup: other-symbol -267B FE0F ; fully-qualified # ♻️ recycling symbol -1F4DB ; fully-qualified # 📛 name badge -269C FE0F ; fully-qualified # ⚜️ fleur-de-lis -1F530 ; fully-qualified # 🔰 Japanese symbol for beginner -1F531 ; fully-qualified # 🔱 trident emblem -2B55 ; fully-qualified # ⭕ heavy large circle -2705 ; fully-qualified # ✅ white heavy check mark -2611 FE0F ; fully-qualified # ☑️ ballot box with check -2714 FE0F ; fully-qualified # ✔️ heavy check mark -2716 FE0F ; fully-qualified # ✖️ heavy multiplication x -274C ; fully-qualified # ❌ cross mark -274E ; fully-qualified # ❎ cross mark button -2795 ; fully-qualified # ➕ heavy plus sign -2640 FE0F ; fully-qualified # ♀️ female sign -2642 FE0F ; fully-qualified # ♂️ male sign -2695 FE0F ; fully-qualified # ⚕️ medical symbol -2796 ; fully-qualified # ➖ heavy minus sign -2797 ; fully-qualified # ➗ heavy division sign -27B0 ; fully-qualified # ➰ curly loop -27BF ; fully-qualified # ➿ double curly loop -303D FE0F ; fully-qualified # 〽️ part alternation mark -2733 FE0F ; fully-qualified # ✳️ eight-spoked asterisk -2734 FE0F ; fully-qualified # ✴️ eight-pointed star -2747 FE0F ; fully-qualified # ❇️ sparkle -203C FE0F ; fully-qualified # ‼️ double exclamation mark -2049 FE0F ; fully-qualified # ⁉️ exclamation question mark -2753 ; fully-qualified # ❓ question mark -2754 ; fully-qualified # ❔ white question mark -2755 ; fully-qualified # ❕ white exclamation mark -2757 ; fully-qualified # ❗ exclamation mark -3030 FE0F ; fully-qualified # 〰️ wavy dash -00A9 FE0F ; fully-qualified # ©️ copyright -00AE FE0F ; fully-qualified # ®️ registered -2122 FE0F ; fully-qualified # ™️ trade mark - -# subgroup: keycap -0023 FE0F 20E3 ; fully-qualified # #️⃣ keycap: # -002A FE0F 20E3 ; fully-qualified # *️⃣ keycap: * -0030 FE0F 20E3 ; fully-qualified # 0️⃣ keycap: 0 -0031 FE0F 20E3 ; fully-qualified # 1️⃣ keycap: 1 -0032 FE0F 20E3 ; fully-qualified # 2️⃣ keycap: 2 -0033 FE0F 20E3 ; fully-qualified # 3️⃣ keycap: 3 -0034 FE0F 20E3 ; fully-qualified # 4️⃣ keycap: 4 -0035 FE0F 20E3 ; fully-qualified # 5️⃣ keycap: 5 -0036 FE0F 20E3 ; fully-qualified # 6️⃣ keycap: 6 -0037 FE0F 20E3 ; fully-qualified # 7️⃣ keycap: 7 -0038 FE0F 20E3 ; fully-qualified # 8️⃣ keycap: 8 -0039 FE0F 20E3 ; fully-qualified # 9️⃣ keycap: 9 -1F51F ; fully-qualified # 🔟 keycap 10 - -# subgroup: alphanum -1F4AF ; fully-qualified # 💯 hundred points -1F520 ; fully-qualified # 🔠 input latin uppercase -1F521 ; fully-qualified # 🔡 input latin lowercase -1F522 ; fully-qualified # 🔢 input numbers -1F523 ; fully-qualified # 🔣 input symbols -1F524 ; fully-qualified # 🔤 input latin letters -1F170 FE0F ; fully-qualified # 🅰️ A button (blood type) -1F18E ; fully-qualified # 🆎 AB button (blood type) -1F171 FE0F ; fully-qualified # 🅱️ B button (blood type) -1F191 ; fully-qualified # 🆑 CL button -1F192 ; fully-qualified # 🆒 COOL button -1F193 ; fully-qualified # 🆓 FREE button -2139 FE0F ; fully-qualified # ℹ️ information -1F194 ; fully-qualified # 🆔 ID button -24C2 FE0F ; fully-qualified # Ⓜ️ circled M -1F195 ; fully-qualified # 🆕 NEW button -1F196 ; fully-qualified # 🆖 NG button -1F17E FE0F ; fully-qualified # 🅾️ O button (blood type) -1F197 ; fully-qualified # 🆗 OK button -1F17F FE0F ; fully-qualified # 🅿️ P button -1F198 ; fully-qualified # 🆘 SOS button -1F199 ; fully-qualified # 🆙 UP! button -1F19A ; fully-qualified # 🆚 VS button -1F201 ; fully-qualified # 🈁 Japanese “here” button -1F202 FE0F ; fully-qualified # 🈂️ Japanese “service charge” button -1F237 FE0F ; fully-qualified # 🈷️ Japanese “monthly amount” button -1F236 ; fully-qualified # 🈶 Japanese “not free of charge” button -1F22F ; fully-qualified # 🈯 Japanese “reserved” button -1F250 ; fully-qualified # 🉐 Japanese “bargain” button -1F239 ; fully-qualified # 🈹 Japanese “discount” button -1F21A ; fully-qualified # 🈚 Japanese “free of charge” button -1F232 ; fully-qualified # 🈲 Japanese “prohibited” button -1F251 ; fully-qualified # 🉑 Japanese “acceptable” button -1F238 ; fully-qualified # 🈸 Japanese “application” button -1F234 ; fully-qualified # 🈴 Japanese “passing grade” button -1F233 ; fully-qualified # 🈳 Japanese “vacancy” button -3297 FE0F ; fully-qualified # ㊗️ Japanese “congratulations” button -3299 FE0F ; fully-qualified # ㊙️ Japanese “secret” button -1F23A ; fully-qualified # 🈺 Japanese “open for business” button -1F235 ; fully-qualified # 🈵 Japanese “no vacancy” button - -# subgroup: geometric -25AA FE0F ; fully-qualified # ▪️ black small square -25AB FE0F ; fully-qualified # ▫️ white small square -25FB FE0F ; fully-qualified # ◻️ white medium square -25FC FE0F ; fully-qualified # ◼️ black medium square -25FD ; fully-qualified # ◽ white medium-small square -25FE ; fully-qualified # ◾ black medium-small square -2B1B ; fully-qualified # ⬛ black large square -2B1C ; fully-qualified # ⬜ white large square -1F536 ; fully-qualified # 🔶 large orange diamond -1F537 ; fully-qualified # 🔷 large blue diamond -1F538 ; fully-qualified # 🔸 small orange diamond -1F539 ; fully-qualified # 🔹 small blue diamond -1F53A ; fully-qualified # 🔺 red triangle pointed up -1F53B ; fully-qualified # 🔻 red triangle pointed down -1F4A0 ; fully-qualified # 💠 diamond with a dot -1F518 ; fully-qualified # 🔘 radio button -1F532 ; fully-qualified # 🔲 black square button -1F533 ; fully-qualified # 🔳 white square button -26AA ; fully-qualified # ⚪ white circle -26AB ; fully-qualified # ⚫ black circle -1F534 ; fully-qualified # 🔴 red circle -1F535 ; fully-qualified # 🔵 blue circle - -# Symbols subtotal: 205 -# Symbols subtotal: 205 w/o modifiers - -# group: Flags - -# subgroup: flag -1F3C1 ; fully-qualified # 🏁 chequered flag -1F6A9 ; fully-qualified # 🚩 triangular flag -1F38C ; fully-qualified # 🎌 crossed flags -1F3F4 ; fully-qualified # 🏴 black flag -1F3F3 FE0F ; fully-qualified # 🏳️ white flag -1F3F3 FE0F 200D 1F308 ; fully-qualified # 🏳️‍🌈 rainbow flag -1F3F3 200D 1F308 ; non-fully-qualified # 🏳‍🌈 rainbow flag - -# subgroup: country-flag -1F1E6 1F1E8 ; fully-qualified # 🇦🇨 Ascension Island -1F1E6 1F1E9 ; fully-qualified # 🇦🇩 Andorra -1F1E6 1F1EA ; fully-qualified # 🇦🇪 United Arab Emirates -1F1E6 1F1EB ; fully-qualified # 🇦🇫 Afghanistan -1F1E6 1F1EC ; fully-qualified # 🇦🇬 Antigua & Barbuda -1F1E6 1F1EE ; fully-qualified # 🇦🇮 Anguilla -1F1E6 1F1F1 ; fully-qualified # 🇦🇱 Albania -1F1E6 1F1F2 ; fully-qualified # 🇦🇲 Armenia -1F1E6 1F1F4 ; fully-qualified # 🇦🇴 Angola -1F1E6 1F1F6 ; fully-qualified # 🇦🇶 Antarctica -1F1E6 1F1F7 ; fully-qualified # 🇦🇷 Argentina -1F1E6 1F1F8 ; fully-qualified # 🇦🇸 American Samoa -1F1E6 1F1F9 ; fully-qualified # 🇦🇹 Austria -1F1E6 1F1FA ; fully-qualified # 🇦🇺 Australia -1F1E6 1F1FC ; fully-qualified # 🇦🇼 Aruba -1F1E6 1F1FD ; fully-qualified # 🇦🇽 Åland Islands -1F1E6 1F1FF ; fully-qualified # 🇦🇿 Azerbaijan -1F1E7 1F1E6 ; fully-qualified # 🇧🇦 Bosnia & Herzegovina -1F1E7 1F1E7 ; fully-qualified # 🇧🇧 Barbados -1F1E7 1F1E9 ; fully-qualified # 🇧🇩 Bangladesh -1F1E7 1F1EA ; fully-qualified # 🇧🇪 Belgium -1F1E7 1F1EB ; fully-qualified # 🇧🇫 Burkina Faso -1F1E7 1F1EC ; fully-qualified # 🇧🇬 Bulgaria -1F1E7 1F1ED ; fully-qualified # 🇧🇭 Bahrain -1F1E7 1F1EE ; fully-qualified # 🇧🇮 Burundi -1F1E7 1F1EF ; fully-qualified # 🇧🇯 Benin -1F1E7 1F1F1 ; fully-qualified # 🇧🇱 St. Barthélemy -1F1E7 1F1F2 ; fully-qualified # 🇧🇲 Bermuda -1F1E7 1F1F3 ; fully-qualified # 🇧🇳 Brunei -1F1E7 1F1F4 ; fully-qualified # 🇧🇴 Bolivia -1F1E7 1F1F6 ; fully-qualified # 🇧🇶 Caribbean Netherlands -1F1E7 1F1F7 ; fully-qualified # 🇧🇷 Brazil -1F1E7 1F1F8 ; fully-qualified # 🇧🇸 Bahamas -1F1E7 1F1F9 ; fully-qualified # 🇧🇹 Bhutan -1F1E7 1F1FB ; fully-qualified # 🇧🇻 Bouvet Island -1F1E7 1F1FC ; fully-qualified # 🇧🇼 Botswana -1F1E7 1F1FE ; fully-qualified # 🇧🇾 Belarus -1F1E7 1F1FF ; fully-qualified # 🇧🇿 Belize -1F1E8 1F1E6 ; fully-qualified # 🇨🇦 Canada -1F1E8 1F1E8 ; fully-qualified # 🇨🇨 Cocos (Keeling) Islands -1F1E8 1F1E9 ; fully-qualified # 🇨🇩 Congo - Kinshasa -1F1E8 1F1EB ; fully-qualified # 🇨🇫 Central African Republic -1F1E8 1F1EC ; fully-qualified # 🇨🇬 Congo - Brazzaville -1F1E8 1F1ED ; fully-qualified # 🇨🇭 Switzerland -1F1E8 1F1EE ; fully-qualified # 🇨🇮 Côte d’Ivoire -1F1E8 1F1F0 ; fully-qualified # 🇨🇰 Cook Islands -1F1E8 1F1F1 ; fully-qualified # 🇨🇱 Chile -1F1E8 1F1F2 ; fully-qualified # 🇨🇲 Cameroon -1F1E8 1F1F3 ; fully-qualified # 🇨🇳 China -1F1E8 1F1F4 ; fully-qualified # 🇨🇴 Colombia -1F1E8 1F1F5 ; fully-qualified # 🇨🇵 Clipperton Island -1F1E8 1F1F7 ; fully-qualified # 🇨🇷 Costa Rica -1F1E8 1F1FA ; fully-qualified # 🇨🇺 Cuba -1F1E8 1F1FB ; fully-qualified # 🇨🇻 Cape Verde -1F1E8 1F1FC ; fully-qualified # 🇨🇼 Curaçao -1F1E8 1F1FD ; fully-qualified # 🇨🇽 Christmas Island -1F1E8 1F1FE ; fully-qualified # 🇨🇾 Cyprus -1F1E8 1F1FF ; fully-qualified # 🇨🇿 Czech Republic -1F1E9 1F1EA ; fully-qualified # 🇩🇪 Germany -1F1E9 1F1EC ; fully-qualified # 🇩🇬 Diego Garcia -1F1E9 1F1EF ; fully-qualified # 🇩🇯 Djibouti -1F1E9 1F1F0 ; fully-qualified # 🇩🇰 Denmark -1F1E9 1F1F2 ; fully-qualified # 🇩🇲 Dominica -1F1E9 1F1F4 ; fully-qualified # 🇩🇴 Dominican Republic -1F1E9 1F1FF ; fully-qualified # 🇩🇿 Algeria -1F1EA 1F1E6 ; fully-qualified # 🇪🇦 Ceuta & Melilla -1F1EA 1F1E8 ; fully-qualified # 🇪🇨 Ecuador -1F1EA 1F1EA ; fully-qualified # 🇪🇪 Estonia -1F1EA 1F1EC ; fully-qualified # 🇪🇬 Egypt -1F1EA 1F1ED ; fully-qualified # 🇪🇭 Western Sahara -1F1EA 1F1F7 ; fully-qualified # 🇪🇷 Eritrea -1F1EA 1F1F8 ; fully-qualified # 🇪🇸 Spain -1F1EA 1F1F9 ; fully-qualified # 🇪🇹 Ethiopia -1F1EA 1F1FA ; fully-qualified # 🇪🇺 European Union -1F1EB 1F1EE ; fully-qualified # 🇫🇮 Finland -1F1EB 1F1EF ; fully-qualified # 🇫🇯 Fiji -1F1EB 1F1F0 ; fully-qualified # 🇫🇰 Falkland Islands -1F1EB 1F1F2 ; fully-qualified # 🇫🇲 Micronesia -1F1EB 1F1F4 ; fully-qualified # 🇫🇴 Faroe Islands -1F1EB 1F1F7 ; fully-qualified # 🇫🇷 France -1F1EC 1F1E6 ; fully-qualified # 🇬🇦 Gabon -1F1EC 1F1E7 ; fully-qualified # 🇬🇧 United Kingdom -1F1EC 1F1E9 ; fully-qualified # 🇬🇩 Grenada -1F1EC 1F1EA ; fully-qualified # 🇬🇪 Georgia -1F1EC 1F1EB ; fully-qualified # 🇬🇫 French Guiana -1F1EC 1F1EC ; fully-qualified # 🇬🇬 Guernsey -1F1EC 1F1ED ; fully-qualified # 🇬🇭 Ghana -1F1EC 1F1EE ; fully-qualified # 🇬🇮 Gibraltar -1F1EC 1F1F1 ; fully-qualified # 🇬🇱 Greenland -1F1EC 1F1F2 ; fully-qualified # 🇬🇲 Gambia -1F1EC 1F1F3 ; fully-qualified # 🇬🇳 Guinea -1F1EC 1F1F5 ; fully-qualified # 🇬🇵 Guadeloupe -1F1EC 1F1F6 ; fully-qualified # 🇬🇶 Equatorial Guinea -1F1EC 1F1F7 ; fully-qualified # 🇬🇷 Greece -1F1EC 1F1F8 ; fully-qualified # 🇬🇸 South Georgia & South Sandwich Islands -1F1EC 1F1F9 ; fully-qualified # 🇬🇹 Guatemala -1F1EC 1F1FA ; fully-qualified # 🇬🇺 Guam -1F1EC 1F1FC ; fully-qualified # 🇬🇼 Guinea-Bissau -1F1EC 1F1FE ; fully-qualified # 🇬🇾 Guyana -1F1ED 1F1F0 ; fully-qualified # 🇭🇰 Hong Kong SAR China -1F1ED 1F1F2 ; fully-qualified # 🇭🇲 Heard & McDonald Islands -1F1ED 1F1F3 ; fully-qualified # 🇭🇳 Honduras -1F1ED 1F1F7 ; fully-qualified # 🇭🇷 Croatia -1F1ED 1F1F9 ; fully-qualified # 🇭🇹 Haiti -1F1ED 1F1FA ; fully-qualified # 🇭🇺 Hungary -1F1EE 1F1E8 ; fully-qualified # 🇮🇨 Canary Islands -1F1EE 1F1E9 ; fully-qualified # 🇮🇩 Indonesia -1F1EE 1F1EA ; fully-qualified # 🇮🇪 Ireland -1F1EE 1F1F1 ; fully-qualified # 🇮🇱 Israel -1F1EE 1F1F2 ; fully-qualified # 🇮🇲 Isle of Man -1F1EE 1F1F3 ; fully-qualified # 🇮🇳 India -1F1EE 1F1F4 ; fully-qualified # 🇮🇴 British Indian Ocean Territory -1F1EE 1F1F6 ; fully-qualified # 🇮🇶 Iraq -1F1EE 1F1F7 ; fully-qualified # 🇮🇷 Iran -1F1EE 1F1F8 ; fully-qualified # 🇮🇸 Iceland -1F1EE 1F1F9 ; fully-qualified # 🇮🇹 Italy -1F1EF 1F1EA ; fully-qualified # 🇯🇪 Jersey -1F1EF 1F1F2 ; fully-qualified # 🇯🇲 Jamaica -1F1EF 1F1F4 ; fully-qualified # 🇯🇴 Jordan -1F1EF 1F1F5 ; fully-qualified # 🇯🇵 Japan -1F1F0 1F1EA ; fully-qualified # 🇰🇪 Kenya -1F1F0 1F1EC ; fully-qualified # 🇰🇬 Kyrgyzstan -1F1F0 1F1ED ; fully-qualified # 🇰🇭 Cambodia -1F1F0 1F1EE ; fully-qualified # 🇰🇮 Kiribati -1F1F0 1F1F2 ; fully-qualified # 🇰🇲 Comoros -1F1F0 1F1F3 ; fully-qualified # 🇰🇳 St. Kitts & Nevis -1F1F0 1F1F5 ; fully-qualified # 🇰🇵 North Korea -1F1F0 1F1F7 ; fully-qualified # 🇰🇷 South Korea -1F1F0 1F1FC ; fully-qualified # 🇰🇼 Kuwait -1F1F0 1F1FE ; fully-qualified # 🇰🇾 Cayman Islands -1F1F0 1F1FF ; fully-qualified # 🇰🇿 Kazakhstan -1F1F1 1F1E6 ; fully-qualified # 🇱🇦 Laos -1F1F1 1F1E7 ; fully-qualified # 🇱🇧 Lebanon -1F1F1 1F1E8 ; fully-qualified # 🇱🇨 St. Lucia -1F1F1 1F1EE ; fully-qualified # 🇱🇮 Liechtenstein -1F1F1 1F1F0 ; fully-qualified # 🇱🇰 Sri Lanka -1F1F1 1F1F7 ; fully-qualified # 🇱🇷 Liberia -1F1F1 1F1F8 ; fully-qualified # 🇱🇸 Lesotho -1F1F1 1F1F9 ; fully-qualified # 🇱🇹 Lithuania -1F1F1 1F1FA ; fully-qualified # 🇱🇺 Luxembourg -1F1F1 1F1FB ; fully-qualified # 🇱🇻 Latvia -1F1F1 1F1FE ; fully-qualified # 🇱🇾 Libya -1F1F2 1F1E6 ; fully-qualified # 🇲🇦 Morocco -1F1F2 1F1E8 ; fully-qualified # 🇲🇨 Monaco -1F1F2 1F1E9 ; fully-qualified # 🇲🇩 Moldova -1F1F2 1F1EA ; fully-qualified # 🇲🇪 Montenegro -1F1F2 1F1EB ; fully-qualified # 🇲🇫 St. Martin -1F1F2 1F1EC ; fully-qualified # 🇲🇬 Madagascar -1F1F2 1F1ED ; fully-qualified # 🇲🇭 Marshall Islands -1F1F2 1F1F0 ; fully-qualified # 🇲🇰 Macedonia -1F1F2 1F1F1 ; fully-qualified # 🇲🇱 Mali -1F1F2 1F1F2 ; fully-qualified # 🇲🇲 Myanmar (Burma) -1F1F2 1F1F3 ; fully-qualified # 🇲🇳 Mongolia -1F1F2 1F1F4 ; fully-qualified # 🇲🇴 Macau SAR China -1F1F2 1F1F5 ; fully-qualified # 🇲🇵 Northern Mariana Islands -1F1F2 1F1F6 ; fully-qualified # 🇲🇶 Martinique -1F1F2 1F1F7 ; fully-qualified # 🇲🇷 Mauritania -1F1F2 1F1F8 ; fully-qualified # 🇲🇸 Montserrat -1F1F2 1F1F9 ; fully-qualified # 🇲🇹 Malta -1F1F2 1F1FA ; fully-qualified # 🇲🇺 Mauritius -1F1F2 1F1FB ; fully-qualified # 🇲🇻 Maldives -1F1F2 1F1FC ; fully-qualified # 🇲🇼 Malawi -1F1F2 1F1FD ; fully-qualified # 🇲🇽 Mexico -1F1F2 1F1FE ; fully-qualified # 🇲🇾 Malaysia -1F1F2 1F1FF ; fully-qualified # 🇲🇿 Mozambique -1F1F3 1F1E6 ; fully-qualified # 🇳🇦 Namibia -1F1F3 1F1E8 ; fully-qualified # 🇳🇨 New Caledonia -1F1F3 1F1EA ; fully-qualified # 🇳🇪 Niger -1F1F3 1F1EB ; fully-qualified # 🇳🇫 Norfolk Island -1F1F3 1F1EC ; fully-qualified # 🇳🇬 Nigeria -1F1F3 1F1EE ; fully-qualified # 🇳🇮 Nicaragua -1F1F3 1F1F1 ; fully-qualified # 🇳🇱 Netherlands -1F1F3 1F1F4 ; fully-qualified # 🇳🇴 Norway -1F1F3 1F1F5 ; fully-qualified # 🇳🇵 Nepal -1F1F3 1F1F7 ; fully-qualified # 🇳🇷 Nauru -1F1F3 1F1FA ; fully-qualified # 🇳🇺 Niue -1F1F3 1F1FF ; fully-qualified # 🇳🇿 New Zealand -1F1F4 1F1F2 ; fully-qualified # 🇴🇲 Oman -1F1F5 1F1E6 ; fully-qualified # 🇵🇦 Panama -1F1F5 1F1EA ; fully-qualified # 🇵🇪 Peru -1F1F5 1F1EB ; fully-qualified # 🇵🇫 French Polynesia -1F1F5 1F1EC ; fully-qualified # 🇵🇬 Papua New Guinea -1F1F5 1F1ED ; fully-qualified # 🇵🇭 Philippines -1F1F5 1F1F0 ; fully-qualified # 🇵🇰 Pakistan -1F1F5 1F1F1 ; fully-qualified # 🇵🇱 Poland -1F1F5 1F1F2 ; fully-qualified # 🇵🇲 St. Pierre & Miquelon -1F1F5 1F1F3 ; fully-qualified # 🇵🇳 Pitcairn Islands -1F1F5 1F1F7 ; fully-qualified # 🇵🇷 Puerto Rico -1F1F5 1F1F8 ; fully-qualified # 🇵🇸 Palestinian Territories -1F1F5 1F1F9 ; fully-qualified # 🇵🇹 Portugal -1F1F5 1F1FC ; fully-qualified # 🇵🇼 Palau -1F1F5 1F1FE ; fully-qualified # 🇵🇾 Paraguay -1F1F6 1F1E6 ; fully-qualified # 🇶🇦 Qatar -1F1F7 1F1EA ; fully-qualified # 🇷🇪 Réunion -1F1F7 1F1F4 ; fully-qualified # 🇷🇴 Romania -1F1F7 1F1F8 ; fully-qualified # 🇷🇸 Serbia -1F1F7 1F1FA ; fully-qualified # 🇷🇺 Russia -1F1F7 1F1FC ; fully-qualified # 🇷🇼 Rwanda -1F1F8 1F1E6 ; fully-qualified # 🇸🇦 Saudi Arabia -1F1F8 1F1E7 ; fully-qualified # 🇸🇧 Solomon Islands -1F1F8 1F1E8 ; fully-qualified # 🇸🇨 Seychelles -1F1F8 1F1E9 ; fully-qualified # 🇸🇩 Sudan -1F1F8 1F1EA ; fully-qualified # 🇸🇪 Sweden -1F1F8 1F1EC ; fully-qualified # 🇸🇬 Singapore -1F1F8 1F1ED ; fully-qualified # 🇸🇭 St. Helena -1F1F8 1F1EE ; fully-qualified # 🇸🇮 Slovenia -1F1F8 1F1EF ; fully-qualified # 🇸🇯 Svalbard & Jan Mayen -1F1F8 1F1F0 ; fully-qualified # 🇸🇰 Slovakia -1F1F8 1F1F1 ; fully-qualified # 🇸🇱 Sierra Leone -1F1F8 1F1F2 ; fully-qualified # 🇸🇲 San Marino -1F1F8 1F1F3 ; fully-qualified # 🇸🇳 Senegal -1F1F8 1F1F4 ; fully-qualified # 🇸🇴 Somalia -1F1F8 1F1F7 ; fully-qualified # 🇸🇷 Suriname -1F1F8 1F1F8 ; fully-qualified # 🇸🇸 South Sudan -1F1F8 1F1F9 ; fully-qualified # 🇸🇹 São Tomé & Príncipe -1F1F8 1F1FB ; fully-qualified # 🇸🇻 El Salvador -1F1F8 1F1FD ; fully-qualified # 🇸🇽 Sint Maarten -1F1F8 1F1FE ; fully-qualified # 🇸🇾 Syria -1F1F8 1F1FF ; fully-qualified # 🇸🇿 Swaziland -1F1F9 1F1E6 ; fully-qualified # 🇹🇦 Tristan da Cunha -1F1F9 1F1E8 ; fully-qualified # 🇹🇨 Turks & Caicos Islands -1F1F9 1F1E9 ; fully-qualified # 🇹🇩 Chad -1F1F9 1F1EB ; fully-qualified # 🇹🇫 French Southern Territories -1F1F9 1F1EC ; fully-qualified # 🇹🇬 Togo -1F1F9 1F1ED ; fully-qualified # 🇹🇭 Thailand -1F1F9 1F1EF ; fully-qualified # 🇹🇯 Tajikistan -1F1F9 1F1F0 ; fully-qualified # 🇹🇰 Tokelau -1F1F9 1F1F1 ; fully-qualified # 🇹🇱 Timor-Leste -1F1F9 1F1F2 ; fully-qualified # 🇹🇲 Turkmenistan -1F1F9 1F1F3 ; fully-qualified # 🇹🇳 Tunisia -1F1F9 1F1F4 ; fully-qualified # 🇹🇴 Tonga -1F1F9 1F1F7 ; fully-qualified # 🇹🇷 Turkey -1F1F9 1F1F9 ; fully-qualified # 🇹🇹 Trinidad & Tobago -1F1F9 1F1FB ; fully-qualified # 🇹🇻 Tuvalu -1F1F9 1F1FC ; fully-qualified # 🇹🇼 Taiwan -1F1F9 1F1FF ; fully-qualified # 🇹🇿 Tanzania -1F1FA 1F1E6 ; fully-qualified # 🇺🇦 Ukraine -1F1FA 1F1EC ; fully-qualified # 🇺🇬 Uganda -1F1FA 1F1F2 ; fully-qualified # 🇺🇲 U.S. Outlying Islands -1F1FA 1F1F3 ; fully-qualified # 🇺🇳 United Nations -1F1FA 1F1F8 ; fully-qualified # 🇺🇸 United States -1F1FA 1F1FE ; fully-qualified # 🇺🇾 Uruguay -1F1FA 1F1FF ; fully-qualified # 🇺🇿 Uzbekistan -1F1FB 1F1E6 ; fully-qualified # 🇻🇦 Vatican City -1F1FB 1F1E8 ; fully-qualified # 🇻🇨 St. Vincent & Grenadines -1F1FB 1F1EA ; fully-qualified # 🇻🇪 Venezuela -1F1FB 1F1EC ; fully-qualified # 🇻🇬 British Virgin Islands -1F1FB 1F1EE ; fully-qualified # 🇻🇮 U.S. Virgin Islands -1F1FB 1F1F3 ; fully-qualified # 🇻🇳 Vietnam -1F1FB 1F1FA ; fully-qualified # 🇻🇺 Vanuatu -1F1FC 1F1EB ; fully-qualified # 🇼🇫 Wallis & Futuna -1F1FC 1F1F8 ; fully-qualified # 🇼🇸 Samoa -1F1FD 1F1F0 ; fully-qualified # 🇽🇰 Kosovo -1F1FE 1F1EA ; fully-qualified # 🇾🇪 Yemen -1F1FE 1F1F9 ; fully-qualified # 🇾🇹 Mayotte -1F1FF 1F1E6 ; fully-qualified # 🇿🇦 South Africa -1F1FF 1F1F2 ; fully-qualified # 🇿🇲 Zambia -1F1FF 1F1FC ; fully-qualified # 🇿🇼 Zimbabwe - -# Flags subtotal: 264 -# Flags subtotal: 264 w/o modifiers - -#EOF diff --git a/ibus-HEAD.patch b/ibus-HEAD.patch deleted file mode 100644 index 6f1dcb0..0000000 --- a/ibus-HEAD.patch +++ /dev/null @@ -1,9971 +0,0 @@ -From 7e477d5e0ffe19b6c52558c5b37fdd9cb82c097a Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Thu, 9 Mar 2017 11:31:21 +0900 -Subject: [PATCH] tools: Fix `ibus emoji` SEGV when language is changed. - -BUG=rhbz#1430290 - -Review URL: https://codereview.appspot.com/317410043 ---- - tools/main.vala | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/tools/main.vala b/tools/main.vala -index 73c6f57..fd9fd0e 100644 ---- a/tools/main.vala -+++ b/tools/main.vala -@@ -353,6 +353,9 @@ int emoji_dialog(string[] argv) { - } else { - GLib.MainLoop loop = new GLib.MainLoop(); - emojier.loaded_emoji_dict.connect(() => { -+ // The signal is called when the language is changed. -+ if (emojier.is_running()) -+ return; - run_dialog(emojier); - loop.quit(); - }); --- -2.7.4 - -From 9dbea347050ae2ad79d1b53f2ad62a7a2cafadc6 Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Thu, 9 Mar 2017 12:45:20 +0900 -Subject: [PATCH] ui/gtk3: Get emoji colors from the theme - -Get selected and normal text color from the theme. -Implement to activate an emoji with mouse motion. -Create back button on emoji language chooser dialog. -Set row_homogeneous on emoji table. - -R=Shawn.P.Huang@gmail.com - -Review URL: https://codereview.appspot.com/319470043 ---- - ui/gtk3/Makefile.am | 3 + - ui/gtk3/candidatearea.vala | 186 +++++++++++++++++++++++++------------------ - ui/gtk3/emojier.vala | 192 ++++++++++++++++++++++++++++++++------------- - 3 files changed, 251 insertions(+), 130 deletions(-) - -diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am -index d5ddc42..4e7fd1b 100644 ---- a/ui/gtk3/Makefile.am -+++ b/ui/gtk3/Makefile.am -@@ -172,8 +172,11 @@ libibus_emoji_dialog_1_0_la_LDFLAGS = \ - -version-info @LT_VERSION_INFO@ \ - $(NULL) - libibus_emoji_dialog_1_0_la_SOURCES = \ -+ candidatearea.c \ - emojier.c \ - iconwidget.c \ -+ pango.c \ -+ separator.c \ - $(NULL) - - -include $(INTROSPECTION_MAKEFILE) -diff --git a/ui/gtk3/candidatearea.vala b/ui/gtk3/candidatearea.vala -index a095e76..e162a96 100644 ---- a/ui/gtk3/candidatearea.vala -+++ b/ui/gtk3/candidatearea.vala -@@ -21,6 +21,108 @@ - * USA - */ - -+class ThemedRGBA { -+ public Gdk.RGBA *normal_fg { get; set; } -+ public Gdk.RGBA *normal_bg { get; set; } -+ public Gdk.RGBA *selected_fg { get; set; } -+ public Gdk.RGBA *selected_bg { get; set; } -+ -+ private Gtk.StyleContext m_style_context; -+ -+ public ThemedRGBA(Gtk.Widget widget) { -+ this.normal_fg = null; -+ this.normal_bg = null; -+ this.selected_fg = null; -+ this.selected_bg = null; -+ -+ /* Use the color of Gtk.TextView instead of Gtk.Label -+ * because the selected label "color" is not configured -+ * in "Adwaita" theme and the selected label "background-color" -+ * is not configured in "Maia" theme. -+ * https://github.com/ibus/ibus/issues/1871 -+ */ -+ Gtk.WidgetPath widget_path = new Gtk.WidgetPath(); -+ widget_path.append_type(typeof(Gtk.TextView)); -+ m_style_context = new Gtk.StyleContext(); -+ m_style_context.set_path(widget_path); -+ m_style_context.add_class(Gtk.STYLE_CLASS_VIEW); -+ -+ /* "-gtk-secondary-caret-color" value is different -+ * if the parent widget is set in "Menta" theme. -+ */ -+ m_style_context.set_parent(widget.get_style_context()); -+ -+ get_rgba(); -+ -+ m_style_context.changed.connect(() => { get_rgba(); }); -+ } -+ -+ ~ThemedRGBA() { -+ reset_rgba(); -+ } -+ -+ private void reset_rgba() { -+ if (this.normal_fg != null) { -+ this.normal_fg.free(); -+ this.normal_fg = null; -+ } -+ if (this.normal_bg != null) { -+ this.normal_bg.free(); -+ this.normal_bg = null; -+ } -+ if (this.selected_fg != null) { -+ this.selected_fg.free(); -+ this.selected_fg = null; -+ } -+ if (this.selected_bg != null) { -+ this.selected_bg.free(); -+ this.selected_bg = null; -+ } -+ } -+ -+ private void get_rgba() { -+ reset_rgba(); -+ Gdk.RGBA *normal_fg = null; -+ Gdk.RGBA *normal_bg = null; -+ Gdk.RGBA *selected_fg = null; -+ Gdk.RGBA *selected_bg = null; -+ m_style_context.get(Gtk.StateFlags.NORMAL, -+ "color", -+ out normal_fg); -+ m_style_context.get(Gtk.StateFlags.SELECTED, -+ "color", -+ out selected_fg); -+ -+ string bg_prop = "background-color"; -+ m_style_context.get(Gtk.StateFlags.NORMAL, -+ bg_prop, -+ out normal_bg); -+ m_style_context.get(Gtk.StateFlags.SELECTED, -+ bg_prop, -+ out selected_bg); -+ if (normal_bg.red == selected_bg.red && -+ normal_bg.green == selected_bg.green && -+ normal_bg.blue == selected_bg.blue && -+ normal_bg.alpha == selected_bg.alpha) { -+ normal_bg.free(); -+ normal_bg = null; -+ normal_bg.free(); -+ normal_bg = null; -+ bg_prop = "-gtk-secondary-caret-color"; -+ m_style_context.get(Gtk.StateFlags.NORMAL, -+ bg_prop, -+ out normal_bg); -+ m_style_context.get(Gtk.StateFlags.SELECTED, -+ bg_prop, -+ out selected_bg); -+ } -+ this.normal_fg = normal_fg; -+ this.normal_bg = normal_bg; -+ this.selected_fg = selected_fg; -+ this.selected_bg = selected_bg; -+ } -+} -+ - class CandidateArea : Gtk.Box { - private bool m_vertical; - private Gtk.Label[] m_labels; -@@ -30,9 +132,7 @@ class CandidateArea : Gtk.Box { - private IBus.Text[] m_ibus_candidates; - private uint m_focus_candidate; - private bool m_show_cursor; -- Gtk.StyleContext m_style_context; -- private Gdk.RGBA *m_selected_fg_color = null; -- private Gdk.RGBA *m_selected_bg_color = null; -+ private ThemedRGBA m_rgba; - - private const string LABELS[] = { - "1.", "2.", "3.", "4.", "5.", "6.", "7.", "8.", -@@ -58,38 +158,7 @@ class CandidateArea : Gtk.Box { - public CandidateArea(bool vertical) { - GLib.Object(); - set_vertical(vertical, true); -- -- /* Use the color of Gtk.TextView instead of Gtk.Label -- * because the selected label "color" is not configured -- * in "Adwaita" theme and the selected label "background-color" -- * is not configured in "Maia" theme. -- * https://github.com/ibus/ibus/issues/1871 -- */ -- Gtk.WidgetPath widget_path = new Gtk.WidgetPath(); -- widget_path.append_type(typeof(Gtk.TextView)); -- m_style_context = new Gtk.StyleContext(); -- m_style_context.set_path(widget_path); -- m_style_context.add_class(Gtk.STYLE_CLASS_VIEW); -- -- /* "-gtk-secondary-caret-color" value is different -- * if the parent widget is set in "Menta" theme. -- */ -- m_style_context.set_parent(get_style_context()); -- -- get_selected_color(); -- -- m_style_context.changed.connect(() => { get_selected_color(); }); -- } -- -- ~CandidateArea() { -- if (m_selected_bg_color != null) { -- m_selected_bg_color.free(); -- m_selected_bg_color = null; -- } -- if (m_selected_bg_color != null) { -- m_selected_bg_color.free(); -- m_selected_bg_color = null; -- } -+ m_rgba = new ThemedRGBA(this); - } - - public bool candidate_scrolled(Gdk.EventScroll event) { -@@ -150,17 +219,17 @@ class CandidateArea : Gtk.Box { - Pango.AttrList attrs = get_pango_attr_list_from_ibus_text(candidates[i]); - if (i == focus_candidate && show_cursor) { - Pango.Attribute pango_attr = Pango.attr_foreground_new( -- (uint16)(m_selected_fg_color.red * uint16.MAX), -- (uint16)(m_selected_fg_color.green * uint16.MAX), -- (uint16)(m_selected_fg_color.blue * uint16.MAX)); -+ (uint16)(m_rgba.selected_fg.red * uint16.MAX), -+ (uint16)(m_rgba.selected_fg.green * uint16.MAX), -+ (uint16)(m_rgba.selected_fg.blue * uint16.MAX)); - pango_attr.start_index = 0; - pango_attr.end_index = candidates[i].get_text().length; - attrs.insert((owned)pango_attr); - - pango_attr = Pango.attr_background_new( -- (uint16)(m_selected_bg_color.red * uint16.MAX), -- (uint16)(m_selected_bg_color.green * uint16.MAX), -- (uint16)(m_selected_bg_color.blue * uint16.MAX)); -+ (uint16)(m_rgba.selected_bg.red * uint16.MAX), -+ (uint16)(m_rgba.selected_bg.green * uint16.MAX), -+ (uint16)(m_rgba.selected_bg.blue * uint16.MAX)); - pango_attr.start_index = 0; - pango_attr.end_index = candidates[i].get_text().length; - attrs.insert((owned)pango_attr); -@@ -181,41 +250,6 @@ class CandidateArea : Gtk.Box { - } - } - -- private void get_selected_color() { -- if (m_selected_fg_color != null) { -- m_selected_fg_color.free(); -- m_selected_fg_color = null; -- } -- m_style_context.get(Gtk.StateFlags.SELECTED, -- "color", -- out m_selected_fg_color); -- -- string bg_prop = "background-color"; -- Gdk.RGBA *normal_color = null; -- if (m_selected_bg_color != null) { -- m_selected_bg_color.free(); -- m_selected_bg_color = null; -- } -- m_style_context.get(Gtk.StateFlags.NORMAL, -- bg_prop, -- out normal_color); -- m_style_context.get(Gtk.StateFlags.SELECTED, -- bg_prop, -- out m_selected_bg_color); -- if (normal_color.red == m_selected_bg_color.red && -- normal_color.green == m_selected_bg_color.green && -- normal_color.blue == m_selected_bg_color.blue && -- normal_color.alpha == m_selected_bg_color.alpha) { -- m_selected_bg_color.free(); -- m_selected_bg_color = null; -- bg_prop = "-gtk-secondary-caret-color"; -- m_style_context.get(Gtk.StateFlags.SELECTED, -- bg_prop, -- out m_selected_bg_color); -- } -- normal_color.free(); -- } -- - private void recreate_ui() { - foreach (Gtk.Widget w in get_children()) { - w.destroy(); -diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala -index 5496c4e..bc1eff4 100644 ---- a/ui/gtk3/emojier.vala -+++ b/ui/gtk3/emojier.vala -@@ -72,12 +72,31 @@ class IBusEmojier : Gtk.Window { - private class EGrid : Gtk.Grid { - public EGrid() { - GLib.Object( -+ row_homogeneous : false, - vexpand : true, - halign : Gtk.Align.FILL, - valign : Gtk.Align.FILL - ); - } - } -+ private class EWhiteLabel : Gtk.Label { -+ public EWhiteLabel(string text) { -+ GLib.Object( -+ name : "IBusEmojierWhiteLabel" -+ ); -+ if (text != "") -+ set_label(text); -+ } -+ } -+ private class ESelectedLabel : Gtk.Label { -+ public ESelectedLabel(string text) { -+ GLib.Object( -+ name : "IBusEmojierSelectedLabel" -+ ); -+ if (text != "") -+ set_label(text); -+ } -+ } - private class EPaddedLabel : Gtk.Box { - public EPaddedLabel(string text, - Gtk.Align align, -@@ -161,6 +180,7 @@ class IBusEmojier : Gtk.Window { - } - - private const uint EMOJI_GRID_PAGE = 10; -+ private ThemedRGBA m_rgba; - private Gtk.Box m_vbox; - private ETitleLabel m_title; - private EEntry m_entry; -@@ -174,7 +194,8 @@ class IBusEmojier : Gtk.Window { - private GLib.MainLoop? m_loop; - private string? m_result; - private GLib.SList m_lang_list; -- private string m_current_lang = "en"; -+ private string m_current_lang_id = "en"; -+ private string m_current_language = "English"; - private string? m_unicode_point = null; - private bool m_candidate_panel_is_visible; - private GLib.HashTable? -@@ -189,11 +210,8 @@ class IBusEmojier : Gtk.Window { - private Gtk.Label[] m_candidates; - private string m_emoji_font = "Monospace 16"; - private string[] m_favorites = {}; -- // TODO: Get the selected color from CandidateArea -- private Gdk.RGBA m_selected_fg_color = Gdk.RGBA(){ -- red = 1.0, green = 1.0, blue = 1.0, alpha = 1.0 }; -- private Gdk.RGBA m_selected_bg_color = Gdk.RGBA(){ -- red = 0.300, green = 0.565, blue = 0.851, alpha = 1.0 }; -+ private bool m_enter_notify_enable = true; -+ private uint m_entry_notify_show_id; - - public signal void candidate_clicked(uint index, uint button, uint state); - public signal void loaded_emoji_dict(); -@@ -220,7 +238,33 @@ class IBusEmojier : Gtk.Window { - warning("Could not open display."); - return; - } -- string data = "grid { background-color: #ffffff; }"; -+ m_rgba = new ThemedRGBA(this); -+ uint bg_red = (uint)(m_rgba.normal_bg.red * 255); -+ uint bg_green = (uint)(m_rgba.normal_bg.green * 255); -+ uint bg_blue = (uint)(m_rgba.normal_bg.blue * 255); -+ double bg_alpha = m_rgba.normal_bg.alpha; -+ string data = -+ "#IBusEmojierWhiteLabel { background-color: " + -+ "rgba(%u, %u, %u, %lf); ".printf( -+ bg_red, bg_green, bg_blue, bg_alpha) + -+ "border-width: 4px; border-radius: 3px; } "; -+ -+ uint fg_red = (uint)(m_rgba.selected_fg.red * 255); -+ uint fg_green = (uint)(m_rgba.selected_fg.green * 255); -+ uint fg_blue = (uint)(m_rgba.selected_fg.blue * 255); -+ double fg_alpha = m_rgba.selected_fg.alpha; -+ bg_red = (uint)(m_rgba.selected_bg.red * 255); -+ bg_green = (uint)(m_rgba.selected_bg.green * 255); -+ bg_blue = (uint)(m_rgba.selected_bg.blue * 255); -+ bg_alpha = m_rgba.selected_bg.alpha; -+ data += "#IBusEmojierSelectedLabel { color: " + -+ "rgba(%u, %u, %u, %lf); ".printf( -+ fg_red, fg_green, fg_blue, fg_alpha) + -+ "background-color: " + -+ "rgba(%u, %u, %u, %lf); ".printf( -+ bg_red, bg_green, bg_blue, bg_alpha) + -+ "border-width: 4px; border-radius: 3px; }"; -+ - Gtk.CssProvider css_provider = new Gtk.CssProvider(); - try { - css_provider.load_from_data(data, -1); -@@ -317,6 +361,8 @@ class IBusEmojier : Gtk.Window { - lang_list.sort((a, b) => { - string a_lang = IBus.get_language_name(a); - string b_lang = IBus.get_language_name(b); -+ a_lang = "%s (%s)".printf(a_lang, a); -+ b_lang = "%s (%s)".printf(b_lang, b); - return GLib.strcmp(a_lang, b_lang); - }); - return lang_list; -@@ -325,8 +371,8 @@ class IBusEmojier : Gtk.Window { - private void reload_emoji_dict() { - init_emoji_dict(); - make_emoji_dict("en"); -- if (m_current_lang != "en") -- make_emoji_dict(m_current_lang); -+ if (m_current_lang_id != "en") -+ make_emoji_dict(m_current_lang_id); - loaded_emoji_dict(); - } - -@@ -458,22 +504,50 @@ class IBusEmojier : Gtk.Window { - } - } - -+ private void activated_language(EBoxRow row) { -+ m_category_active_index = 0; -+ if (m_current_lang_id != row.id) { -+ m_current_lang_id = row.id; -+ m_current_language = row.text; -+ reload_emoji_dict(); -+ } -+ m_current_category_type = CategoryType.EMOJI; -+ show_category_list(); -+ } -+ - private void show_category_list() { - remove_all_children(); - m_scrolled_window = new EScrolledWindow(); - set_fixed_size(); -- string language = IBus.get_language_name(m_current_lang); -- EPaddedLabel label = new EPaddedLabel(language, Gtk.Align.CENTER); -+ EPaddedLabel label; -+ if (m_current_category_type == CategoryType.EMOJI) { -+ label = new EPaddedLabel(m_current_language, Gtk.Align.CENTER); -+ } else if (m_current_category_type == CategoryType.LANG) { -+ label = new EPaddedLabel(m_current_language, -+ Gtk.Align.CENTER, -+ TravelDirection.BACKWARD); -+ } else { -+ label = new EPaddedLabel("", Gtk.Align.CENTER); -+ } - Gtk.Button button = new Gtk.Button(); - button.add(label); - m_vbox.add(button); - button.show_all(); -- button.button_press_event.connect((e) => { -- m_category_active_index = 0; -- m_current_category_type = CategoryType.LANG; -- show_category_list(); -- return true; -- }); -+ if (m_current_category_type == CategoryType.EMOJI) { -+ button.button_press_event.connect((e) => { -+ m_category_active_index = 0; -+ m_current_category_type = CategoryType.LANG; -+ show_category_list(); -+ return true; -+ }); -+ } else if (m_current_category_type == CategoryType.LANG) { -+ button.button_press_event.connect((e) => { -+ m_category_active_index = 0; -+ m_current_category_type = CategoryType.EMOJI; -+ show_category_list(); -+ return true; -+ }); -+ } - - m_vbox.add(m_scrolled_window); - Gtk.Viewport viewport = new Gtk.Viewport(null, null); -@@ -523,21 +597,19 @@ class IBusEmojier : Gtk.Window { - } - } else if (m_current_category_type == CategoryType.LANG) { - m_list_box.row_activated.connect((box, gtkrow) => { -- m_category_active_index = 0; -- EBoxRow row = gtkrow as EBoxRow; -- if (m_current_lang != row.id) { -- m_current_lang = row.id; -- reload_emoji_dict(); -- } -- m_current_category_type = CategoryType.EMOJI; -- show_category_list(); -+ activated_language(gtkrow as EBoxRow); - }); - uint n = 1; -+ string prev_language = null; - foreach (unowned string id in m_lang_list) { -- string selected_language = IBus.get_language_name(id); -- EBoxRow row = new EBoxRow("", id); -+ string language = IBus.get_language_name(id); -+ if (prev_language == language) -+ language = "%s (%s)".printf(language, id); -+ else -+ prev_language = language; -+ EBoxRow row = new EBoxRow(language, id); - EPaddedLabel widget = -- new EPaddedLabel(selected_language, Gtk.Align.CENTER); -+ new EPaddedLabel(language, Gtk.Align.CENTER); - row.add(widget); - m_list_box.add(row); - if (n++ == m_category_active_index) -@@ -573,27 +645,6 @@ class IBusEmojier : Gtk.Window { - show_candidate_panel(); - } - -- private void label_set_active_color(Gtk.Label label) { -- unowned string text = label.get_text(); -- Pango.AttrList attrs = new Pango.AttrList(); -- Pango.Attribute pango_attr = Pango.attr_foreground_new( -- (uint16)(m_selected_fg_color.red * uint16.MAX), -- (uint16)(m_selected_fg_color.green * uint16.MAX), -- (uint16)(m_selected_fg_color.blue * uint16.MAX)); -- pango_attr.start_index = 0; -- pango_attr.end_index = text.char_count(); -- attrs.insert((owned)pango_attr); -- -- pango_attr = Pango.attr_background_new( -- (uint16)(m_selected_bg_color.red * uint16.MAX), -- (uint16)(m_selected_bg_color.green * uint16.MAX), -- (uint16)(m_selected_bg_color.blue * uint16.MAX)); -- pango_attr.start_index = 0; -- pango_attr.end_index = text.char_count(); -- attrs.insert((owned)pango_attr); -- label.set_attributes(attrs); -- } -- - private void show_arrow_buttons() { - Gtk.Button next_button = new Gtk.Button(); - next_button.clicked.connect(() => { -@@ -709,10 +760,17 @@ class IBusEmojier : Gtk.Window { - }); - } - EGrid grid = new EGrid(); -+ grid.set_row_spacing(5); -+ grid.set_column_spacing(5); -+ grid.set_border_width(2); - int n = 0; - for (uint i = page_start_pos; i < page_end_pos; i++) { - IBus.Text candidate = m_lookup_table.get_candidate(i); -- Gtk.Label label = new Gtk.Label(candidate.text); -+ Gtk.Label label; -+ if (i == cursor) -+ label = new ESelectedLabel(candidate.text) as Gtk.Label; -+ else -+ label = new EWhiteLabel(candidate.text) as Gtk.Label; - string emoji_font = m_emoji_font; - if (candidate.text.char_count() > 2) { - Pango.FontDescription font_desc = -@@ -726,9 +784,6 @@ class IBusEmojier : Gtk.Window { - label.set_markup(markup); - label.set_halign(Gtk.Align.FILL); - label.set_valign(Gtk.Align.FILL); -- if (i == cursor) { -- label_set_active_color(label); -- } - Gtk.EventBox candidate_ebox = new Gtk.EventBox(); - candidate_ebox.add(label); - // Make a copy of i to workaround a bug in vala. -@@ -738,6 +793,23 @@ class IBusEmojier : Gtk.Window { - candidate_clicked(index, e.button, e.state); - return true; - }); -+ // m_enter_notify_enable is added because -+ // enter_notify_event conflicts with keyboard operations. -+ if (m_enter_notify_enable) { -+ candidate_ebox.enter_notify_event.connect((e) => { -+ m_lookup_table.set_cursor_pos(index); -+ if (m_entry_notify_show_id > 0) { -+ GLib.Source.remove(m_entry_notify_show_id); -+ } -+ // If timeout is not added, memory leak happens and -+ // button_press_event signal does not work above. -+ m_entry_notify_show_id = GLib.Timeout.add(100, () => { -+ show_candidate_panel(); -+ return false; -+ }); -+ return true; -+ }); -+ } - grid.attach(candidate_ebox, - n % (int)EMOJI_GRID_PAGE, n / (int)EMOJI_GRID_PAGE, - 1, 1); -@@ -797,6 +869,7 @@ class IBusEmojier : Gtk.Window { - } - - private void hide_candidate_panel() { -+ m_enter_notify_enable = true; - m_candidate_panel_is_visible = false; - if (m_loop.is_running()) - show_category_list(); -@@ -841,6 +914,7 @@ class IBusEmojier : Gtk.Window { - } - - private void candidate_panel_cursor_down() { -+ m_enter_notify_enable = false; - uint ncandidates = m_lookup_table.get_number_of_candidates(); - uint cursor = m_lookup_table.get_cursor_pos(); - if ((cursor + EMOJI_GRID_PAGE) < ncandidates) { -@@ -854,6 +928,7 @@ class IBusEmojier : Gtk.Window { - } - - private void candidate_panel_cursor_up() { -+ m_enter_notify_enable = false; - int ncandidates = (int)m_lookup_table.get_number_of_candidates(); - int cursor = (int)m_lookup_table.get_cursor_pos(); - int highest_pos = -@@ -891,6 +966,7 @@ class IBusEmojier : Gtk.Window { - m_input_context_path = input_context_path; - m_candidate_panel_is_visible = false; - m_result = null; -+ m_enter_notify_enable = true; - - /* Let gtk recalculate the window size. */ - resize(1, 1); -@@ -1011,7 +1087,10 @@ class IBusEmojier : Gtk.Window { - } else if (m_category_active_index > 0) { - Gtk.ListBoxRow gtkrow = m_list_box.get_selected_row(); - EBoxRow row = gtkrow as EBoxRow; -- show_emoji_for_category(row); -+ if (m_current_category_type == CategoryType.EMOJI) -+ show_emoji_for_category(row); -+ else if (m_current_category_type == CategoryType.LANG) -+ activated_language(row); - } - return true; - case Gdk.Key.BackSpace: -@@ -1026,6 +1105,7 @@ class IBusEmojier : Gtk.Window { - break; - } - if (m_candidate_panel_is_visible) { -+ m_enter_notify_enable = false; - m_lookup_table.cursor_down(); - show_candidate_panel(); - } -@@ -1035,6 +1115,7 @@ class IBusEmojier : Gtk.Window { - return true; - case Gdk.Key.Right: - if (m_candidate_panel_is_visible) { -+ m_enter_notify_enable = false; - m_lookup_table.cursor_down(); - show_candidate_panel(); - return true; -@@ -1042,6 +1123,7 @@ class IBusEmojier : Gtk.Window { - break; - case Gdk.Key.Left: - if (m_candidate_panel_is_visible) { -+ m_enter_notify_enable = false; - m_lookup_table.cursor_up(); - show_candidate_panel(); - return true; -@@ -1061,6 +1143,7 @@ class IBusEmojier : Gtk.Window { - return true; - case Gdk.Key.Page_Down: - if (m_candidate_panel_is_visible) { -+ m_enter_notify_enable = false; - m_lookup_table.page_down(); - show_candidate_panel(); - return true; -@@ -1068,6 +1151,7 @@ class IBusEmojier : Gtk.Window { - break; - case Gdk.Key.Page_Up: - if (m_candidate_panel_is_visible) { -+ m_enter_notify_enable = false; - m_lookup_table.page_up(); - show_candidate_panel(); - return true; --- -2.7.4 - -From 31ed31e5303c3946f53b035a63d76f5c1e3cd84a Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Mon, 13 Mar 2017 11:43:09 +0900 -Subject: [PATCH] src: Fix ibus_emoji_dict_load() - -R=Shawn.P.Huang@gmail.com - -Review URL: https://codereview.appspot.com/320350043 ---- - configure.ac | 3 ++- - src/emoji-parser.c | 6 ++++-- - src/ibusemoji.c | 6 +++--- - 3 files changed, 9 insertions(+), 6 deletions(-) - -diff --git a/configure.ac b/configure.ac -index 027c027..369485c 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -620,7 +620,8 @@ fi - - AC_ARG_WITH(emoji-json-file, - AS_HELP_STRING([--with-emoji-json-file[=DIR/emoji.json]], -- [Set emoji.json. (default: "/usr/lib/node_modules/emojione/emoji.json")]), -+ [Set emoji.json. (default: "/usr/lib/node_modules/emojione/emoji.json") -+ You can get emoji.json with "npm install -g emojione".]), - EMOJI_JSON_FILE=$with_emoji_json_file, - EMOJI_JSON_FILE="/usr/lib/node_modules/emojione/emoji.json" - ) -diff --git a/src/emoji-parser.c b/src/emoji-parser.c -index c5af42c..5965309 100644 ---- a/src/emoji-parser.c -+++ b/src/emoji-parser.c -@@ -1,7 +1,7 @@ - /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ - /* vim:set et sts=4: */ - /* ibus - The Input Bus -- * Copyright (C) 2016 Takao Fujiwara -+ * Copyright (C) 2016-2017 Takao Fujiwara - * Copyright (C) 2016 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or -@@ -20,9 +20,11 @@ - * USA - */ - --/* Convert ../data/annotations/en.xml and -+/* Convert /usr/share/unicode/cldr/common/annotations/*.xml and - * /usr/lib/node_modules/emojione/emoji.json - * to the dictionary file which look up the Emoji from the annotation. -+ * Get *.xml from https://github.com/fujiwarat/cldr-emoji-annotation -+ * or http://www.unicode.org/repos/cldr/trunk/common/annotations . - * Get emoji.json with 'npm install -g emojione'. - * en.xml is used for the Unicode annotations and emoji.json is used - * for the aliases_ascii, e.g. ":)", and category, e.g. "people". -diff --git a/src/ibusemoji.c b/src/ibusemoji.c -index 8d88704..996d136 100644 ---- a/src/ibusemoji.c -+++ b/src/ibusemoji.c -@@ -431,10 +431,10 @@ ibus_emoji_dict_load (const gchar *path) - GHashTable *dict = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, -- free_dict_words); -+ g_object_unref); - - for (l = list; l; l = l->next) { -- IBusEmojiData *data = list->data; -+ IBusEmojiData *data = l->data; - if (!IBUS_IS_EMOJI_DATA (data)) { - g_warning ("Your dict format is no longer supported.\n" - "Need to create the dictionaries again."); -@@ -442,7 +442,7 @@ ibus_emoji_dict_load (const gchar *path) - } - g_hash_table_insert (dict, - g_strdup (ibus_emoji_data_get_emoji (data)), -- data); -+ g_object_ref_sink (data)); - } - - g_slist_free (list); --- -2.9.3 - -From c580845167b54ccab76d8b72bdc797ef8d64d554 Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Mon, 13 Mar 2017 11:58:06 +0900 -Subject: [PATCH] ui/gtk3: Fix emoji text entry cusor position - -Focus on emoji text entry by default -Remove internal text buffer and use Gtk.Entry buffer instead. -Implement cusor left, right, home, end on emoji annotation preedit. - -Review URL: https://codereview.appspot.com/313710043 ---- - ui/gtk3/emojier.vala | 104 +++++++++++++++++++++++++++++++++------------------ - 1 file changed, 68 insertions(+), 36 deletions(-) - -diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala -index bc1eff4..8cecea8 100644 ---- a/ui/gtk3/emojier.vala -+++ b/ui/gtk3/emojier.vala -@@ -75,7 +75,10 @@ class IBusEmojier : Gtk.Window { - row_homogeneous : false, - vexpand : true, - halign : Gtk.Align.FILL, -- valign : Gtk.Align.FILL -+ valign : Gtk.Align.FILL, -+ row_spacing : 5, -+ column_spacing : 5, -+ border_width : 2 - ); - } - } -@@ -190,7 +193,6 @@ class IBusEmojier : Gtk.Window { - private CategoryType m_current_category_type = CategoryType.EMOJI; - private bool m_is_running = false; - private string m_input_context_path = ""; -- private GLib.StringBuilder m_buffer_string; - private GLib.MainLoop? m_loop; - private string? m_result; - private GLib.SList m_lang_list; -@@ -288,11 +290,9 @@ class IBusEmojier : Gtk.Window { - m_entry.set_placeholder_text(_("Type annotation or choose emoji")); - m_vbox.add(m_entry); - m_entry.changed.connect(() => { -- m_buffer_string.assign(m_entry.get_text()); -- update_cadidate_window(); -+ update_candidate_window(); - }); - m_entry.icon_release.connect((icon_pos, event) => { -- m_buffer_string.erase(); - hide_candidate_panel(); - }); - -@@ -303,7 +303,6 @@ class IBusEmojier : Gtk.Window { - Atk.Object obj = m_entry.get_accessible(); - obj.set_role (Atk.Role.STATUSBAR); - -- m_buffer_string = new StringBuilder(); - grab_focus(); - - // The constructor of IBus.LookupTable does not support more than -@@ -680,11 +679,16 @@ class IBusEmojier : Gtk.Window { - buttons_hbox.show_all(); - } - -- private bool check_unicode_point(bool check_xdigit_only) { -- m_unicode_point = null; -+ private bool check_unicode_point(string? annotation=null) { -+ bool check_xdigit_only = true; -+ if (annotation == null) { -+ annotation = m_entry.get_text(); -+ m_unicode_point = null; -+ check_xdigit_only = false; -+ } - GLib.StringBuilder buff = new GLib.StringBuilder(); -- for (int i = 0; i < m_buffer_string.str.char_count(); i++) { -- unichar ch = m_buffer_string.str.get_char(i); -+ for (int i = 0; i < annotation.char_count(); i++) { -+ unichar ch = annotation.get_char(i); - if (ch == 0) - return false; - if (!ch.isxdigit()) -@@ -704,7 +708,7 @@ class IBusEmojier : Gtk.Window { - return true; - } - -- public void update_cadidate_window() { -+ public void update_candidate_window() { - string annotation = m_entry.get_text(); - if (annotation.length == 0) { - hide_candidate_panel(); -@@ -716,7 +720,7 @@ class IBusEmojier : Gtk.Window { - return; - } - // Call check_unicode_point() to get m_unicode_point -- check_unicode_point(false); -+ check_unicode_point(); - unowned GLib.SList? emojis = - m_annotation_to_emojis_dict.lookup(annotation); - if (emojis == null && m_unicode_point == null) { -@@ -725,7 +729,7 @@ class IBusEmojier : Gtk.Window { - } - m_lookup_table.clear(); - // Call check_unicode_point() to update m_lookup_table -- check_unicode_point(false); -+ check_unicode_point(); - foreach (unowned string emoji in emojis) { - IBus.Text text = new IBus.Text.from_string(emoji); - m_lookup_table.append_candidate(text); -@@ -760,9 +764,6 @@ class IBusEmojier : Gtk.Window { - }); - } - EGrid grid = new EGrid(); -- grid.set_row_spacing(5); -- grid.set_column_spacing(5); -- grid.set_border_width(2); - int n = 0; - for (uint i = page_start_pos; i < page_end_pos; i++) { - IBus.Text candidate = m_lookup_table.get_candidate(i); -@@ -878,14 +879,11 @@ class IBusEmojier : Gtk.Window { - private bool if_in_range_of_lookup(uint keyval) { - if (!m_candidate_panel_is_visible) - return false; -- string backup_annotation = m_buffer_string.str.dup(); -+ StringBuilder buffer_string = new StringBuilder(m_entry.get_text()); - unichar ch = IBus.keyval_to_unicode (keyval); -- m_buffer_string.append_unichar(ch); -- if (check_unicode_point(true)) { -- m_buffer_string.assign(backup_annotation); -+ buffer_string.append_unichar(ch); -+ if (check_unicode_point(buffer_string.str)) - return false; -- } -- m_buffer_string.assign(backup_annotation); - if (keyval < Gdk.Key.@0 || keyval > Gdk.Key.@9) - return false; - if (keyval == Gdk.Key.@0) -@@ -958,6 +956,18 @@ class IBusEmojier : Gtk.Window { - show_category_list(); - } - -+ private void entry_enter_keyval(uint keyval) { -+ unichar ch = IBus.keyval_to_unicode(keyval); -+ if (!ch.isgraph()) -+ return; -+ string str = ch.to_string(); -+ -+ // what gtk_entry_commit_cb() do -+ int pos = m_entry.get_position(); -+ m_entry.insert_text(str, -1, ref pos); -+ m_entry.set_position(pos); -+ } -+ - public string run(Gdk.Event event, - string input_context_path) { - assert (m_loop == null); -@@ -972,7 +982,7 @@ class IBusEmojier : Gtk.Window { - resize(1, 1); - - m_entry.set_text(""); -- m_buffer_string.erase(); -+ m_entry.grab_focus(); - - Gdk.Device device = event.get_device(); - if (device == null) { -@@ -1070,12 +1080,12 @@ class IBusEmojier : Gtk.Window { - m_current_category_type = CategoryType.EMOJI; - show_candidate_panel(); - return true; -- } else if (m_buffer_string.str.length == 0) { -+ } else if (m_entry.get_text().length == 0) { - m_loop.quit(); - hide_candidate_panel(); - return true; - } -- m_buffer_string.erase(); -+ m_entry.delete_text(0, -1); - break; - case Gdk.Key.Return: - if (m_candidate_panel_is_visible) { -@@ -1094,14 +1104,14 @@ class IBusEmojier : Gtk.Window { - } - return true; - case Gdk.Key.BackSpace: -- if (m_buffer_string.len > 0) -- m_buffer_string.erase(m_buffer_string.len - 1); -+ if (m_entry.get_text().len() > 0) { -+ GLib.Signal.emit_by_name(m_entry, "backspace"); -+ } - break; - case Gdk.Key.space: - case Gdk.Key.KP_Space: - if ((modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) { -- unichar ch = IBus.keyval_to_unicode (keyval); -- m_buffer_string.append_unichar(ch); -+ entry_enter_keyval(keyval); - break; - } - if (m_candidate_panel_is_visible) { -@@ -1120,6 +1130,12 @@ class IBusEmojier : Gtk.Window { - show_candidate_panel(); - return true; - } -+ if (m_entry.get_text().len() > 0) { -+ GLib.Signal.emit_by_name(m_entry, "move-cursor", -+ Gtk.MovementStep.VISUAL_POSITIONS, -+ 1, false); -+ return true; -+ } - break; - case Gdk.Key.Left: - if (m_candidate_panel_is_visible) { -@@ -1128,6 +1144,12 @@ class IBusEmojier : Gtk.Window { - show_candidate_panel(); - return true; - } -+ if (m_entry.get_text().len() > 0) { -+ GLib.Signal.emit_by_name(m_entry, "move-cursor", -+ Gtk.MovementStep.VISUAL_POSITIONS, -+ -1, false); -+ return true; -+ } - break; - case Gdk.Key.Down: - if (m_candidate_panel_is_visible) -@@ -1157,17 +1179,27 @@ class IBusEmojier : Gtk.Window { - return true; - } - break; -- default: -- unichar ch = IBus.keyval_to_unicode(keyval); -- if (!ch.isgraph()) -+ case Gdk.Key.Home: -+ if (m_entry.get_text().len() > 0) { -+ GLib.Signal.emit_by_name(m_entry, "move-cursor", -+ Gtk.MovementStep.DISPLAY_LINE_ENDS, -+ -1, false); -+ return true; -+ } -+ break; -+ case Gdk.Key.End: -+ if (m_entry.get_text().len() > 0) { -+ GLib.Signal.emit_by_name(m_entry, "move-cursor", -+ Gtk.MovementStep.DISPLAY_LINE_ENDS, -+ 1, false); - return true; -- m_buffer_string.append_unichar(ch); -+ } -+ break; -+ default: -+ entry_enter_keyval(keyval); - break; - } - -- string annotation = m_buffer_string.str; -- m_entry.set_text(annotation); -- - return true; - } - --- -2.9.3 - -From fbe3de1789c73a8a175a451b2a6b967f5d3c6514 Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Mon, 13 Mar 2017 12:08:21 +0900 -Subject: [PATCH] src: Update emoji-parser to treat tts as emoji - description - -unicode annotation xml files have a 'tts' attribute and seem -it is used as the emoji descriptions instead of emoji annotations. -This can show the translated descriptions. - -R=Shawn.P.Huang@gmail.com - -Review URL: https://codereview.appspot.com/319480043 ---- - src/emoji-parser.c | 26 +++++++++++++++++++------- - src/ibusemoji.c | 16 +++++++++++++--- - src/ibusemoji.h | 11 +++++++++++ - ui/gtk3/emojier.vala | 17 ++++++++++++++--- - 4 files changed, 57 insertions(+), 13 deletions(-) - -diff --git a/src/emoji-parser.c b/src/emoji-parser.c -index 5965309..8ff04f1 100644 ---- a/src/emoji-parser.c -+++ b/src/emoji-parser.c -@@ -43,6 +43,7 @@ struct _EmojiData { - GSList *annotations; - gboolean is_annotation; - gchar *description; -+ gboolean is_tts; - gchar *category; - GSList *list; - }; -@@ -97,6 +98,8 @@ update_emoji_list (EmojiData *data) - (GCopyFunc) g_strdup, - NULL)); - } -+ if (data->description) -+ ibus_emoji_data_set_description (emoji, data->description); - } else { - IBusEmojiData *emoji = - ibus_emoji_data_new ("emoji", -@@ -149,13 +152,12 @@ unicode_annotations_start_element_cb (GMarkupParseContext *context, - data->emoji = g_strdup (value); - } - } -- else if (g_strcmp0 (attribute, "tts") == 0) { -- GSList *duplicated = g_slist_find_custom (data->annotations, -- value, -- (GCompareFunc) g_strcmp0); -- if (duplicated == NULL) { -- data->annotations = g_slist_prepend (data->annotations, -- g_strdup (value)); -+ /* tts seems 'text to speach' and it would be a description -+ * instead of annotation. -+ */ -+ else if (g_strcmp0 (attribute, "type") == 0) { -+ if (g_strcmp0 (value, "tts") == 0) { -+ data->is_tts = TRUE; - } - } - } -@@ -177,6 +179,7 @@ unicode_annotations_end_element_cb (GMarkupParseContext *context, - - update_emoji_list (data); - data->is_annotation = FALSE; -+ data->is_tts = FALSE; - } - - void -@@ -194,6 +197,15 @@ unicode_annotations_text_cb (GMarkupParseContext *context, - g_assert (data != NULL); - if (!data->is_annotation) - return; -+ if (data->is_tts) { -+ if (data->description) { -+ g_warning ("Duplicated 'tts' is found: %s: %s", -+ data->description, text); -+ g_clear_pointer (&data->description, g_free); -+ } -+ data->description = g_strdup (text); -+ return; -+ } - annotations = g_strsplit (text, " | ", -1); - for (i = 0; (annotation = annotations[i]) != NULL; i++) { - GSList *duplicated = g_slist_find_custom (data->annotations, -diff --git a/src/ibusemoji.c b/src/ibusemoji.c -index 996d136..c61cd70 100644 ---- a/src/ibusemoji.c -+++ b/src/ibusemoji.c -@@ -29,7 +29,7 @@ - #include "ibusinternal.h" - - #define IBUS_EMOJI_DATA_MAGIC "IBusEmojiData" --#define IBUS_EMOJI_DATA_VERSION (1) -+#define IBUS_EMOJI_DATA_VERSION (2) - - enum { - PROP_0 = 0, -@@ -128,7 +128,7 @@ ibus_emoji_data_class_init (IBusEmojiDataClass *class) - "emoji description", - "The emoji description", - "", -- G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); -+ G_PARAM_READWRITE)); - - /** - * IBusEmojiData:category: -@@ -352,6 +352,16 @@ ibus_emoji_data_get_description (IBusEmojiData *emoji) - return emoji->priv->description; - } - -+void -+ibus_emoji_data_set_description (IBusEmojiData *emoji, -+ const gchar *description) -+{ -+ g_return_if_fail (IBUS_IS_EMOJI_DATA (emoji)); -+ -+ g_free (emoji->priv->description); -+ emoji->priv->description = g_strdup (description); -+} -+ - const gchar * - ibus_emoji_data_get_category (IBusEmojiData *emoji) - { -@@ -541,7 +551,7 @@ ibus_emoji_data_load (const gchar *path) - goto out_load_cache; - } - -- if (version != IBUS_EMOJI_DATA_VERSION) { -+ if (version > IBUS_EMOJI_DATA_VERSION) { - g_warning ("cache version is different: %u != %u", - version, IBUS_EMOJI_DATA_VERSION); - goto out_load_cache; -diff --git a/src/ibusemoji.h b/src/ibusemoji.h -index 3fd09a9..eb24fdd 100644 ---- a/src/ibusemoji.h -+++ b/src/ibusemoji.h -@@ -134,6 +134,17 @@ void ibus_emoji_data_set_annotations (IBusEmojiData *emoji, - const gchar * ibus_emoji_data_get_description (IBusEmojiData *emoji); - - /** -+ * ibus_emoji_data_set_description: -+ * @emoji : An #IBusEmojiData -+ * @description: An emoji description -+ * -+ * Sets the description in #IBusEmojiData. -+ */ -+void ibus_emoji_data_set_description (IBusEmojiData *emoji, -+ const gchar *description); -+ -+ -+/** - * ibus_emoji_data_get_category: - * @emoji : An #IBusEmojiData - * -diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala -index 8cecea8..5e126e9 100644 ---- a/ui/gtk3/emojier.vala -+++ b/ui/gtk3/emojier.vala -@@ -370,8 +370,14 @@ class IBusEmojier : Gtk.Window { - private void reload_emoji_dict() { - init_emoji_dict(); - make_emoji_dict("en"); -- if (m_current_lang_id != "en") -+ if (m_current_lang_id != "en") { -+ var lang_ids = m_current_lang_id.split("_"); -+ if (lang_ids.length > 1) { -+ string sub_id = lang_ids[0]; -+ make_emoji_dict(sub_id); -+ } - make_emoji_dict(m_current_lang_id); -+ } - loaded_emoji_dict(); - } - -@@ -393,8 +399,8 @@ class IBusEmojier : Gtk.Window { - if (emoji_list == null) - return; - foreach (IBus.EmojiData data in emoji_list) { -- update_annotation_to_emojis_dict(data); - update_emoji_to_data_dict(data, lang); -+ update_annotation_to_emojis_dict(data); - update_category_to_emojis_dict(data, lang); - } - GLib.List annotations = -@@ -434,11 +440,16 @@ class IBusEmojier : Gtk.Window { - unowned IBus.EmojiData? en_data = - m_emoji_to_data_dict.lookup(emoji); - if (en_data == null) { -- warning("No IBusEmojiData for English: %s".printf(emoji)); - m_emoji_to_data_dict.insert(emoji, data); - return; - } -+ string trans_description = data.get_description(); -+ en_data.set_description(trans_description); - unowned GLib.SList annotations = data.get_annotations(); -+ var words = trans_description.split(" "); -+ // If the description has less than 3 words, add it to annotations -+ if (words.length < 3) -+ annotations.append(trans_description); - unowned GLib.SList en_annotations - = en_data.get_annotations(); - foreach (string annotation in en_annotations) { --- -2.9.3 - -From ab6c38c192cdf22356cbf254b98fb5b3d9d9a680 Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Wed, 15 Mar 2017 11:48:24 +0900 -Subject: [PATCH] client/x11: Add XSetIOErrorHandler() for GNOME3 desktop - -When log into GNOME3 desktop immediately after the system is booted, -ibus-daemon is sometimes alive but ibus-x11 is dead after log out -the session. Because gdk_x_io_error() is called as the callback of -XSetIOErrorHandler() in gtk/gdk/x11/gdkmain-x11.c in ibus-x11. -Now I assume the callback is called in logout. - -BUG=https://github.com/ibus/ibus/issues/1907 - -Review URL: https://codereview.appspot.com/319490043 ---- - client/x11/main.c | 16 ++++++++++++++++ - 1 file changed, 16 insertions(+) - -diff --git a/client/x11/main.c b/client/x11/main.c -index a717a2c..159f430 100644 ---- a/client/x11/main.c -+++ b/client/x11/main.c -@@ -2,6 +2,7 @@ - /* vim:set et sts=4: */ - /* ibus - * Copyright (C) 2007-2015 Peng Huang -+ * Copyright (C) 2015-2017 Takao Fujiwara - * Copyright (C) 2007-2015 Red Hat, Inc. - * - * main.c: -@@ -1131,6 +1132,20 @@ _xerror_handler (Display *dpy, XErrorEvent *e) - return 1; - } - -+/* When log into GNOME3 desktop immediately after the system is booted, -+ * ibus-daemon is sometimes alive but ibus-x11 is dead after log out -+ * the session. Because gdk_x_io_error() is called as the callback of -+ * XSetIOErrorHandler() in gtk/gdk/x11/gdkmain-x11.c in ibus-x11. -+ * Now I assume the callback is called in logout. -+ */ -+static int -+_xerror_io_handler (Display *dpy) -+{ -+ if (_kill_daemon) -+ _atexit_cb (); -+ return 0; -+} -+ - int - main (int argc, char **argv) - { -@@ -1146,6 +1161,7 @@ main (int argc, char **argv) - - gtk_init (&argc, &argv); - XSetErrorHandler (_xerror_handler); -+ XSetIOErrorHandler (_xerror_io_handler); - - while (1) { - static struct option long_options [] = { --- -2.9.3 - -From 58f6140f427815adc947a5bb5c7dea4f3e315ae8 Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Wed, 15 Mar 2017 11:52:39 +0900 -Subject: [PATCH] ui/gtk3: Implement shortcut keys on emoji dialog - -- Implement Ctrl-f, Ctrl-b, Ctrl-n, Ctrl-p, Ctrl-h, Ctrh-e for - cursor movements; forward, back, next, previous, head, end - on emoji grid. -- Implement Ctrl-a and Shift+arrow for text selection on emoji annotation. -- Implement Ctrl-u to delete text on emoji annotation. -- Implement to delete a selected text on emoji annotation. -- Change to show page indices to candidate indices on emoji. -- Sorted emoji categories. -- Added timeout of m_enter_notify_enable = false to bring back mouse. - -R=Shawn.P.Huang@gmail.com - -Review URL: https://codereview.appspot.com/315700043 ---- - ui/gtk3/emojier.vala | 311 +++++++++++++++++++++++++++++++++++---------------- - 1 file changed, 215 insertions(+), 96 deletions(-) - -diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala -index 5e126e9..7da96c7 100644 ---- a/ui/gtk3/emojier.vala -+++ b/ui/gtk3/emojier.vala -@@ -214,6 +214,7 @@ class IBusEmojier : Gtk.Window { - private string[] m_favorites = {}; - private bool m_enter_notify_enable = true; - private uint m_entry_notify_show_id; -+ private uint m_entry_notify_disable_id; - - public signal void candidate_clicked(uint index, uint button, uint state); - public signal void loaded_emoji_dict(); -@@ -525,6 +526,18 @@ class IBusEmojier : Gtk.Window { - show_category_list(); - } - -+ private string get_title_string(string orig) { -+ StringBuilder buff = new StringBuilder(); -+ for (int i = 0; i < orig.char_count(); i++) { -+ unichar ch = orig.get_char(i); -+ if (i == 0) -+ buff.append_unichar(ch.toupper()); -+ else -+ buff.append_unichar(ch); -+ } -+ return buff.str; -+ } -+ - private void show_category_list() { - remove_all_children(); - m_scrolled_window = new EScrolledWindow(); -@@ -586,19 +599,14 @@ class IBusEmojier : Gtk.Window { - } - GLib.List categories = - m_category_to_emojis_dict.get_keys(); -+ categories.sort((a, b) => { -+ return GLib.strcmp(_(a), _(b)); -+ }); - foreach (unowned string category in categories) { - EBoxRow row = new EBoxRow(category); - string locale_category = _(category); -- StringBuilder capital_category = new StringBuilder(); -- for (int i = 0; i < locale_category.char_count(); i++) { -- unichar ch = locale_category.get_char(i); -- if (i == 0) -- capital_category.append_unichar(ch.toupper()); -- else -- capital_category.append_unichar(ch); -- } - EPaddedLabel widget = -- new EPaddedLabel(capital_category.str, -+ new EPaddedLabel(get_title_string(locale_category), - Gtk.Align.CENTER); - row.add(widget); - m_list_box.add(row); -@@ -650,7 +658,7 @@ class IBusEmojier : Gtk.Window { - IBus.Text text = new IBus.Text.from_string(emoji); - m_lookup_table.append_candidate(text); - } -- m_backward = row.text; -+ m_backward = get_title_string(row.text); - } - show_candidate_panel(); - } -@@ -759,9 +767,7 @@ class IBusEmojier : Gtk.Window { - uint page_end_pos = uint.min(page_start_pos + page_size, ncandidates); - if (m_backward != null) { - string backward_desc = -- "%s (%u / %u)".printf(m_backward, -- cursor / page_size + 1, -- ncandidates / page_size + 1); -+ "%s (%u / %u)".printf(m_backward, cursor, ncandidates - 1); - EPaddedLabel label = new EPaddedLabel(backward_desc, - Gtk.Align.CENTER, - TravelDirection.BACKWARD); -@@ -805,23 +811,23 @@ class IBusEmojier : Gtk.Window { - candidate_clicked(index, e.button, e.state); - return true; - }); -- // m_enter_notify_enable is added because -- // enter_notify_event conflicts with keyboard operations. -- if (m_enter_notify_enable) { -- candidate_ebox.enter_notify_event.connect((e) => { -- m_lookup_table.set_cursor_pos(index); -- if (m_entry_notify_show_id > 0) { -+ candidate_ebox.enter_notify_event.connect((e) => { -+ // m_enter_notify_enable is added because -+ // enter_notify_event conflicts with keyboard operations. -+ if (!m_enter_notify_enable) -+ return true; -+ m_lookup_table.set_cursor_pos(index); -+ if (m_entry_notify_show_id > 0) { - GLib.Source.remove(m_entry_notify_show_id); -- } -- // If timeout is not added, memory leak happens and -- // button_press_event signal does not work above. -- m_entry_notify_show_id = GLib.Timeout.add(100, () => { -+ } -+ // If timeout is not added, memory leak happens and -+ // button_press_event signal does not work above. -+ m_entry_notify_show_id = GLib.Timeout.add(100, () => { - show_candidate_panel(); - return false; -- }); -- return true; - }); -- } -+ return true; -+ }); - grid.attach(candidate_ebox, - n % (int)EMOJI_GRID_PAGE, n / (int)EMOJI_GRID_PAGE, - 1, 1); -@@ -844,16 +850,23 @@ class IBusEmojier : Gtk.Window { - widget.show_all(); - return; - } -- unowned IBus.EmojiData data = -+ unowned IBus.EmojiData? data = - m_emoji_to_data_dict.lookup(candidate.text); -- unowned string description = data.get_description(); -- if (description != "") { -+ if (data == null) { -+ // TODO: Provide a description for the favorite emojis. - EPaddedLabel widget = new EPaddedLabel( -- _("Description: %s").printf(description), -+ _("Description: %s").printf(_("None")), - Gtk.Align.START); - m_vbox.add(widget); - widget.show_all(); -+ return; - } -+ unowned string description = data.get_description(); -+ EPaddedLabel desc_widget = new EPaddedLabel( -+ _("Description: %s").printf(description), -+ Gtk.Align.START); -+ m_vbox.add(desc_widget); -+ desc_widget.show_all(); - unowned GLib.SList? annotations = - data.get_annotations(); - GLib.StringBuilder buff = new GLib.StringBuilder(); -@@ -922,8 +935,21 @@ class IBusEmojier : Gtk.Window { - m_result = text.text; - } - -- private void candidate_panel_cursor_down() { -+ private void enter_notify_disable_with_timer() { -+ // Enable keyboard operation and disable mouse operation. - m_enter_notify_enable = false; -+ if (m_entry_notify_disable_id > 0) { -+ GLib.Source.remove(m_entry_notify_disable_id); -+ } -+ // Bring back the mouse operation after a timeout. -+ m_entry_notify_show_id = GLib.Timeout.add(100, () => { -+ m_enter_notify_enable = true; -+ return false; -+ }); -+ } -+ -+ private void candidate_panel_cursor_down() { -+ enter_notify_disable_with_timer(); - uint ncandidates = m_lookup_table.get_number_of_candidates(); - uint cursor = m_lookup_table.get_cursor_pos(); - if ((cursor + EMOJI_GRID_PAGE) < ncandidates) { -@@ -937,11 +963,11 @@ class IBusEmojier : Gtk.Window { - } - - private void candidate_panel_cursor_up() { -- m_enter_notify_enable = false; -+ enter_notify_disable_with_timer(); - int ncandidates = (int)m_lookup_table.get_number_of_candidates(); - int cursor = (int)m_lookup_table.get_cursor_pos(); - int highest_pos = -- (ncandidates / (int)EMOJI_GRID_PAGE * (int)EMOJI_GRID_PAGE) -+ ((ncandidates - 1)/ (int)EMOJI_GRID_PAGE * (int)EMOJI_GRID_PAGE) - + (cursor % (int)EMOJI_GRID_PAGE); - if ((cursor - (int)EMOJI_GRID_PAGE) >= 0) { - m_lookup_table.set_cursor_pos(cursor - (int)EMOJI_GRID_PAGE); -@@ -967,13 +993,119 @@ class IBusEmojier : Gtk.Window { - show_category_list(); - } - -+ private bool key_press_cursor_horizontal(uint keyval, -+ uint modifiers) { -+ assert (keyval == Gdk.Key.Left || keyval == Gdk.Key.Right); -+ -+ if (m_candidate_panel_is_visible) { -+ enter_notify_disable_with_timer(); -+ if (keyval == Gdk.Key.Left) -+ m_lookup_table.cursor_up(); -+ else if (keyval == Gdk.Key.Right) -+ m_lookup_table.cursor_down(); -+ show_candidate_panel(); -+ } else if (m_entry.get_text().len() > 0) { -+ int step = 0; -+ if (keyval == Gdk.Key.Left) -+ step = -1; -+ else if (keyval == Gdk.Key.Right) -+ step = 1; -+ GLib.Signal.emit_by_name( -+ m_entry, "move-cursor", -+ Gtk.MovementStep.VISUAL_POSITIONS, -+ step, -+ (modifiers & Gdk.ModifierType.SHIFT_MASK) != 0 -+ ? true : false); -+ } else { -+ // For Gdk.Key.f and Gdk.Key.b -+ if (keyval == Gdk.Key.Left) -+ keyval = Gdk.Key.Up; -+ else if (keyval == Gdk.Key.Right) -+ keyval = Gdk.Key.Down; -+ category_list_cursor_move(keyval); -+ } -+ return true; -+ } -+ -+ private bool key_press_cursor_vertical(uint keyval) { -+ assert (keyval == Gdk.Key.Down || keyval == Gdk.Key.Up); -+ -+ if (m_candidate_panel_is_visible) { -+ if (keyval == Gdk.Key.Down) -+ candidate_panel_cursor_down(); -+ else if (keyval == Gdk.Key.Up) -+ candidate_panel_cursor_up(); -+ } else { -+ category_list_cursor_move(keyval); -+ } -+ return true; -+ } -+ -+ private bool key_press_cursor_home_end(uint keyval, -+ uint modifiers) { -+ assert (keyval == Gdk.Key.Home || keyval == Gdk.Key.End); -+ -+ if (m_candidate_panel_is_visible) { -+ enter_notify_disable_with_timer(); -+ if (keyval == Gdk.Key.Home) { -+ m_lookup_table.set_cursor_pos(0); -+ } else if (keyval == Gdk.Key.End) { -+ uint ncandidates = m_lookup_table.get_number_of_candidates(); -+ m_lookup_table.set_cursor_pos(ncandidates - 1); -+ } -+ show_candidate_panel(); -+ return true; -+ } -+ if (m_entry.get_text().len() > 0) { -+ int step = 0; -+ if (keyval == Gdk.Key.Home) -+ step = -1; -+ else if (keyval == Gdk.Key.End) -+ step = 1; -+ GLib.Signal.emit_by_name( -+ m_entry, "move-cursor", -+ Gtk.MovementStep.DISPLAY_LINE_ENDS, -+ step, -+ (modifiers & Gdk.ModifierType.SHIFT_MASK) != 0 -+ ? true : false); -+ return true; -+ } -+ return false; -+ } -+ -+ private bool key_press_cursor_escape() { -+ if (m_candidate_panel_is_visible) { -+ hide_candidate_panel(); -+ return true; -+ } else if (m_current_category_type == CategoryType.LANG) { -+ m_current_category_type = CategoryType.EMOJI; -+ show_candidate_panel(); -+ return true; -+ } else if (m_entry.get_text().length == 0) { -+ m_loop.quit(); -+ hide_candidate_panel(); -+ return true; -+ } -+ m_entry.delete_text(0, -1); -+ return true; -+ } -+ - private void entry_enter_keyval(uint keyval) { - unichar ch = IBus.keyval_to_unicode(keyval); -- if (!ch.isgraph()) -+ if (ch.iscntrl()) - return; - string str = ch.to_string(); - - // what gtk_entry_commit_cb() do -+ if (m_entry.get_selection_bounds(null, null)) { -+ m_entry.delete_selection(); -+ } else { -+ if (m_entry.get_overwrite_mode()) { -+ uint text_length = m_entry.get_buffer().get_length(); -+ if (m_entry.cursor_position < text_length) -+ m_entry.delete_from_cursor(Gtk.DeleteType.CHARS, 1); -+ } -+ } - int pos = m_entry.get_position(); - m_entry.insert_text(str, -1, ref pos); - m_entry.set_position(pos); -@@ -1084,19 +1216,8 @@ class IBusEmojier : Gtk.Window { - } - switch (keyval) { - case Gdk.Key.Escape: -- if (m_candidate_panel_is_visible) { -- hide_candidate_panel(); -- return true; -- } else if (m_current_category_type == CategoryType.LANG) { -- m_current_category_type = CategoryType.EMOJI; -- show_candidate_panel(); -+ if (key_press_cursor_escape()) - return true; -- } else if (m_entry.get_text().length == 0) { -- m_loop.quit(); -- hide_candidate_panel(); -- return true; -- } -- m_entry.delete_text(0, -1); - break; - case Gdk.Key.Return: - if (m_candidate_panel_is_visible) { -@@ -1126,7 +1247,7 @@ class IBusEmojier : Gtk.Window { - break; - } - if (m_candidate_panel_is_visible) { -- m_enter_notify_enable = false; -+ enter_notify_disable_with_timer(); - m_lookup_table.cursor_down(); - show_candidate_panel(); - } -@@ -1135,48 +1256,20 @@ class IBusEmojier : Gtk.Window { - } - return true; - case Gdk.Key.Right: -- if (m_candidate_panel_is_visible) { -- m_enter_notify_enable = false; -- m_lookup_table.cursor_down(); -- show_candidate_panel(); -- return true; -- } -- if (m_entry.get_text().len() > 0) { -- GLib.Signal.emit_by_name(m_entry, "move-cursor", -- Gtk.MovementStep.VISUAL_POSITIONS, -- 1, false); -- return true; -- } -- break; -+ key_press_cursor_horizontal(keyval, modifiers); -+ return true; - case Gdk.Key.Left: -- if (m_candidate_panel_is_visible) { -- m_enter_notify_enable = false; -- m_lookup_table.cursor_up(); -- show_candidate_panel(); -- return true; -- } -- if (m_entry.get_text().len() > 0) { -- GLib.Signal.emit_by_name(m_entry, "move-cursor", -- Gtk.MovementStep.VISUAL_POSITIONS, -- -1, false); -- return true; -- } -- break; -+ key_press_cursor_horizontal(keyval, modifiers); -+ return true; - case Gdk.Key.Down: -- if (m_candidate_panel_is_visible) -- candidate_panel_cursor_down(); -- else -- category_list_cursor_move(Gdk.Key.Down); -+ key_press_cursor_vertical(keyval); - return true; - case Gdk.Key.Up: -- if (m_candidate_panel_is_visible) -- candidate_panel_cursor_up(); -- else -- category_list_cursor_move(Gdk.Key.Up); -+ key_press_cursor_vertical(keyval); - return true; - case Gdk.Key.Page_Down: - if (m_candidate_panel_is_visible) { -- m_enter_notify_enable = false; -+ enter_notify_disable_with_timer(); - m_lookup_table.page_down(); - show_candidate_panel(); - return true; -@@ -1184,33 +1277,59 @@ class IBusEmojier : Gtk.Window { - break; - case Gdk.Key.Page_Up: - if (m_candidate_panel_is_visible) { -- m_enter_notify_enable = false; -+ enter_notify_disable_with_timer(); - m_lookup_table.page_up(); - show_candidate_panel(); - return true; - } - break; - case Gdk.Key.Home: -- if (m_entry.get_text().len() > 0) { -- GLib.Signal.emit_by_name(m_entry, "move-cursor", -- Gtk.MovementStep.DISPLAY_LINE_ENDS, -- -1, false); -+ if (key_press_cursor_home_end(keyval, modifiers)) - return true; -- } - break; - case Gdk.Key.End: -- if (m_entry.get_text().len() > 0) { -- GLib.Signal.emit_by_name(m_entry, "move-cursor", -- Gtk.MovementStep.DISPLAY_LINE_ENDS, -- 1, false); -+ if (key_press_cursor_home_end(keyval, modifiers)) - return true; -- } -- break; -- default: -- entry_enter_keyval(keyval); - break; - } - -+ if ((modifiers & Gdk.ModifierType.CONTROL_MASK) != 0) { -+ switch (keyval) { -+ case Gdk.Key.f: -+ key_press_cursor_horizontal(Gdk.Key.Right, modifiers); -+ return true; -+ case Gdk.Key.b: -+ key_press_cursor_horizontal(Gdk.Key.Left, modifiers); -+ return true; -+ case Gdk.Key.n: -+ key_press_cursor_vertical(Gdk.Key.Down); -+ return true; -+ case Gdk.Key.p: -+ key_press_cursor_vertical(Gdk.Key.Up); -+ return true; -+ case Gdk.Key.h: -+ if (key_press_cursor_home_end(Gdk.Key.Home, modifiers)) -+ return true; -+ break; -+ case Gdk.Key.e: -+ if (key_press_cursor_home_end(Gdk.Key.End, modifiers)) -+ return true; -+ break; -+ case Gdk.Key.u: -+ if (key_press_cursor_escape()) -+ return true; -+ break; -+ case Gdk.Key.a: -+ if (m_entry.get_text().len() > 0) { -+ m_entry.select_region(0, -1); -+ return true; -+ } -+ break; -+ } -+ return false; -+ } -+ -+ entry_enter_keyval(keyval); - return true; - } - --- -2.9.3 - -From 50e344afaffc29e626dbc27747a1aeee6cccafdf Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Fri, 17 Mar 2017 12:08:50 +0900 -Subject: [PATCH] ui/gtk3: Enable strcasecmp to match emoji annotation - -Users can type capital annotations. -Also shows emoji annotations in the status bar if the -typing unicode point matches a emoji character. - -Review URL: https://codereview.appspot.com/314640043 ---- - ui/gtk3/emojier.vala | 97 +++++++++++++++++++++++++++++++++++----------------- - 1 file changed, 65 insertions(+), 32 deletions(-) - -diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala -index 7da96c7..b1dc50c 100644 ---- a/ui/gtk3/emojier.vala -+++ b/ui/gtk3/emojier.vala -@@ -432,10 +432,45 @@ class IBusEmojier : Gtk.Window { - } - } - -+ private string utf8_down(string str) { -+ GLib.StringBuilder buff = new GLib.StringBuilder(); -+ int length = str.char_count(); -+ for (int i = 0; i < length; i++) { -+ buff.append_unichar(str.get_char(0).tolower()); -+ str = str.next_char(); -+ } -+ return buff.str; -+ } -+ -+ private string utf8_title(string str) { -+ StringBuilder buff = new StringBuilder(); -+ int length = str.char_count(); -+ for (int i = 0; i < length; i++) { -+ unichar ch = str.get_char(0); -+ if (i == 0) -+ buff.append_unichar(ch.toupper()); -+ else -+ buff.append_unichar(ch); -+ str = str.next_char(); -+ } -+ return buff.str; -+ } -+ - private void update_emoji_to_data_dict(IBus.EmojiData data, - string lang) { - string emoji = data.get_emoji(); - if (lang == "en") { -+ string description = utf8_down(data.get_description()); -+ unowned GLib.SList annotations = data.get_annotations(); -+ var words = description.split(" "); -+ // If the description has less than 3 words, add it to annotations -+ if (words.length < 3 && -+ annotations.find_custom( -+ description, -+ (GLib.CompareFunc)GLib.strcmp) == null) { -+ annotations.append(description); -+ data.set_annotations(annotations.copy_deep(GLib.strdup)); -+ } - m_emoji_to_data_dict.replace(emoji, data); - } else { - unowned IBus.EmojiData? en_data = -@@ -446,16 +481,24 @@ class IBusEmojier : Gtk.Window { - } - string trans_description = data.get_description(); - en_data.set_description(trans_description); -+ trans_description = utf8_down(trans_description); - unowned GLib.SList annotations = data.get_annotations(); - var words = trans_description.split(" "); - // If the description has less than 3 words, add it to annotations -- if (words.length < 3) -+ if (words.length < 3 && -+ annotations.find_custom( -+ trans_description, -+ (GLib.CompareFunc)GLib.strcmp) == null) { - annotations.append(trans_description); -+ } - unowned GLib.SList en_annotations - = en_data.get_annotations(); - foreach (string annotation in en_annotations) { -- if (annotations.find_custom(annotation, GLib.strcmp) == null) -+ if (annotations.find_custom( -+ annotation, -+ (GLib.CompareFunc)GLib.strcmp) == null) { - annotations.append(annotation.dup()); -+ } - } - en_data.set_annotations(annotations.copy_deep(GLib.strdup)); - } -@@ -526,18 +569,6 @@ class IBusEmojier : Gtk.Window { - show_category_list(); - } - -- private string get_title_string(string orig) { -- StringBuilder buff = new StringBuilder(); -- for (int i = 0; i < orig.char_count(); i++) { -- unichar ch = orig.get_char(i); -- if (i == 0) -- buff.append_unichar(ch.toupper()); -- else -- buff.append_unichar(ch); -- } -- return buff.str; -- } -- - private void show_category_list() { - remove_all_children(); - m_scrolled_window = new EScrolledWindow(); -@@ -606,7 +637,7 @@ class IBusEmojier : Gtk.Window { - EBoxRow row = new EBoxRow(category); - string locale_category = _(category); - EPaddedLabel widget = -- new EPaddedLabel(get_title_string(locale_category), -+ new EPaddedLabel(utf8_title(locale_category), - Gtk.Align.CENTER); - row.add(widget); - m_list_box.add(row); -@@ -658,7 +689,7 @@ class IBusEmojier : Gtk.Window { - IBus.Text text = new IBus.Text.from_string(emoji); - m_lookup_table.append_candidate(text); - } -- m_backward = get_title_string(row.text); -+ m_backward = utf8_title(row.text); - } - show_candidate_panel(); - } -@@ -734,6 +765,7 @@ class IBusEmojier : Gtk.Window { - m_backward = null; - return; - } -+ annotation = utf8_down(annotation); - if (annotation.length > m_emoji_max_seq_len) { - hide_candidate_panel(); - return; -@@ -841,6 +873,8 @@ class IBusEmojier : Gtk.Window { - m_vbox.add(grid); - grid.show_all(); - IBus.Text candidate = m_lookup_table.get_candidate(cursor); -+ unowned IBus.EmojiData? data = -+ m_emoji_to_data_dict.lookup(candidate.text); - if (cursor == 0 && candidate.text == m_unicode_point) { - EPaddedLabel widget = new EPaddedLabel( - _("Description: Unicode point U+%04X").printf( -@@ -848,25 +882,25 @@ class IBusEmojier : Gtk.Window { - Gtk.Align.START); - m_vbox.add(widget); - widget.show_all(); -- return; -- } -- unowned IBus.EmojiData? data = -- m_emoji_to_data_dict.lookup(candidate.text); -- if (data == null) { -- // TODO: Provide a description for the favorite emojis. -+ if (data == null) -+ return; -+ } else if (data == null) { -+ // TODO: Provide a custom description and annotation for -+ // the favorite emojis. - EPaddedLabel widget = new EPaddedLabel( - _("Description: %s").printf(_("None")), - Gtk.Align.START); - m_vbox.add(widget); - widget.show_all(); - return; -+ } else { -+ unowned string description = data.get_description(); -+ EPaddedLabel widget = new EPaddedLabel( -+ _("Description: %s").printf(description), -+ Gtk.Align.START); -+ m_vbox.add(widget); -+ widget.show_all(); - } -- unowned string description = data.get_description(); -- EPaddedLabel desc_widget = new EPaddedLabel( -- _("Description: %s").printf(description), -- Gtk.Align.START); -- m_vbox.add(desc_widget); -- desc_widget.show_all(); - unowned GLib.SList? annotations = - data.get_annotations(); - GLib.StringBuilder buff = new GLib.StringBuilder(); -@@ -1243,10 +1277,9 @@ class IBusEmojier : Gtk.Window { - case Gdk.Key.space: - case Gdk.Key.KP_Space: - if ((modifiers & Gdk.ModifierType.SHIFT_MASK) != 0) { -- entry_enter_keyval(keyval); -- break; -- } -- if (m_candidate_panel_is_visible) { -+ if (m_entry.get_text().len() > 0) -+ entry_enter_keyval(keyval); -+ } else if (m_candidate_panel_is_visible) { - enter_notify_disable_with_timer(); - m_lookup_table.cursor_down(); - show_candidate_panel(); --- -2.9.3 - -From bd7e0ba297f72ae1e2989743f2426c44df29f3ec Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Tue, 21 Mar 2017 12:56:23 +0900 -Subject: [PATCH] Make more readable error messages if emoji xml files are - missed - -Also Fix CONFIG_CLEAN_FILES for autoreconf - -R=Shawn.P.Huang@gmail.com - -Review URL: https://codereview.appspot.com/320750043 ---- - configure.ac | 26 +++++++++++++++++--------- - src/Makefile.am | 5 ++++- - src/emoji-parser.c | 2 +- - ui/gtk3/Makefile.am | 4 +++- - 4 files changed, 25 insertions(+), 12 deletions(-) - -diff --git a/configure.ac b/configure.ac -index 369485c..0a5f2d5 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -611,17 +611,11 @@ AC_ARG_ENABLE(emoji-dict, - [enable_emoji_dict=yes] - ) - AM_CONDITIONAL([ENABLE_EMOJI_DICT], [test x"$enable_emoji_dict" = x"yes"]) --if test x"$enable_emoji_dict" = x"yes"; then -- PKG_CHECK_MODULES(JSON_GLIB1, [ -- json-glib-1.0 -- ]) -- enable_emoji_dict="yes (enabled, use --disable-emoji-dict to disable)" --fi - - AC_ARG_WITH(emoji-json-file, - AS_HELP_STRING([--with-emoji-json-file[=DIR/emoji.json]], - [Set emoji.json. (default: "/usr/lib/node_modules/emojione/emoji.json") -- You can get emoji.json with "npm install -g emojione".]), -+ ]), - EMOJI_JSON_FILE=$with_emoji_json_file, - EMOJI_JSON_FILE="/usr/lib/node_modules/emojione/emoji.json" - ) -@@ -630,13 +624,27 @@ AC_SUBST(EMOJI_JSON_FILE) - AC_ARG_WITH(emoji-annotation-dir, - AS_HELP_STRING([--with-emoji-annotation-dir[=DIR]], - [Set the directory of CLDR annotation files. -- (default: "/usr/share/unicode/cldr/common/annotations") -- You can get https://github.com/fujiwarat/cldr-emoji-annotation]), -+ (default: "/usr/share/unicode/cldr/common/annotations")]), - EMOJI_ANNOTATION_DIR=$with_emoji_annotation_dir, - EMOJI_ANNOTATION_DIR="/usr/share/unicode/cldr/common/annotations" - ) - AC_SUBST(EMOJI_ANNOTATION_DIR) - -+if test x"$enable_emoji_dict" = x"yes"; then -+ if test ! -f $EMOJI_JSON_FILE ; then -+ AC_MSG_ERROR(Not found $EMOJI_JSON_FILE. You can get emoji.json \ -+with "npm install -g emojione".) -+ fi -+ if test ! -f $EMOJI_ANNOTATION_DIR/en.xml ; then -+ AC_MSG_ERROR(Not found $EMOJI_ANNOTATION_DIR/en.xml. You can get \ -+https://github.com/fujiwarat/cldr-emoji-annotation) -+ fi -+ PKG_CHECK_MODULES(JSON_GLIB1, [ -+ json-glib-1.0 -+ ]) -+ enable_emoji_dict="yes (enabled, use --disable-emoji-dict to disable)" -+fi -+ - # Check iso-codes. - PKG_CHECK_MODULES(ISOCODES, [ - iso-codes -diff --git a/src/Makefile.am b/src/Makefile.am -index 0d403e8..7053e3e 100644 ---- a/src/Makefile.am -+++ b/src/Makefile.am -@@ -243,7 +243,10 @@ LANG_FILES = $(basename $(notdir $(wildcard $(EMOJI_ANNOTATION_DIR)/*.xml))) - noinst_PROGRAMS = emoji-parser - - dicts/emoji-en.dict: emoji-parser -- $(AM_V_at)for f in $(LANG_FILES) ; do \ -+ $(AM_V_at)if test x"$(LANG_FILES)" = x ; then \ -+ echo "WARNING: Not found $(EMOJI_ANNOTATION_DIR)/en.xml" 1>&2; \ -+ fi; \ -+ for f in $(LANG_FILES) ; do \ - if test x"$$f" = xen ; then \ - $(builddir)/emoji-parser \ - --xml $(EMOJI_ANNOTATION_DIR)/$$f.xml \ -diff --git a/src/emoji-parser.c b/src/emoji-parser.c -index 8ff04f1..f9e3470 100644 ---- a/src/emoji-parser.c -+++ b/src/emoji-parser.c -@@ -20,7 +20,7 @@ - * USA - */ - --/* Convert /usr/share/unicode/cldr/common/annotations/*.xml and -+/* Convert /usr/share/unicode/cldr/common/annotations/\*.xml and - * /usr/lib/node_modules/emojione/emoji.json - * to the dictionary file which look up the Emoji from the annotation. - * Get *.xml from https://github.com/fujiwarat/cldr-emoji-annotation -diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am -index 4e7fd1b..b055f67 100644 ---- a/ui/gtk3/Makefile.am -+++ b/ui/gtk3/Makefile.am -@@ -81,6 +81,8 @@ AM_VALAFLAGS = \ - --target-glib="$(VALA_TARGET_GLIB_VERSION)" \ - $(NULL) - -+CONFIG_CLEAN_FILES = -+ - if ENABLE_LIBNOTIFY - AM_CFLAGS += \ - @LIBNOTIFY_CFLAGS@ \ -@@ -239,7 +241,7 @@ vapi_DATA = $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps) - - MAINTAINERCLEANFILES = $(VAPIGEN_VAPIS) - # for make distclean --CONFIG_CLEAN_FILES = $(VAPIGEN_VAPIS) -+CONFIG_CLEAN_FILES += $(VAPIGEN_VAPIS) - EXTRA_DIST += $(VAPIGEN_VAPIS) - - endif --- -2.9.3 - -From 0efb1c503d5901bbddcdb6fa73007b364ba4368d Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Mon, 27 Mar 2017 15:12:42 +0900 -Subject: [PATCH] Move language setting from IBusEmojier to ibus-setup - -The language setting of emoji annotations now can be saved -with ibus-setup. -Implement `ibus emoji [--font|--lang|--help]` - -R=Shawn.P.Huang@gmail.com - -Review URL: https://codereview.appspot.com/323720043 ---- - data/ibus.schemas.in | 46 +++--- - setup/Makefile.am | 26 ++-- - setup/emojilang.py | 348 ++++++++++++++++++++++++++++++++++++++++++++++ - setup/main.py | 41 ++++-- - setup/setup.ui | 97 ++++++++----- - tools/main.vala | 38 ++++- - ui/gtk3/emojier.vala | 295 +++++++++++++++------------------------ - ui/gtk3/ibusemojidialog.h | 11 ++ - ui/gtk3/panel.vala | 39 ++++-- - 9 files changed, 671 insertions(+), 270 deletions(-) - create mode 100644 setup/emojilang.py - -diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in -index 218d223..c0bbd6f 100644 ---- a/data/ibus.schemas.in -+++ b/data/ibus.schemas.in -@@ -106,18 +106,6 @@ - - - -- /schemas/desktop/ibus/general/hotkey/emoji -- /desktop/ibus/general/hotkey/emoji -- ibus -- list -- string -- [<Control><Shift>e] -- -- Emoji shortcut keys for gtk_accelerator_parse -- The shortcut keys for turning emoji typing on or off -- -- -- - /schemas/desktop/ibus/general/hotkey/enable_unconditional - /desktop/ibus/general/hotkey/enable_unconditional - ibus -@@ -366,8 +354,20 @@ - - - -- /schemas/desktop/ibus/panel/emoji_font -- /desktop/ibus/panel/emoji_font -+ /schemas/desktop/ibus/panel/emoji/hotkey -+ /desktop/ibus/panel/emoji/hotkey -+ ibus -+ list -+ string -+ [<Control><Shift>e] -+ -+ Emoji shortcut keys for gtk_accelerator_parse -+ The shortcut keys for turning emoji typing on or off -+ -+ -+ -+ /schemas/desktop/ibus/panel/emoji/font -+ /desktop/ibus/panel/emoji/font - ibus - string - Monospace 16 -@@ -377,8 +377,22 @@ - - - -- /schemas/desktop/ibus/panel/emoji_favorites -- /desktop/ibus/panel/emoji_favorites -+ /schemas/desktop/ibus/panel/emoji/lang -+ /desktop/ibus/panel/emoji/lang -+ ibus -+ string -+ en -+ -+ Default language for emoji dictionary -+ Choose a default language of emoji dictionaries on -+ the emoji dialog. The value $lang is applied to -+ /usr/share/unicode/cldr/common/annotations/$lang.xml -+ -+ -+ -+ -+ /schemas/desktop/ibus/panel/emoji/favorites -+ /desktop/ibus/panel/emoji/favorites - ibus - list - [] -diff --git a/setup/Makefile.am b/setup/Makefile.am -index 2d822d2..b7dd755 100644 ---- a/setup/Makefile.am -+++ b/setup/Makefile.am -@@ -3,7 +3,8 @@ - # ibus - The Input Bus - # - # Copyright (c) 2007-2014 Peng Huang --# Copyright (c) 2007-2014 Red Hat, Inc. -+# Copyright (c) 2015-2017 Takao Fujiwara -+# Copyright (c) 2007-2017 Red Hat, Inc. - # - # This library is free software; you can redistribute it and/or - # modify it under the terms of the GNU Lesser General Public -@@ -21,19 +22,20 @@ - # USA - - ibussetup_PYTHON = \ -- main.py \ -- i18n.py \ -- icon.py \ -- enginecombobox.py \ -- enginedialog.py \ -- enginetreeview.py \ -- engineabout.py \ -- keyboardshortcut.py \ -- $(NULL) -+ emojilang.py \ -+ enginecombobox.py \ -+ enginedialog.py \ -+ enginetreeview.py \ -+ engineabout.py \ -+ i18n.py \ -+ icon.py \ -+ keyboardshortcut.py \ -+ main.py \ -+ $(NULL) - - ibussetup_DATA = \ -- setup.ui \ -- $(NULL) -+ setup.ui \ -+ $(NULL) - - bin_SCRIPTS = ibus-setup - ibussetupdir = $(pkgdatadir)/setup -diff --git a/setup/emojilang.py b/setup/emojilang.py -new file mode 100644 -index 0000000..8250589 ---- /dev/null -+++ b/setup/emojilang.py -@@ -0,0 +1,348 @@ -+# vim:set et sts=4 sw=4: -+# -*- coding: utf-8 -*- -+# -+# ibus - The Input Bus -+# -+# Copyright (c) 2017 Takao Fujiwara -+# Copyright (c) 2017 Red Hat, Inc. -+# -+# 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, see . -+ -+# for python2 -+from __future__ import print_function -+ -+__all__ = ( -+ "EmojiLangButton", -+); -+ -+from gi.repository import Gtk -+from gi.repository import GLib -+from gi.repository import GObject -+from gi.repository import IBus -+ -+import functools -+import gettext -+import i18n -+import locale -+import os -+ -+from icon import load_icon -+from i18n import _, N_ -+ -+ROW_TRAVEL_DIRECTION_NONE, \ -+ROW_TRAVEL_DIRECTION_FORWARD, \ -+ROW_TRAVEL_DIRECTION_BACKWARD = list(range(3)) -+ -+class LanguageString: -+ def __init__(self, id, trans = ""): -+ self.id = id -+ self.trans = trans -+ -+class EmojiLangChooser(Gtk.Dialog): -+ __gtype_name__ = 'EmojiLangChooser' -+ __initial_languages = [ IBus.get_language_name('en_US'), -+ IBus.get_language_name('en_GB'), -+ IBus.get_language_name('de_DE'), -+ IBus.get_language_name('fr_FR'), -+ IBus.get_language_name('es_ES'), -+ IBus.get_language_name('zh_CN'), -+ IBus.get_language_name('ja_JP'), -+ IBus.get_language_name('ru_RU'), -+ IBus.get_language_name('ar_EG') ] -+ -+ -+ def __init__(self, id = None, transient_for = None): -+ super(EmojiLangChooser, self).__init__( -+ title = _("Select a language"), -+ transient_for = transient_for, -+ resizable = True) -+ buttons = (_("_Cancel"), Gtk.ResponseType.CANCEL, -+ _("_OK"), Gtk.ResponseType.APPLY) -+ self.add_buttons(*buttons) -+ -+ if id == None: -+ id = 'en' -+ self.__id = id -+ self.__engines_for_lang = {} -+ self.__untrans_for_lang = {} -+ self.__langs = {} -+ self.__lang_list = [] -+ -+ self.__scrolled = Gtk.ScrolledWindow( -+ hscrollbar_policy = Gtk.PolicyType.NEVER, -+ vscrollbar_policy = Gtk.PolicyType.NEVER, -+ shadow_type = Gtk.ShadowType.IN, -+ margin_left = 6, -+ margin_right = 6, -+ margin_top = 6, -+ margin_bottom = 6) -+ self.vbox.add(self.__scrolled) -+ viewport = Gtk.Viewport() -+ self.__scrolled.add(viewport) -+ self.__list = Gtk.ListBox(vexpand = True, -+ halign = Gtk.Align.FILL, -+ valign = Gtk.Align.FILL) -+ viewport.add(self.__list) -+ -+ self.__adjustment = self.__scrolled.get_vadjustment() -+ self.__list.set_adjustment(self.__adjustment) -+ self.__list.set_filter_func(self.__list_filter, None) -+ self.__list.connect('row-activated', self.__row_activated) -+ -+ self.__showing_extra = False -+ self.__more_row = self.__more_row_new() -+ self.__load_lang_list() -+ self.__show_lang_rows() -+ self.show_all() -+ -+ -+ def __load_lang_list(self): -+ dictdir = os.path.dirname(__file__) + '/../dicts' -+ for filename in os.listdir(dictdir): -+ suffix = '.dict' -+ if not filename.endswith(suffix): -+ continue -+ lang_id = filename[0:len(filename) - len(suffix)] -+ prefix = 'emoji-' -+ if not lang_id.startswith(prefix): -+ continue -+ lang_id = lang_id[len(prefix):] -+ lang = LanguageString(lang_id, IBus.get_language_name(lang_id)) -+ self.__lang_list.append(lang) -+ if len(self.__lang_list) == 0: -+ print("Not found dicts in %s" % dictdir, file=sys.stderr) -+ lang = LanguageString('en', IBus.get_language_name('en')) -+ self.__lang_list.append(lang) -+ return -+ -+ def cmp_lang(a, b): -+ label_a = a.trans + a.id -+ label_b = b.trans + b.id -+ return (label_a > label_b) - (label_a < label_b) -+ -+ self.__lang_list.sort(key = functools.cmp_to_key(cmp_lang)) -+ -+ loc = locale.getlocale()[0] -+ # None on C locale -+ if loc == None or loc == 'C': -+ loc = 'en_US' -+ index = 0 -+ for lang in self.__lang_list: -+ # move current language to the first place -+ if lang.trans == IBus.get_language_name(loc): -+ self.__lang_list.remove(lang) -+ self.__lang_list.insert(index, lang) -+ index += 1 -+ -+ for lang in self.__lang_list: -+ # move English to the second place -+ if lang.trans == IBus.get_language_name('en'): -+ self.__lang_list.remove(lang) -+ self.__lang_list.insert(index, lang) -+ index += 1 -+ -+ -+ def __list_filter(self, row, data): -+ if row.id == self.__id: -+ self.__list.select_row(row) -+ if row == self.__more_row: -+ return not self.__showing_extra -+ if not self.__showing_extra and row.is_extra: -+ return False -+ return True -+ -+ -+ def __row_activated(self, box, row): -+ if row == self.__more_row: -+ self.__show_more() -+ return -+ self.__id = row.id -+ -+ -+ def __padded_label_new(self, text, icon, alignment, direction): -+ hbox = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL) -+ -+ if direction == ROW_TRAVEL_DIRECTION_BACKWARD: -+ rtl = (Gtk.Widget.get_default_direction() == \ -+ Gtk.TextDirection.RTL) -+ if rtl: -+ arrow = Gtk.Image.new_from_icon_name( -+ 'go-previous-rtl-symbolic', Gtk.IconSize.MENU) -+ else: -+ arrow = Gtk.Image.new_from_icon_name( -+ 'go-previous-symbolic', Gtk.IconSize.MENU) -+ hbox.pack_start(arrow, False, True, 0) -+ -+ if icon != None: -+ pixbuf = load_icon(icon, Gtk.IconSize.LARGE_TOOLBAR) -+ image = Gtk.Image(pixbuf = pixbuf) -+ hbox.pack_start(image, False, True, 0) -+ -+ label = Gtk.Label(label = text) -+ label.set_halign(alignment) -+ label.set_valign(Gtk.Align.CENTER) -+ label.set_margin_left(20) -+ label.set_margin_right(20) -+ label.set_margin_top(6) -+ label.set_margin_bottom(6) -+ hbox.pack_start(label, True, True, 0) -+ return hbox -+ -+ -+ def __list_box_row_new(self, lang): -+ row = Gtk.ListBoxRow() -+ row.trans = lang.trans -+ row.id = lang.id -+ row.is_extra = False -+ return row -+ -+ -+ def __lang_row_new(self, lang, prev_lang): -+ row = self.__list_box_row_new(lang) -+ label = lang.trans -+ if lang.id == self.__id: -+ row.is_extra = False -+ elif prev_lang != None and label == prev_lang.trans: -+ label = "%s (%s)" % (lang.trans, lang.id) -+ row.is_extra = True -+ elif not self.__showing_extra and \ -+ lang.trans not in self.__initial_languages: -+ row.is_extra = True -+ widget = self.__padded_label_new(label, -+ None, -+ Gtk.Align.CENTER, -+ ROW_TRAVEL_DIRECTION_NONE) -+ row.add(widget) -+ return row -+ -+ -+ def __more_row_new(self): -+ row = Gtk.ListBoxRow() -+ row.id = None -+ hbox = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL) -+ row.add(hbox) -+ row.set_tooltip_text(_("More…")) -+ arrow = Gtk.Image.new_from_icon_name('view-more-symbolic', -+ Gtk.IconSize.MENU) -+ arrow.set_margin_left(20) -+ arrow.set_margin_right(20) -+ arrow.set_margin_top(6) -+ arrow.set_margin_bottom(6) -+ arrow.set_halign(Gtk.Align.CENTER) -+ arrow.set_valign(Gtk.Align.CENTER) -+ hbox.pack_start(arrow, True, True, 0) -+ return row -+ -+ -+ def __set_fixed_size(self): -+ if self.__scrolled.get_policy()[0] == Gtk.PolicyType.AUTOMATIC: -+ return -+ (width, height) = self.get_size() -+ self.set_size_request(width, height) -+ self.__scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, -+ Gtk.PolicyType.AUTOMATIC) -+ -+ -+ def __remove_all_children(self): -+ for l in self.__list.get_children(): -+ self.__list.remove(l) -+ -+ -+ def __show_lang_rows(self): -+ self.__remove_all_children() -+ prev_lang = None -+ for lang in self.__lang_list: -+ row = self.__lang_row_new(lang, prev_lang) -+ self.__list.add(row) -+ prev_lang = lang -+ self.__list.add(self.__more_row) -+ self.__list.show_all() -+ self.__adjustment.set_value(self.__adjustment.get_lower()) -+ self.__list.invalidate_filter() -+ self.__list.set_selection_mode(Gtk.SelectionMode.SINGLE) -+ -+ -+ def __show_more(self): -+ self.__set_fixed_size() -+ self.__showing_extra = True -+ self.__list.invalidate_filter() -+ -+ -+ def get_selected_lang(self): -+ return self.__id -+ -+ -+class EmojiLangButton(Gtk.Button): -+ __gtype_name__ = 'EmojiLangButton' -+ __gproperties__ = { -+ 'lang' : ( -+ str, -+ 'lang', -+ 'lang for emojo-*.dict', -+ 'en', -+ GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE) -+ } -+ -+ -+ def __init__(self): -+ super(EmojiLangButton, self).__init__() -+ self.__lang = '' -+ -+ -+ def do_get_property(self, prop): -+ if prop.name == 'lang': -+ return self.__lang -+ else: -+ raise AttributeError('unknown property %s' % prop.name) -+ -+ -+ def do_set_property(self, prop, value): -+ if prop.name == 'lang': -+ self.set_lang(value) -+ else: -+ raise AttributeError('unknown property %s' % prop.name) -+ -+ -+ def do_clicked(self): -+ dialog = EmojiLangChooser(id = self.__lang, -+ transient_for = self.get_toplevel()) -+ id = dialog.run() -+ if id != Gtk.ResponseType.APPLY: -+ dialog.destroy() -+ return -+ self.set_lang(dialog.get_selected_lang()) -+ dialog.destroy() -+ -+ -+ def set_lang(self, lang): -+ self.__lang = lang -+ self.notify("lang") -+ self.set_label(IBus.get_language_name(lang)) -+ -+ -+ def get_lang(self, lang): -+ return self.__lang -+ -+ -+GObject.type_register(EmojiLangButton) -+ -+ -+if __name__ == "__main__": -+ dialog = EmojiLangChooser() -+ id = dialog.run() -+ if id != Gtk.ResponseType.APPLY: -+ dialog.destroy() -+ import sys -+ sys.exit(0) -+ print("Selected language:", dialog.get_selected_lang()) -diff --git a/setup/main.py b/setup/main.py -index 09b0ebd..7839cea 100644 ---- a/setup/main.py -+++ b/setup/main.py -@@ -51,6 +51,7 @@ from os import path - import i18n - import keyboardshortcut - import locale -+from emojilang import EmojiLangButton - from enginecombobox import EngineComboBox - from enginedialog import EngineDialog - from enginetreeview import EngineTreeView -@@ -92,6 +93,8 @@ class Setup(object): - schema = "org.freedesktop.ibus.general.hotkey"); - self.__settings_panel = Gio.Settings( - schema = "org.freedesktop.ibus.panel"); -+ self.__settings_emoji = Gio.Settings( -+ schema = "org.freedesktop.ibus.panel.emoji"); - - # IBus.Bus() calls ibus_bus_new(). - # Gtk.Builder().add_from_file() also calls ibus_bus_new_async() -@@ -122,7 +125,10 @@ class Setup(object): - self.__init_hotkey(name, label) - - def __init_hotkey(self, name, label, comment=None): -- shortcuts = self.__settings_hotkey.get_strv(name) -+ if name == 'emoji': -+ shortcuts = self.__settings_emoji.get_strv('hotkey') -+ else: -+ shortcuts = self.__settings_hotkey.get_strv(name) - button = self.__builder.get_object("button_%s" % label) - entry = self.__builder.get_object("entry_%s" % label) - entry.set_text("; ".join(shortcuts)) -@@ -130,8 +136,12 @@ class Setup(object): - if comment != None: - tooltip += "\n" + comment - entry.set_tooltip_text(tooltip) -- button.connect("clicked", self.__shortcut_button_clicked_cb, -- name, "general/hotkey", label, entry) -+ if name == 'emoji': -+ button.connect("clicked", self.__shortcut_button_clicked_cb, -+ 'hotkey', 'panel/' + name, label, entry) -+ else: -+ button.connect("clicked", self.__shortcut_button_clicked_cb, -+ name, "general/hotkey", label, entry) - - def __init_panel(self): - # lookup table orientation -@@ -169,21 +179,27 @@ class Setup(object): - - self.__fontbutton_custom_font = self.__builder.get_object( - "fontbutton_custom_font") -- self.__fontbutton_emoji_font = self.__builder.get_object( -- "fontbutton_emoji_font") -- self.__fontbutton_emoji_font.set_preview_text("🙂🍎🚃💓📧⚽🐳"); - self.__settings_panel.bind('custom-font', - self.__fontbutton_custom_font, - 'font-name', - Gio.SettingsBindFlags.DEFAULT) -- self.__settings_panel.bind('emoji-font', -- self.__fontbutton_emoji_font, -- 'font-name', -- Gio.SettingsBindFlags.DEFAULT) - self.__settings_panel.bind('use-custom-font', - self.__fontbutton_custom_font, - 'sensitive', - Gio.SettingsBindFlags.GET) -+ self.__fontbutton_emoji_font = self.__builder.get_object( -+ 'fontbutton_emoji_font') -+ self.__fontbutton_emoji_font.set_preview_text('🙂🍎🚃💓📧⚽🐳'); -+ self.__settings_emoji.bind('font', -+ self.__fontbutton_emoji_font, -+ 'font-name', -+ Gio.SettingsBindFlags.DEFAULT) -+ self.__button_emoji_lang = self.__builder.get_object( -+ 'button_emoji_lang') -+ self.__settings_emoji.bind('lang', -+ self.__button_emoji_lang, -+ 'lang', -+ Gio.SettingsBindFlags.DEFAULT) - - # show icon on system tray - self.__checkbutton_show_icon_on_systray = self.__builder.get_object( -@@ -458,7 +474,10 @@ class Setup(object): - dialog.destroy() - if id != Gtk.ResponseType.OK: - return -- self.__settings_hotkey.set_strv(name, shortcuts) -+ if section == 'panel/emoji': -+ self.__settings_emoji.set_strv(name, shortcuts) -+ else: -+ self.__settings_hotkey.set_strv(name, shortcuts) - text = "; ".join(shortcuts) - entry.set_text(text) - tooltip = "\n".join(shortcuts) -diff --git a/setup/setup.ui b/setup/setup.ui -index d5ee392..4ef3423 100644 ---- a/setup/setup.ui -+++ b/setup/setup.ui -@@ -661,38 +661,11 @@ - - - -- -- vertical -+ - True -- False -- -- -- False -- True -- True -- True -- False -- -- -- False -- False -- 0 -- -- -- -- -- False -- True -- True -- True -- False -- -- -- False -- False -- 1 -- -- -+ True -+ True -+ False - - - 1 -@@ -702,6 +675,68 @@ - GTK_FILL - - -+ -+ -+ True -+ False -+ Set a font of emoji candidates on the emoji dialog -+ start -+ Emoji font: -+ right -+ -+ -+ 7 -+ 8 -+ GTK_FILL -+ GTK_FILL -+ -+ -+ -+ -+ True -+ True -+ True -+ False -+ -+ -+ 1 -+ 2 -+ 7 -+ 8 -+ GTK_FILL -+ -+ -+ -+ -+ True -+ False -+ Set a language of emoji annotations on the emoji dialog -+ start -+ Emoji annotation language: -+ right -+ -+ -+ 8 -+ 9 -+ GTK_FILL -+ GTK_FILL -+ -+ -+ -+ -+ True -+ True -+ True -+ False -+ -+ -+ 1 -+ 2 -+ 8 -+ 9 -+ GTK_FILL -+ -+ - - - -diff --git a/tools/main.vala b/tools/main.vala -index fd9fd0e..2bf1dc7 100644 ---- a/tools/main.vala -+++ b/tools/main.vala -@@ -31,6 +31,8 @@ bool name_only = false; - /* system() exists as a public API. */ - bool is_system = false; - string cache_file = null; -+string emoji_font = null; -+string annotation_lang = null; - - class EngineList { - public IBus.EngineDesc[] data = {}; -@@ -342,12 +344,40 @@ private void run_dialog(IBus.Emojier emojier) { - } - - int emoji_dialog(string[] argv) { -+ const OptionEntry[] options = { -+ { "font", 0, 0, OptionArg.STRING, out emoji_font, -+ N_("FONT for emoji chracters on emoji dialog."), "FONT" }, -+ { "lang", 0, 0, OptionArg.STRING, out annotation_lang, -+ N_("LANG for annotations on emoji dialog. E.g. \"en\""), "LANG" }, -+ { null } -+ }; -+ -+ var option = new OptionContext(); -+ option.add_main_entries(options, Config.GETTEXT_PACKAGE); -+ -+ try { -+ option.parse(ref argv); -+ } catch (OptionError e) { -+ stderr.printf("%s\n", e.message); -+ return Posix.EXIT_FAILURE; -+ } -+ - Gtk.init(ref argv); -- GLib.Settings settings_panel = -- new GLib.Settings("org.freedesktop.ibus.panel"); -- string emoji_font = settings_panel.get_string("emoji-font"); -+ if (emoji_font == null) { -+ GLib.Settings settings_emoji = -+ new GLib.Settings("org.freedesktop.ibus.panel.emoji"); -+ emoji_font = settings_emoji.get_string("font"); -+ } -+ if (annotation_lang == null) { -+ GLib.Settings settings_emoji = -+ new GLib.Settings("org.freedesktop.ibus.panel.emoji"); -+ annotation_lang = settings_emoji.get_string("lang"); -+ } - IBus.Emojier emojier = new IBus.Emojier(); -- emojier.set_emoji_font(emoji_font); -+ if (emoji_font != null && emoji_font != "") -+ emojier.set_emoji_font(emoji_font); -+ if (annotation_lang != null && annotation_lang != "") -+ emojier.set_annotation_lang(annotation_lang); - if (emojier.has_loaded_emoji_dict()) { - run_dialog(emojier); - } else { -diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala -index b1dc50c..20c1378 100644 ---- a/ui/gtk3/emojier.vala -+++ b/ui/gtk3/emojier.vala -@@ -42,14 +42,11 @@ class IBusEmojier : Gtk.Window { - } - } - private class EBoxRow : Gtk.ListBoxRow { -- public EBoxRow(string text, -- string id="") { -+ public EBoxRow(string text) { - this.text = text; -- this.id = id; - } - - public string text { get; set; } -- public string id { get; set; } - } - private class EScrolledWindow : Gtk.ScrolledWindow { - public EScrolledWindow(Gtk.Adjustment? hadjustment=null, -@@ -132,6 +129,7 @@ class IBusEmojier : Gtk.Window { - } - } - private class ETitleLabel : Gtk.Box { -+ private Gtk.Label m_label; - private Gtk.Button m_close_button; - private ulong m_close_handler; - -@@ -142,14 +140,14 @@ class IBusEmojier : Gtk.Window { - orientation : Gtk.Orientation.HORIZONTAL, - spacing : 0 - ); -- Gtk.Label label = new Gtk.Label(text); -- label.set_halign(align); -- label.set_valign(align); -- label.set_margin_start(20); -- label.set_margin_end(20); -- label.set_margin_top(6); -- label.set_margin_bottom(6); -- pack_start(label, true, true, 0); -+ m_label = new Gtk.Label(text); -+ m_label.set_halign(align); -+ m_label.set_valign(align); -+ m_label.set_margin_start(20); -+ m_label.set_margin_end(20); -+ m_label.set_margin_top(6); -+ m_label.set_margin_bottom(6); -+ pack_start(m_label, true, true, 0); - IconWidget icon = new IconWidget("window-close", Gtk.IconSize.MENU); - m_close_button = new Gtk.Button(); - m_close_button.add(icon); -@@ -170,6 +168,9 @@ class IBusEmojier : Gtk.Window { - m_close_handler = 0; - } - } -+ public void set_label(string str) { -+ m_label.set_label(str); -+ } - } - - private enum TravelDirection { -@@ -177,11 +178,6 @@ class IBusEmojier : Gtk.Window { - BACKWARD, - } - -- private enum CategoryType { -- EMOJI, -- LANG, -- } -- - private const uint EMOJI_GRID_PAGE = 10; - private ThemedRGBA m_rgba; - private Gtk.Box m_vbox; -@@ -190,14 +186,11 @@ class IBusEmojier : Gtk.Window { - private string? m_backward; - private EScrolledWindow? m_scrolled_window = null; - private EListBox m_list_box; -- private CategoryType m_current_category_type = CategoryType.EMOJI; - private bool m_is_running = false; - private string m_input_context_path = ""; - private GLib.MainLoop? m_loop; - private string? m_result; -- private GLib.SList m_lang_list; - private string m_current_lang_id = "en"; -- private string m_current_language = "English"; - private string? m_unicode_point = null; - private bool m_candidate_panel_is_visible; - private GLib.HashTable? -@@ -215,6 +208,7 @@ class IBusEmojier : Gtk.Window { - private bool m_enter_notify_enable = true; - private uint m_entry_notify_show_id; - private uint m_entry_notify_disable_id; -+ private uint m_reload_emoji_dict_id; - - public signal void candidate_clicked(uint index, uint button, uint state); - public signal void loaded_emoji_dict(); -@@ -323,50 +317,13 @@ class IBusEmojier : Gtk.Window { - hide_candidate_panel(); - }); - -- GLib.Idle.add(() => { -- m_lang_list = read_lang_list(); -+ m_reload_emoji_dict_id = GLib.Idle.add(() => { - reload_emoji_dict(); -+ m_reload_emoji_dict_id = 0; - return false; - }); - } - -- private GLib.SList read_lang_list() { -- GLib.SList lang_list = new GLib.SList(); -- const string dict_path = Config.PKGDATADIR + "/dicts"; -- GLib.Dir dir = null; -- try { -- dir = GLib.Dir.open(dict_path); -- } catch (GLib.FileError e) { -- warning("Error loading %s: %s", dict_path, e.message); -- return lang_list; -- } -- string name; -- while ((name = dir.read_name()) != null) { -- const string dict_suffix = ".dict"; -- const string dict_prefix = "emoji-"; -- if (name.has_suffix(dict_suffix)) { -- name = name[0:name.length - dict_suffix.length]; -- if (name.has_prefix(dict_prefix)) { -- name = name[dict_prefix.length:name.length]; -- lang_list.append(name); -- } else { -- warning("Need %s prefix in the filename: %s/%s%s", -- dict_prefix, dict_path, name, dict_suffix); -- } -- } else { -- warning("Need %s extention in the filename: %s/%s", -- dict_suffix, dict_path, name); -- } -- } -- lang_list.sort((a, b) => { -- string a_lang = IBus.get_language_name(a); -- string b_lang = IBus.get_language_name(b); -- a_lang = "%s (%s)".printf(a_lang, a); -- b_lang = "%s (%s)".printf(b_lang, b); -- return GLib.strcmp(a_lang, b_lang); -- }); -- return lang_list; -- } - - private void reload_emoji_dict() { - init_emoji_dict(); -@@ -382,6 +339,7 @@ class IBusEmojier : Gtk.Window { - loaded_emoji_dict(); - } - -+ - private void init_emoji_dict() { - m_annotation_to_emojis_dict = - new GLib.HashTable(GLib.str_hash, -@@ -394,6 +352,7 @@ class IBusEmojier : Gtk.Window { - GLib.str_equal); - } - -+ - private void make_emoji_dict(string lang) { - GLib.SList emoji_list = IBus.EmojiData.load( - Config.PKGDATADIR + "/dicts/emoji-" + lang + ".dict"); -@@ -412,6 +371,7 @@ class IBusEmojier : Gtk.Window { - } - } - -+ - private void update_annotation_to_emojis_dict(IBus.EmojiData data) { - string emoji = data.get_emoji(); - unowned GLib.SList annotations = data.get_annotations(); -@@ -432,6 +392,7 @@ class IBusEmojier : Gtk.Window { - } - } - -+ - private string utf8_down(string str) { - GLib.StringBuilder buff = new GLib.StringBuilder(); - int length = str.char_count(); -@@ -442,6 +403,7 @@ class IBusEmojier : Gtk.Window { - return buff.str; - } - -+ - private string utf8_title(string str) { - StringBuilder buff = new StringBuilder(); - int length = str.char_count(); -@@ -456,6 +418,7 @@ class IBusEmojier : Gtk.Window { - return buff.str; - } - -+ - private void update_emoji_to_data_dict(IBus.EmojiData data, - string lang) { - string emoji = data.get_emoji(); -@@ -464,10 +427,11 @@ class IBusEmojier : Gtk.Window { - unowned GLib.SList annotations = data.get_annotations(); - var words = description.split(" "); - // If the description has less than 3 words, add it to annotations -+ // FIXME: How to cast GLib.CompareFunc to strcmp? - if (words.length < 3 && - annotations.find_custom( - description, -- (GLib.CompareFunc)GLib.strcmp) == null) { -+ GLib.strcmp) == null) { - annotations.append(description); - data.set_annotations(annotations.copy_deep(GLib.strdup)); - } -@@ -485,18 +449,20 @@ class IBusEmojier : Gtk.Window { - unowned GLib.SList annotations = data.get_annotations(); - var words = trans_description.split(" "); - // If the description has less than 3 words, add it to annotations -+ // FIXME: How to cast GLib.CompareFunc to strcmp? - if (words.length < 3 && - annotations.find_custom( - trans_description, -- (GLib.CompareFunc)GLib.strcmp) == null) { -+ GLib.strcmp) == null) { - annotations.append(trans_description); - } - unowned GLib.SList en_annotations - = en_data.get_annotations(); - foreach (string annotation in en_annotations) { -+ // FIXME: How to cast GLib.CompareFunc to strcmp? - if (annotations.find_custom( - annotation, -- (GLib.CompareFunc)GLib.strcmp) == null) { -+ GLib.strcmp) == null) { - annotations.append(annotation.dup()); - } - } -@@ -504,6 +470,7 @@ class IBusEmojier : Gtk.Window { - } - } - -+ - private void update_category_to_emojis_dict(IBus.EmojiData data, - string lang) { - string emoji = data.get_emoji(); -@@ -525,29 +492,12 @@ class IBusEmojier : Gtk.Window { - } - } - -+ - private void set_fixed_size() { -- if (!m_candidate_panel_is_visible && -- m_current_category_type == CategoryType.LANG) { -- Gtk.PolicyType vpolicy; -- m_scrolled_window.get_policy(null, out vpolicy); -- if (vpolicy == Gtk.PolicyType.AUTOMATIC) -- return; -- int width, height; -- get_size(out width, out height); -- set_size_request(width, height); -- if (m_scrolled_window != null) { -- m_scrolled_window.set_policy(Gtk.PolicyType.NEVER, -- Gtk.PolicyType.AUTOMATIC); -- } -- } else { -- resize(20, 1); -- if (m_scrolled_window != null) { -- m_scrolled_window.set_policy(Gtk.PolicyType.NEVER, -- Gtk.PolicyType.NEVER); -- } -- } -+ resize(20, 1); - } - -+ - private void remove_all_children() { - foreach (Gtk.Widget w in m_vbox.get_children()) { - if (w.name == "IBusEmojierEntry" || -@@ -558,51 +508,16 @@ class IBusEmojier : Gtk.Window { - } - } - -- private void activated_language(EBoxRow row) { -- m_category_active_index = 0; -- if (m_current_lang_id != row.id) { -- m_current_lang_id = row.id; -- m_current_language = row.text; -- reload_emoji_dict(); -- } -- m_current_category_type = CategoryType.EMOJI; -- show_category_list(); -- } - - private void show_category_list() { - remove_all_children(); - m_scrolled_window = new EScrolledWindow(); - set_fixed_size(); -- EPaddedLabel label; -- if (m_current_category_type == CategoryType.EMOJI) { -- label = new EPaddedLabel(m_current_language, Gtk.Align.CENTER); -- } else if (m_current_category_type == CategoryType.LANG) { -- label = new EPaddedLabel(m_current_language, -- Gtk.Align.CENTER, -- TravelDirection.BACKWARD); -- } else { -- label = new EPaddedLabel("", Gtk.Align.CENTER); -- } -- Gtk.Button button = new Gtk.Button(); -- button.add(label); -- m_vbox.add(button); -- button.show_all(); -- if (m_current_category_type == CategoryType.EMOJI) { -- button.button_press_event.connect((e) => { -- m_category_active_index = 0; -- m_current_category_type = CategoryType.LANG; -- show_category_list(); -- return true; -- }); -- } else if (m_current_category_type == CategoryType.LANG) { -- button.button_press_event.connect((e) => { -- m_category_active_index = 0; -- m_current_category_type = CategoryType.EMOJI; -- show_category_list(); -- return true; -- }); -- } - -+ string language = "%s (%s)".printf( -+ _("Emoji Dialog"), -+ IBus.get_language_name(m_current_lang_id)); -+ m_title.set_label(language); - m_vbox.add(m_scrolled_window); - Gtk.Viewport viewport = new Gtk.Viewport(null, null); - m_scrolled_window.add(viewport); -@@ -611,59 +526,38 @@ class IBusEmojier : Gtk.Window { - viewport.add(m_list_box); - Gtk.Adjustment adjustment = m_scrolled_window.get_vadjustment(); - m_list_box.set_adjustment(adjustment); -- if (m_current_category_type == CategoryType.EMOJI) { -- m_list_box.row_activated.connect((box, gtkrow) => { -- m_category_active_index = 0; -- EBoxRow row = gtkrow as EBoxRow; -- show_emoji_for_category(row); -- }); -+ m_list_box.row_activated.connect((box, gtkrow) => { -+ m_category_active_index = 0; -+ EBoxRow row = gtkrow as EBoxRow; -+ show_emoji_for_category(row); -+ }); - -- uint n = 1; -- if (m_favorites.length > 0) { -- EBoxRow row = new EBoxRow("@favorites"); -- EPaddedLabel widget = -- new EPaddedLabel(_("Favorites"), Gtk.Align.CENTER); -- row.add(widget); -- m_list_box.add(row); -- if (n++ == m_category_active_index) -- m_list_box.select_row(row); -- } -- GLib.List categories = -- m_category_to_emojis_dict.get_keys(); -- categories.sort((a, b) => { -- return GLib.strcmp(_(a), _(b)); -- }); -- foreach (unowned string category in categories) { -- EBoxRow row = new EBoxRow(category); -- string locale_category = _(category); -- EPaddedLabel widget = -- new EPaddedLabel(utf8_title(locale_category), -- Gtk.Align.CENTER); -- row.add(widget); -- m_list_box.add(row); -- if (n++ == m_category_active_index) -- m_list_box.select_row(row); -- } -- } else if (m_current_category_type == CategoryType.LANG) { -- m_list_box.row_activated.connect((box, gtkrow) => { -- activated_language(gtkrow as EBoxRow); -- }); -- uint n = 1; -- string prev_language = null; -- foreach (unowned string id in m_lang_list) { -- string language = IBus.get_language_name(id); -- if (prev_language == language) -- language = "%s (%s)".printf(language, id); -- else -- prev_language = language; -- EBoxRow row = new EBoxRow(language, id); -- EPaddedLabel widget = -- new EPaddedLabel(language, Gtk.Align.CENTER); -- row.add(widget); -- m_list_box.add(row); -- if (n++ == m_category_active_index) -- m_list_box.select_row(row); -- } -+ uint n = 1; -+ if (m_favorites.length > 0) { -+ EBoxRow row = new EBoxRow("@favorites"); -+ EPaddedLabel widget = -+ new EPaddedLabel(_("Favorites"), Gtk.Align.CENTER); -+ row.add(widget); -+ m_list_box.add(row); -+ if (n++ == m_category_active_index) -+ m_list_box.select_row(row); -+ } -+ GLib.List categories = -+ m_category_to_emojis_dict.get_keys(); -+ // FIXME: How to cast GLib.CompareFunc to strcmp? -+ categories.sort((a, b) => { -+ return GLib.strcmp(_(a), _(b)); -+ }); -+ foreach (unowned string category in categories) { -+ EBoxRow row = new EBoxRow(category); -+ string locale_category = _(category); -+ EPaddedLabel widget = -+ new EPaddedLabel(utf8_title(locale_category), -+ Gtk.Align.CENTER); -+ row.add(widget); -+ m_list_box.add(row); -+ if (n++ == m_category_active_index) -+ m_list_box.select_row(row); - } - - m_scrolled_window.show_all(); -@@ -673,6 +567,7 @@ class IBusEmojier : Gtk.Window { - m_list_box.set_selection_mode(Gtk.SelectionMode.SINGLE); - } - -+ - private void show_emoji_for_category(EBoxRow row) { - if (row.text == "@favorites") { - m_lookup_table.clear(); -@@ -694,6 +589,7 @@ class IBusEmojier : Gtk.Window { - show_candidate_panel(); - } - -+ - private void show_arrow_buttons() { - Gtk.Button next_button = new Gtk.Button(); - next_button.clicked.connect(() => { -@@ -729,6 +625,7 @@ class IBusEmojier : Gtk.Window { - buttons_hbox.show_all(); - } - -+ - private bool check_unicode_point(string? annotation=null) { - bool check_xdigit_only = true; - if (annotation == null) { -@@ -758,6 +655,7 @@ class IBusEmojier : Gtk.Window { - return true; - } - -+ - public void update_candidate_window() { - string annotation = m_entry.get_text(); - if (annotation.length == 0) { -@@ -788,6 +686,7 @@ class IBusEmojier : Gtk.Window { - show_candidate_panel(); - } - -+ - private void show_candidate_panel() { - remove_all_children(); - set_fixed_size(); -@@ -848,6 +747,8 @@ class IBusEmojier : Gtk.Window { - // enter_notify_event conflicts with keyboard operations. - if (!m_enter_notify_enable) - return true; -+ if (m_lookup_table.get_cursor_pos() == index) -+ return true; - m_lookup_table.set_cursor_pos(index); - if (m_entry_notify_show_id > 0) { - GLib.Source.remove(m_entry_notify_show_id); -@@ -927,6 +828,7 @@ class IBusEmojier : Gtk.Window { - } - } - -+ - private void hide_candidate_panel() { - m_enter_notify_enable = true; - m_candidate_panel_is_visible = false; -@@ -934,6 +836,7 @@ class IBusEmojier : Gtk.Window { - show_category_list(); - } - -+ - private bool if_in_range_of_lookup(uint keyval) { - if (!m_candidate_panel_is_visible) - return false; -@@ -957,6 +860,7 @@ class IBusEmojier : Gtk.Window { - return true; - } - -+ - private void set_number_on_lookup(uint keyval) { - if (keyval == Gdk.Key.@0) - keyval = Gdk.Key.@9 + 1; -@@ -969,6 +873,7 @@ class IBusEmojier : Gtk.Window { - m_result = text.text; - } - -+ - private void enter_notify_disable_with_timer() { - // Enable keyboard operation and disable mouse operation. - m_enter_notify_enable = false; -@@ -982,6 +887,7 @@ class IBusEmojier : Gtk.Window { - }); - } - -+ - private void candidate_panel_cursor_down() { - enter_notify_disable_with_timer(); - uint ncandidates = m_lookup_table.get_number_of_candidates(); -@@ -996,6 +902,7 @@ class IBusEmojier : Gtk.Window { - show_candidate_panel(); - } - -+ - private void candidate_panel_cursor_up() { - enter_notify_disable_with_timer(); - int ncandidates = (int)m_lookup_table.get_number_of_candidates(); -@@ -1013,6 +920,7 @@ class IBusEmojier : Gtk.Window { - show_candidate_panel(); - } - -+ - private void category_list_cursor_move(uint keyval) { - GLib.List list = m_list_box.get_children(); - if (keyval == Gdk.Key.Down) { -@@ -1027,6 +935,7 @@ class IBusEmojier : Gtk.Window { - show_category_list(); - } - -+ - private bool key_press_cursor_horizontal(uint keyval, - uint modifiers) { - assert (keyval == Gdk.Key.Left || keyval == Gdk.Key.Right); -@@ -1061,6 +970,7 @@ class IBusEmojier : Gtk.Window { - return true; - } - -+ - private bool key_press_cursor_vertical(uint keyval) { - assert (keyval == Gdk.Key.Down || keyval == Gdk.Key.Up); - -@@ -1075,6 +985,7 @@ class IBusEmojier : Gtk.Window { - return true; - } - -+ - private bool key_press_cursor_home_end(uint keyval, - uint modifiers) { - assert (keyval == Gdk.Key.Home || keyval == Gdk.Key.End); -@@ -1107,14 +1018,11 @@ class IBusEmojier : Gtk.Window { - return false; - } - -+ - private bool key_press_cursor_escape() { - if (m_candidate_panel_is_visible) { - hide_candidate_panel(); - return true; -- } else if (m_current_category_type == CategoryType.LANG) { -- m_current_category_type = CategoryType.EMOJI; -- show_candidate_panel(); -- return true; - } else if (m_entry.get_text().length == 0) { - m_loop.quit(); - hide_candidate_panel(); -@@ -1124,6 +1032,7 @@ class IBusEmojier : Gtk.Window { - return true; - } - -+ - private void entry_enter_keyval(uint keyval) { - unichar ch = IBus.keyval_to_unicode(keyval); - if (ch.iscntrl()) -@@ -1145,6 +1054,7 @@ class IBusEmojier : Gtk.Window { - m_entry.set_position(pos); - } - -+ - public string run(Gdk.Event event, - string input_context_path) { - assert (m_loop == null); -@@ -1178,7 +1088,6 @@ class IBusEmojier : Gtk.Window { - keyboard = device.get_associated_device(); - } - -- m_current_category_type = CategoryType.EMOJI; - show_category_list(); - m_entry.set_activates_default(true); - show_all(); -@@ -1229,12 +1138,14 @@ class IBusEmojier : Gtk.Window { - return m_result; - } - -+ - /* override virtual functions */ - public override void show() { - base.show(); - set_focus_visible(true); - } - -+ - public override bool key_press_event(Gdk.EventKey event) { - uint keyval = event.keyval; - uint modifiers = event.state; -@@ -1263,10 +1174,7 @@ class IBusEmojier : Gtk.Window { - } else if (m_category_active_index > 0) { - Gtk.ListBoxRow gtkrow = m_list_box.get_selected_row(); - EBoxRow row = gtkrow as EBoxRow; -- if (m_current_category_type == CategoryType.EMOJI) -- show_emoji_for_category(row); -- else if (m_current_category_type == CategoryType.LANG) -- activated_language(row); -+ show_emoji_for_category(row); - } - return true; - case Gdk.Key.BackSpace: -@@ -1366,27 +1274,33 @@ class IBusEmojier : Gtk.Window { - return true; - } - -+ - public bool is_running() { - return m_is_running; - } - -+ - public string get_input_context_path() { - return m_input_context_path; - } - -+ - public string get_selected_string() { - return m_result; - } - -+ - public void reset() { - m_input_context_path = ""; - m_result = null; - } - -+ - public void set_emoji_font(string emoji_font) { - m_emoji_font = emoji_font; - } - -+ - public void set_favorites(string[]? unowned_favorites) { - m_favorites = {}; - foreach (string favorite in unowned_favorites) { -@@ -1394,6 +1308,7 @@ class IBusEmojier : Gtk.Window { - } - } - -+ - public bool has_loaded_emoji_dict() { - if (m_emoji_to_data_dict == null) - return false; -@@ -1402,4 +1317,20 @@ class IBusEmojier : Gtk.Window { - return false; - return true; - } -+ -+ -+ public void set_annotation_lang(string lang) { -+ if (m_current_lang_id == lang) -+ return; -+ if (m_reload_emoji_dict_id > 0) { -+ GLib.Source.remove(m_reload_emoji_dict_id); -+ m_reload_emoji_dict_id = 0; -+ } -+ m_current_lang_id = lang; -+ m_reload_emoji_dict_id = GLib.Idle.add(() => { -+ reload_emoji_dict(); -+ m_reload_emoji_dict_id = 0; -+ return false; -+ }); -+ } - } -diff --git a/ui/gtk3/ibusemojidialog.h b/ui/gtk3/ibusemojidialog.h -index c36060c..0f84a48 100644 ---- a/ui/gtk3/ibusemojidialog.h -+++ b/ui/gtk3/ibusemojidialog.h -@@ -149,5 +149,16 @@ void ibus_emojier_set_favorites (IBusEmojier* self, - */ - gboolean ibus_emojier_has_loaded_emoji_dict (IBusEmojier* self); - -+/** -+ * ibus_emojier_set_annotation_lang: -+ * @self: An #IBusEmojier -+ * @lang: A langauge id for emoji annotations. -+ * -+ * Set a language id for emoji annotations. #IBusEmojier will load -+ * $PKGDATADIR/dicts/emoji-@lang.dict. The default is "en". -+ */ -+void ibus_emojier_set_annotation_lang (IBusEmojier* self, -+ const gchar* lang); -+ - G_END_DECLS - #endif -diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala -index 0982134..7350dcc 100644 ---- a/ui/gtk3/panel.vala -+++ b/ui/gtk3/panel.vala -@@ -54,6 +54,7 @@ class Panel : IBus.PanelService { - private GLib.Settings m_settings_general = null; - private GLib.Settings m_settings_hotkey = null; - private GLib.Settings m_settings_panel = null; -+ private GLib.Settings m_settings_emoji = null; - private IconType m_icon_type = IconType.STATUS_ICON; - private Indicator m_indicator; - #if INDICATOR -@@ -161,6 +162,7 @@ class Panel : IBus.PanelService { - m_settings_hotkey = - new GLib.Settings("org.freedesktop.ibus.general.hotkey"); - m_settings_panel = new GLib.Settings("org.freedesktop.ibus.panel"); -+ m_settings_emoji = new GLib.Settings("org.freedesktop.ibus.panel.emoji"); - - m_settings_general.changed["preload-engines"].connect((key) => { - update_engines(m_settings_general.get_strv(key), -@@ -193,19 +195,10 @@ class Panel : IBus.PanelService { - bind_switch_shortcut(); - }); - -- m_settings_hotkey.changed["emoji"].connect((key) => { -- unbind_switch_shortcut(KeyEventFuncType.EMOJI_TYPING); -- bind_emoji_shortcut(); -- }); -- - m_settings_panel.changed["custom-font"].connect((key) => { - set_custom_font(); - }); - -- m_settings_panel.changed["emoji-font"].connect((key) => { -- set_custom_font(); -- }); -- - m_settings_panel.changed["use-custom-font"].connect((key) => { - set_custom_font(); - }); -@@ -239,9 +232,22 @@ class Panel : IBus.PanelService { - set_property_icon_delay_time(); - }); - -- m_settings_panel.changed["emoji-favorites"].connect((key) => { -+ m_settings_emoji.changed["hotkey"].connect((key) => { -+ unbind_switch_shortcut(KeyEventFuncType.EMOJI_TYPING); -+ bind_emoji_shortcut(); -+ }); -+ -+ m_settings_emoji.changed["font"].connect((key) => { -+ set_custom_font(); -+ }); -+ -+ m_settings_emoji.changed["favorites"].connect((key) => { - set_emoji_favorites(); - }); -+ -+ m_settings_emoji.changed["lang"].connect((key) => { -+ set_emoji_lang(); -+ }); - } - - #if INDICATOR -@@ -398,7 +404,7 @@ class Panel : IBus.PanelService { - - private void bind_emoji_shortcut() { - #if EMOJI_DICT -- string[] accelerators = m_settings_hotkey.get_strv("emoji"); -+ string[] accelerators = m_settings_emoji.get_strv("hotkey"); - - var keybinding_manager = KeybindingManager.get_instance(); - -@@ -584,9 +590,9 @@ class Panel : IBus.PanelService { - return; - } - -- string emoji_font = m_settings_panel.get_string("emoji-font"); -+ string emoji_font = m_settings_emoji.get_string("font"); - if (emoji_font == null) { -- warning("No config panel:emoji-font."); -+ warning("No config emoji:font."); - return; - } - m_emojier.set_emoji_font(emoji_font); -@@ -760,7 +766,11 @@ class Panel : IBus.PanelService { - } - - private void set_emoji_favorites() { -- m_emojier.set_favorites(m_settings_panel.get_strv("emoji-favorites")); -+ m_emojier.set_favorites(m_settings_emoji.get_strv("favorites")); -+ } -+ -+ private void set_emoji_lang() { -+ m_emojier.set_annotation_lang(m_settings_emoji.get_string("lang")); - } - - private int compare_versions(string version1, string version2) { -@@ -877,6 +887,7 @@ class Panel : IBus.PanelService { - set_xkb_icon_rgba(); - set_property_icon_delay_time(); - set_emoji_favorites(); -+ set_emoji_lang(); - } - - private void engine_contexts_insert(IBus.EngineDesc engine) { --- -2.9.3 - -From 164300758c70fd3a590788e038de31b6c53d458a Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Tue, 28 Mar 2017 12:15:26 +0900 -Subject: [PATCH] data: Fix a typo - -R=Shawn.P.Huang@gmail.com - -Review URL: https://codereview.appspot.com/321800043 ---- - data/ibus.schemas.in | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in -index c0bbd6f..096dd71 100644 ---- a/data/ibus.schemas.in -+++ b/data/ibus.schemas.in -@@ -386,7 +386,7 @@ - Default language for emoji dictionary - Choose a default language of emoji dictionaries on - the emoji dialog. The value $lang is applied to -- /usr/share/unicode/cldr/common/annotations/$lang.xml -+ /usr/share/ibus/dicts/emoji-$lang.dict - - - --- -2.9.3 - -From c3168d4701eb4e89094249abaa4f0f83ab24149b Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Wed, 29 Mar 2017 13:01:28 +0900 -Subject: [PATCH] Fix IBusEmojiDialog_1_0_gir_LIBS for --as-needed LDFLAGS - -The order gets omitted libibus-1.0.la - -BUG=https://github.com/ibus/ibus/issues/1917 - -Review URL: https://codereview.appspot.com/324720043 ---- - ui/gtk3/Makefile.am | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am -index b055f67..7122ff3 100644 ---- a/ui/gtk3/Makefile.am -+++ b/ui/gtk3/Makefile.am -@@ -205,7 +205,7 @@ IBusEmojiDialog_1_0_gir_SCANNERFLAGS = \ - $(NULL) - IBusEmojiDialog-1.0.gir: $(libibus_emoji_dialog) Makefile - IBusEmojiDialog_1_0_gir_INCLUDES = Gtk-3.0 GLib-2.0 GObject-2.0 Gio-2.0 --IBusEmojiDialog_1_0_gir_LIBS = $(libibus) $(libibus_emoji_dialog) -+IBusEmojiDialog_1_0_gir_LIBS = $(libibus_emoji_dialog) $(libibus) - IBusEmojiDialog_1_0_gir_FILES = \ - $(addprefix $(srcdir)/,$(introspection_sources)) \ - $(NULL) --- -2.9.3 - -From 8b6f9fa531aa9d9b5d6c0184b2294d071f920d7f Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Thu, 30 Mar 2017 12:08:39 +0900 -Subject: [PATCH] ui/gtk3: Enable to type multiple code points on Emojier - -- Can type multiple Unicode characters with digits and Shift-space - keys. E.g. "1f468 1f468 1f466" -- Always show Unicode points of the selected emoji -- Removed a function to commit an emoji by typing a digit char since - it conflicts with digit annotations, code points but it had enabled - with single digits only so not so useful. - -R=Shawn.P.Huang@gmail.com - -Review URL: https://codereview.appspot.com/321820043 ---- - ui/gtk3/emojier.vala | 207 ++++++++++++++++++++++++--------------------------- - 1 file changed, 98 insertions(+), 109 deletions(-) - -diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala -index 20c1378..8a2726c 100644 ---- a/ui/gtk3/emojier.vala -+++ b/ui/gtk3/emojier.vala -@@ -97,12 +97,27 @@ class IBusEmojier : Gtk.Window { - set_label(text); - } - } -- private class EPaddedLabel : Gtk.Box { -+ private class EPaddedLabel : Gtk.Label { - public EPaddedLabel(string text, -- Gtk.Align align, -- TravelDirection direction=TravelDirection.NONE) { -+ Gtk.Align align) { - GLib.Object( - name : "IBusEmojierPaddedLabel", -+ halign : align, -+ valign : Gtk.Align.CENTER, -+ margin_start : 20, -+ margin_end : 20, -+ margin_top : 6, -+ margin_bottom : 6 -+ ); -+ set_text(text); -+ } -+ } -+ private class EPaddedLabelBox : Gtk.Box { -+ public EPaddedLabelBox(string text, -+ Gtk.Align align, -+ TravelDirection direction=TravelDirection.NONE) { -+ GLib.Object( -+ name : "IBusEmojierPaddedLabelBox", - orientation : Gtk.Orientation.HORIZONTAL, - spacing : 0 - ); -@@ -118,36 +133,29 @@ class IBusEmojier : Gtk.Window { - } - pack_start(icon, false, true, 0); - } -- Gtk.Label label = new Gtk.Label(text); -- label.set_halign(align); -- label.set_valign(Gtk.Align.CENTER); -- label.set_margin_start(20); -- label.set_margin_end(20); -- label.set_margin_top(6); -- label.set_margin_bottom(6); -+ EPaddedLabel label = new EPaddedLabel(text, align); - pack_start(label, true, true, 0); - } - } -- private class ETitleLabel : Gtk.Box { -- private Gtk.Label m_label; -+ private class ETitleLabelBox : Gtk.Box { -+ EPaddedLabel m_lang_label; - private Gtk.Button m_close_button; - private ulong m_close_handler; - -- public ETitleLabel(string text, -- Gtk.Align align) { -+ public ETitleLabelBox(string text, -+ Gtk.Align align) { - GLib.Object( -- name : "IBusEmojierTitleLabel", -+ name : "IBusEmojierTitleLabelBox", - orientation : Gtk.Orientation.HORIZONTAL, - spacing : 0 - ); -- m_label = new Gtk.Label(text); -- m_label.set_halign(align); -- m_label.set_valign(align); -- m_label.set_margin_start(20); -- m_label.set_margin_end(20); -- m_label.set_margin_top(6); -- m_label.set_margin_bottom(6); -- pack_start(m_label, true, true, 0); -+ EPaddedLabel label = new EPaddedLabel(text, align); -+ pack_start(label, true, true, 0); -+ Gtk.Separator separator = -+ new Gtk.Separator (Gtk.Orientation.VERTICAL); -+ pack_start(separator, false, true, 0); -+ m_lang_label = new EPaddedLabel("", align); -+ pack_start(m_lang_label, false, true, 0); - IconWidget icon = new IconWidget("window-close", Gtk.IconSize.MENU); - m_close_button = new Gtk.Button(); - m_close_button.add(icon); -@@ -168,8 +176,8 @@ class IBusEmojier : Gtk.Window { - m_close_handler = 0; - } - } -- public void set_label(string str) { -- m_label.set_label(str); -+ public void set_lang_label(string str) { -+ m_lang_label.set_label(str); - } - } - -@@ -181,7 +189,7 @@ class IBusEmojier : Gtk.Window { - private const uint EMOJI_GRID_PAGE = 10; - private ThemedRGBA m_rgba; - private Gtk.Box m_vbox; -- private ETitleLabel m_title; -+ private ETitleLabelBox m_title; - private EEntry m_entry; - private string? m_backward; - private EScrolledWindow? m_scrolled_window = null; -@@ -278,8 +286,8 @@ class IBusEmojier : Gtk.Window { - m_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); - add(m_vbox); - -- m_title = new ETitleLabel(_("Emoji Dialog"), -- Gtk.Align.CENTER); -+ m_title = new ETitleLabelBox(_("Emoji Dialog"), -+ Gtk.Align.CENTER); - m_vbox.add(m_title); - m_entry = new EEntry(); - m_entry.set_placeholder_text(_("Type annotation or choose emoji")); -@@ -419,6 +427,21 @@ class IBusEmojier : Gtk.Window { - } - - -+ private string utf8_code_point(string str) { -+ StringBuilder buff = new StringBuilder(); -+ int length = str.char_count(); -+ for (int i = 0; i < length; i++) { -+ unichar ch = str.get_char(0); -+ if (i == 0) -+ buff.append("U+%04X".printf(ch)); -+ else -+ buff.append(" %04X".printf(ch)); -+ str = str.next_char(); -+ } -+ return buff.str; -+ } -+ -+ - private void update_emoji_to_data_dict(IBus.EmojiData data, - string lang) { - string emoji = data.get_emoji(); -@@ -501,7 +524,7 @@ class IBusEmojier : Gtk.Window { - private void remove_all_children() { - foreach (Gtk.Widget w in m_vbox.get_children()) { - if (w.name == "IBusEmojierEntry" || -- w.name == "IBusEmojierTitleLabel") { -+ w.name == "IBusEmojierTitleLabelBox") { - continue; - } - w.destroy(); -@@ -514,10 +537,9 @@ class IBusEmojier : Gtk.Window { - m_scrolled_window = new EScrolledWindow(); - set_fixed_size(); - -- string language = "%s (%s)".printf( -- _("Emoji Dialog"), -- IBus.get_language_name(m_current_lang_id)); -- m_title.set_label(language); -+ string language = -+ IBus.get_language_name(m_current_lang_id); -+ m_title.set_lang_label(language); - m_vbox.add(m_scrolled_window); - Gtk.Viewport viewport = new Gtk.Viewport(null, null); - m_scrolled_window.add(viewport); -@@ -535,8 +557,8 @@ class IBusEmojier : Gtk.Window { - uint n = 1; - if (m_favorites.length > 0) { - EBoxRow row = new EBoxRow("@favorites"); -- EPaddedLabel widget = -- new EPaddedLabel(_("Favorites"), Gtk.Align.CENTER); -+ EPaddedLabelBox widget = -+ new EPaddedLabelBox(_("Favorites"), Gtk.Align.CENTER); - row.add(widget); - m_list_box.add(row); - if (n++ == m_category_active_index) -@@ -551,9 +573,9 @@ class IBusEmojier : Gtk.Window { - foreach (unowned string category in categories) { - EBoxRow row = new EBoxRow(category); - string locale_category = _(category); -- EPaddedLabel widget = -- new EPaddedLabel(utf8_title(locale_category), -- Gtk.Align.CENTER); -+ EPaddedLabelBox widget = -+ new EPaddedLabelBox(utf8_title(locale_category), -+ Gtk.Align.CENTER); - row.add(widget); - m_list_box.add(row); - if (n++ == m_category_active_index) -@@ -626,18 +648,23 @@ class IBusEmojier : Gtk.Window { - } - - -- private bool check_unicode_point(string? annotation=null) { -- bool check_xdigit_only = true; -- if (annotation == null) { -- annotation = m_entry.get_text(); -- m_unicode_point = null; -- check_xdigit_only = false; -- } -+ private bool check_unicode_point() { -+ string annotation = m_entry.get_text(); -+ m_unicode_point = null; - GLib.StringBuilder buff = new GLib.StringBuilder(); -+ GLib.StringBuilder retval = new GLib.StringBuilder(); - for (int i = 0; i < annotation.char_count(); i++) { - unichar ch = annotation.get_char(i); - if (ch == 0) - return false; -+ if (ch.isspace()) { -+ unichar code = (unichar)buff.str.to_ulong(null, 16); -+ buff.erase(); -+ if (!code.validate()) -+ return false; -+ retval.append(code.to_string()); -+ continue; -+ } - if (!ch.isxdigit()) - return false; - buff.append_unichar(ch); -@@ -645,9 +672,8 @@ class IBusEmojier : Gtk.Window { - unichar code = (unichar)buff.str.to_ulong(null, 16); - if (!code.validate()) - return false; -- if (check_xdigit_only) -- return true; -- m_unicode_point = code.to_string(); -+ retval.append(code.to_string()); -+ m_unicode_point = retval.str; - if (m_unicode_point == null) - return true; - IBus.Text text = new IBus.Text.from_string(m_unicode_point); -@@ -699,9 +725,10 @@ class IBusEmojier : Gtk.Window { - if (m_backward != null) { - string backward_desc = - "%s (%u / %u)".printf(m_backward, cursor, ncandidates - 1); -- EPaddedLabel label = new EPaddedLabel(backward_desc, -- Gtk.Align.CENTER, -- TravelDirection.BACKWARD); -+ EPaddedLabelBox label = -+ new EPaddedLabelBox(backward_desc, -+ Gtk.Align.CENTER, -+ TravelDirection.BACKWARD); - Gtk.Button button = new Gtk.Button(); - button.add(label); - m_vbox.add(button); -@@ -776,27 +803,24 @@ class IBusEmojier : Gtk.Window { - IBus.Text candidate = m_lookup_table.get_candidate(cursor); - unowned IBus.EmojiData? data = - m_emoji_to_data_dict.lookup(candidate.text); -- if (cursor == 0 && candidate.text == m_unicode_point) { -- EPaddedLabel widget = new EPaddedLabel( -- _("Description: Unicode point U+%04X").printf( -- m_unicode_point.get_char(0)), -- Gtk.Align.START); -- m_vbox.add(widget); -- widget.show_all(); -- if (data == null) -- return; -- } else if (data == null) { -+ if (data == null) { - // TODO: Provide a custom description and annotation for - // the favorite emojis. -- EPaddedLabel widget = new EPaddedLabel( -+ EPaddedLabelBox widget = new EPaddedLabelBox( - _("Description: %s").printf(_("None")), - Gtk.Align.START); - m_vbox.add(widget); - widget.show_all(); -+ EPaddedLabelBox widget_code = new EPaddedLabelBox( -+ _("Code point: %s").printf( -+ utf8_code_point(candidate.text)), -+ Gtk.Align.START); -+ m_vbox.add(widget_code); -+ widget_code.show_all(); - return; - } else { - unowned string description = data.get_description(); -- EPaddedLabel widget = new EPaddedLabel( -+ EPaddedLabelBox widget = new EPaddedLabelBox( - _("Description: %s").printf(description), - Gtk.Align.START); - m_vbox.add(widget); -@@ -812,19 +836,26 @@ class IBusEmojier : Gtk.Window { - else - buff.append_printf(" | %s", annotation); - if (buff.str.char_count() > 30) { -- EPaddedLabel widget = new EPaddedLabel(buff.str, -- Gtk.Align.START); -+ EPaddedLabelBox widget = -+ new EPaddedLabelBox(buff.str, -+ Gtk.Align.START); - m_vbox.add(widget); - widget.show_all(); - buff.erase(); - } - } - if (buff.str != "") { -- EPaddedLabel widget = new EPaddedLabel(buff.str, -- Gtk.Align.START); -+ EPaddedLabelBox widget = new EPaddedLabelBox(buff.str, -+ Gtk.Align.START); - m_vbox.add(widget); - widget.show_all(); - } -+ EPaddedLabelBox widget_code = new EPaddedLabelBox( -+ _("Code point: %s").printf( -+ utf8_code_point(candidate.text)), -+ Gtk.Align.START); -+ m_vbox.add(widget_code); -+ widget_code.show_all(); - } - } - -@@ -837,43 +868,6 @@ class IBusEmojier : Gtk.Window { - } - - -- private bool if_in_range_of_lookup(uint keyval) { -- if (!m_candidate_panel_is_visible) -- return false; -- StringBuilder buffer_string = new StringBuilder(m_entry.get_text()); -- unichar ch = IBus.keyval_to_unicode (keyval); -- buffer_string.append_unichar(ch); -- if (check_unicode_point(buffer_string.str)) -- return false; -- if (keyval < Gdk.Key.@0 || keyval > Gdk.Key.@9) -- return false; -- if (keyval == Gdk.Key.@0) -- keyval = Gdk.Key.@9 + 1; -- uint index = keyval - Gdk.Key.@1 + 1; -- uint candidates = m_lookup_table.get_number_of_candidates(); -- uint cursor_pos = m_lookup_table.get_cursor_pos(); -- uint page_size = m_lookup_table.get_page_size(); -- if (index > uint.min(candidates - (cursor_pos / page_size) * page_size, -- page_size)) { -- return false; -- } -- return true; -- } -- -- -- private void set_number_on_lookup(uint keyval) { -- if (keyval == Gdk.Key.@0) -- keyval = Gdk.Key.@9 + 1; -- uint index = keyval - Gdk.Key.@1; -- uint cursor_pos = m_lookup_table.get_cursor_pos(); -- uint cursor_in_page= m_lookup_table.get_cursor_in_page(); -- uint real_index = cursor_pos - cursor_in_page + index; -- m_lookup_table.set_cursor_pos(real_index); -- IBus.Text text = m_lookup_table.get_candidate(real_index); -- m_result = text.text; -- } -- -- - private void enter_notify_disable_with_timer() { - // Enable keyboard operation and disable mouse operation. - m_enter_notify_enable = false; -@@ -1154,11 +1148,6 @@ class IBusEmojier : Gtk.Window { - * key_release_event() so that this can know if the event - * was handled by IME. - */ -- if (if_in_range_of_lookup(keyval)) { -- set_number_on_lookup(keyval); -- m_loop.quit(); -- return true; -- } - switch (keyval) { - case Gdk.Key.Escape: - if (key_press_cursor_escape()) --- -2.9.3 - -From cb0a36c254dc7a96b2a984e715f96cc2ec32e2d5 Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Mon, 3 Apr 2017 12:24:27 +0900 -Subject: [PATCH] src: Enable unicode_alt in EmojiOne json file - -EmojiOne json file has unicode_alt property which includes -emoji modifer characters. - -Review URL: https://codereview.appspot.com/316420043 ---- - src/emoji-parser.c | 20 +++++++++++++++++--- - src/ibusemoji.c | 48 ++++++++++++++++++++++++++++++++++++++++++++---- - src/ibusemoji.h | 14 ++++++++++++++ - ui/gtk3/emojier.vala | 9 ++++++--- - 4 files changed, 81 insertions(+), 10 deletions(-) - -diff --git a/src/emoji-parser.c b/src/emoji-parser.c -index f9e3470..e5dce3f 100644 ---- a/src/emoji-parser.c -+++ b/src/emoji-parser.c -@@ -40,6 +40,7 @@ - typedef struct _EmojiData EmojiData; - struct _EmojiData { - gchar *emoji; -+ gchar *emoji_alternates; - GSList *annotations; - gboolean is_annotation; - gchar *description; -@@ -54,6 +55,7 @@ reset_emoji_element (EmojiData *data) - g_assert (data != NULL); - - g_clear_pointer (&data->emoji, g_free); -+ g_clear_pointer (&data->emoji_alternates, g_free); - g_slist_free_full (data->annotations, g_free); - data->annotations = NULL; - g_clear_pointer (&data->description, g_free); -@@ -112,6 +114,10 @@ update_emoji_list (EmojiData *data) - "category", - data->category ? data->category - : g_strdup (""), -+ "emoji-alternates", -+ data->emoji_alternates -+ ? data->emoji_alternates -+ : g_strdup (""), - NULL); - data->list = g_slist_append (data->list, emoji); - } -@@ -271,7 +277,8 @@ failed_to_parse_unicode_annotations: - - static gboolean - parse_emojione_unicode (JsonNode *node, -- EmojiData *data) -+ EmojiData *data, -+ gboolean is_alternates) - { - const gchar *str, *unicode; - gchar *endptr = NULL; -@@ -305,7 +312,10 @@ parse_emojione_unicode (JsonNode *node, - endptr = NULL; - } - -- data->emoji = g_string_free (emoji, FALSE); -+ if (is_alternates) -+ data->emoji_alternates = g_string_free (emoji, FALSE); -+ else -+ data->emoji = g_string_free (emoji, FALSE); - - return TRUE; - } -@@ -480,7 +490,11 @@ parse_emojione_emoji_data (JsonNode *node, - EmojiData *data) - { - if (g_strcmp0 (member, "unicode") == 0) -- return parse_emojione_unicode (node, data); -+ return parse_emojione_unicode (node, data, FALSE); -+ else if (g_strcmp0 (member, "unicode_alt") == 0) -+ return parse_emojione_unicode (node, data, TRUE); -+ else if (g_strcmp0 (member, "unicode_alternates") == 0) -+ return parse_emojione_unicode (node, data, TRUE); - else if (g_strcmp0 (member, "shortname") == 0) - return parse_emojione_shortname (node, data); - else if (g_strcmp0 (member, "name") == 0) -diff --git a/src/ibusemoji.c b/src/ibusemoji.c -index c61cd70..4be092a 100644 ---- a/src/ibusemoji.c -+++ b/src/ibusemoji.c -@@ -29,14 +29,15 @@ - #include "ibusinternal.h" - - #define IBUS_EMOJI_DATA_MAGIC "IBusEmojiData" --#define IBUS_EMOJI_DATA_VERSION (2) -+#define IBUS_EMOJI_DATA_VERSION (3) - - enum { - PROP_0 = 0, - PROP_EMOJI, - PROP_ANNOTATIONS, - PROP_DESCRIPTION, -- PROP_CATEGORY -+ PROP_CATEGORY, -+ PROP_EMOJI_ALTERNATES - }; - - struct _IBusEmojiDataPrivate { -@@ -44,6 +45,7 @@ struct _IBusEmojiDataPrivate { - GSList *annotations; - gchar *description; - gchar *category; -+ gchar *emoji_alternates; - }; - - #define IBUS_EMOJI_DATA_GET_PRIVATE(o) \ -@@ -142,6 +144,19 @@ ibus_emoji_data_class_init (IBusEmojiDataClass *class) - "The emoji category", - "", - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); -+ -+ /** -+ * IBusEmojiData:emoji_alternates: -+ * -+ * The emoji alternate characters -+ */ -+ g_object_class_install_property (gobject_class, -+ PROP_EMOJI_ALTERNATES, -+ g_param_spec_string ("emoji-alternates", -+ "emoji alternate charasters", -+ "The emoji alternate characters UTF-8", -+ "", -+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - } - - static void -@@ -180,19 +195,24 @@ ibus_emoji_data_set_property (IBusEmojiData *emoji, - emoji->priv->emoji = g_value_dup_string (value); - break; - case PROP_ANNOTATIONS: -- g_assert (emoji->priv->annotations == NULL); -+ if (emoji->priv->annotations) -+ g_slist_free_full (emoji->priv->annotations, g_free); - emoji->priv->annotations = - g_slist_copy_deep (g_value_get_pointer (value), - (GCopyFunc) g_strdup, NULL); - break; - case PROP_DESCRIPTION: -- g_assert (emoji->priv->description == NULL); -+ g_free (emoji->priv->description); - emoji->priv->description = g_value_dup_string (value); - break; - case PROP_CATEGORY: - g_assert (emoji->priv->category == NULL); - emoji->priv->category = g_value_dup_string (value); - break; -+ case PROP_EMOJI_ALTERNATES: -+ g_assert (emoji->priv->emoji_alternates == NULL); -+ emoji->priv->emoji_alternates = g_value_dup_string (value); -+ break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (emoji, prop_id, pspec); - } -@@ -220,6 +240,9 @@ ibus_emoji_data_get_property (IBusEmojiData *emoji, - case PROP_CATEGORY: - g_value_set_string (value, ibus_emoji_data_get_category (emoji)); - break; -+ case PROP_EMOJI_ALTERNATES: -+ g_value_set_string (value, ibus_emoji_data_get_emoji_alternates(emoji)); -+ break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (emoji, prop_id, pspec); - } -@@ -247,6 +270,9 @@ ibus_emoji_data_serialize (IBusEmojiData *emoji, - } - g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->description)); - g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->category)); -+ g_variant_builder_add (builder, "s", -+ NOTNULL (emoji->priv->emoji_alternates)); -+#undef NOTNULL - return TRUE; - } - -@@ -277,6 +303,10 @@ ibus_emoji_data_deserialize (IBusEmojiData *emoji, - &emoji->priv->description); - ibus_g_variant_get_child_string (variant, retval++, - &emoji->priv->category); -+ if (g_variant_n_children (variant) < retval + 1) -+ return retval; -+ ibus_g_variant_get_child_string (variant, retval++, -+ &emoji->priv->emoji_alternates); - return retval; - } - -@@ -295,6 +325,7 @@ ibus_emoji_data_copy (IBusEmojiData *dest, - NULL); - dest->priv->description = g_strdup (src->priv->description); - dest->priv->category = g_strdup (src->priv->category); -+ dest->priv->emoji_alternates = g_strdup (src->priv->emoji_alternates); - return TRUE; - } - -@@ -314,6 +345,7 @@ ibus_emoji_data_new (const gchar *first_property_name, ...) - g_assert (emoji->priv->emoji != NULL); - g_assert (emoji->priv->description != NULL); - g_assert (emoji->priv->category != NULL); -+ g_assert (emoji->priv->emoji_alternates != NULL); - return emoji; - } - -@@ -370,6 +402,14 @@ ibus_emoji_data_get_category (IBusEmojiData *emoji) - return emoji->priv->category; - } - -+const gchar * -+ibus_emoji_data_get_emoji_alternates (IBusEmojiData *emoji) -+{ -+ g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL); -+ -+ return emoji->priv->emoji_alternates; -+} -+ - - static void - variant_foreach_add_emoji (IBusEmojiData *emoji, -diff --git a/src/ibusemoji.h b/src/ibusemoji.h -index eb24fdd..233cadd 100644 ---- a/src/ibusemoji.h -+++ b/src/ibusemoji.h -@@ -156,6 +156,20 @@ const gchar * ibus_emoji_data_get_category (IBusEmojiData *emoji); - - - /** -+ * ibus_emoji_data_get_emoji_alternates: -+ * @emoji : An #IBusEmojiData -+ * -+ * Gets the emoji alternate characters in #IBusEmojiData. It should not be -+ * freed. The alternates are defined in "unicode_alt" in EmojiOne json. -+ * -+ * Returns: emoji alternates property in #IBusEmojiData -+ * -+ */ -+const gchar * ibus_emoji_data_get_emoji_alternates -+ (IBusEmojiData *emoji); -+ -+ -+/** - * ibus_emoji_dict_save: - * @path: A path of the saved dictionary file. - * @dict: (element-type utf8 gpointer) (transfer none): An Emoji dictionary -diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala -index 8a2726c..7b6107f 100644 ---- a/ui/gtk3/emojier.vala -+++ b/ui/gtk3/emojier.vala -@@ -381,7 +381,8 @@ class IBusEmojier : Gtk.Window { - - - private void update_annotation_to_emojis_dict(IBus.EmojiData data) { -- string emoji = data.get_emoji(); -+ string emoji = (data.get_emoji_alternates() != "") ? -+ data.get_emoji_alternates() : data.get_emoji(); - unowned GLib.SList annotations = data.get_annotations(); - foreach (string annotation in annotations) { - bool has_emoji = false; -@@ -444,7 +445,8 @@ class IBusEmojier : Gtk.Window { - - private void update_emoji_to_data_dict(IBus.EmojiData data, - string lang) { -- string emoji = data.get_emoji(); -+ string emoji = (data.get_emoji_alternates() != "") ? -+ data.get_emoji_alternates() : data.get_emoji(); - if (lang == "en") { - string description = utf8_down(data.get_description()); - unowned GLib.SList annotations = data.get_annotations(); -@@ -496,7 +498,8 @@ class IBusEmojier : Gtk.Window { - - private void update_category_to_emojis_dict(IBus.EmojiData data, - string lang) { -- string emoji = data.get_emoji(); -+ string emoji = (data.get_emoji_alternates() != "") ? -+ data.get_emoji_alternates() : data.get_emoji(); - string category = data.get_category(); - if (lang == "en" && category != "") { - bool has_emoji = false; --- -2.9.3 - -From 648f58a361ea1407f229ed1682486240cc2e5026 Mon Sep 17 00:00:00 2001 -From: Peng Wu -Date: Mon, 10 Apr 2017 14:54:08 +0900 -Subject: [PATCH] Make ibus emojier dialog as an unique application - -Because wayland doesn't support clipboard persistence, just hide the dialog; will show the emojier dialog again, when run it again. - -In next patches, ibus emoji command will just spawn the process of ibus-ui-emojier. - -BUG= -R=takao.fujiwara1@gmail.com - -Review URL: https://codereview.appspot.com/320450043 - -Patch from Peng Wu . ---- - po/POTFILES.in | 1 + - po/POTFILES.skip | 1 + - ui/gtk3/Makefile.am | 24 ++++++++++++++++++++++++ - 3 files changed, 26 insertions(+) - -diff --git a/po/POTFILES.in b/po/POTFILES.in -index 469f90b..65a3526 100644 ---- a/po/POTFILES.in -+++ b/po/POTFILES.in -@@ -65,3 +65,4 @@ ui/gtk3/property.vala - ui/gtk3/propertypanel.vala - ui/gtk3/separator.vala - ui/gtk3/switcher.vala -+ui/gtk3/emojierapp.vala -diff --git a/po/POTFILES.skip b/po/POTFILES.skip -index a818c48..891d2cc 100644 ---- a/po/POTFILES.skip -+++ b/po/POTFILES.skip -@@ -21,3 +21,4 @@ ui/gtk3/property.c - ui/gtk3/propertypanel.c - ui/gtk3/separator.c - ui/gtk3/switcher.c -+ui/gtk3/emojierapp.c -diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am -index 7122ff3..96ed3e6 100644 ---- a/ui/gtk3/Makefile.am -+++ b/ui/gtk3/Makefile.am -@@ -108,6 +108,10 @@ endif - - libexec_PROGRAMS = ibus-ui-gtk3 - -+if ENABLE_EMOJI_DICT -+libexec_PROGRAMS += ibus-ui-emojier -+endif -+ - ibus_ui_gtk3_SOURCES = \ - application.vala \ - candidatearea.vala \ -@@ -132,6 +136,26 @@ ibus_ui_gtk3_LDADD = \ - $(AM_LDADD) \ - $(NULL) - -+if ENABLE_EMOJI_DICT -+AM_VALAFLAGS += \ -+ --define=EMOJI_DICT \ -+ --vapidir=$(top_builddir)/ui/gtk3 \ -+ --vapidir=$(top_srcdir)/ui/gtk3 \ -+ --pkg=ibus-emoji-dialog-1.0 \ -+ --pkg=gtk+-3.0 \ -+ $(NULL) -+ -+ -+ibus_ui_emojier_SOURCES = \ -+ emojierapp.vala \ -+ $(NULL) -+ -+ibus_ui_emojier_LDADD = \ -+ $(AM_LDADD) \ -+ $(libibus_emoji_dialog) \ -+ $(NULL) -+endif -+ - gen-%.xml.c: %.xml - echo "Building $@ from $<" - echo "const char * _$(subst -,_,$(subst .,_,$(basename $(notdir $<)))) = " > $@ --- -2.9.3 - -From 2f8982a0a4b25f98c969c09b81724e44efc41c27 Mon Sep 17 00:00:00 2001 -From: Peng Wu -Date: Mon, 10 Apr 2017 15:57:48 +0900 -Subject: [PATCH] Make ibus emojier dialog as an unique application - -Forgot to commit ui/gtk3/emojierapp.vala from the previous commit. - -BUG= -R=takao.fujiwara1@gmail.com - -Review URL: https://codereview.appspot.com/319650043 - -Patch from Peng Wu . ---- - ui/gtk3/emojierapp.vala | 149 ++++++++++++++++++++++++++++++++++++++++++++++++ - 1 file changed, 149 insertions(+) - create mode 100644 ui/gtk3/emojierapp.vala - -diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala -new file mode 100644 -index 0000000..4287bff ---- /dev/null -+++ b/ui/gtk3/emojierapp.vala -@@ -0,0 +1,149 @@ -+/* vim:set et sts=4 sw=4: -+ * -+ * ibus - The Input Bus -+ * -+ * Copyright (c) 2017 Peng Wu -+ * -+ * This library is free software; you can redistribute it and/or -+ * modify it under the terms of the GNU Lesser General Public -+ * License as published by the Free Software Foundation; either -+ * version 2.1 of the License, or (at your option) any later version. -+ * -+ * This library 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 -+ * Lesser General Public License for more details. -+ * -+ * You should have received a copy of the GNU Lesser General Public -+ * License along with this library; if not, write to the Free Software -+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -+ * USA -+ */ -+ -+string emoji_font = null; -+string annotation_lang = null; -+ -+public class EmojiApplication : Application { -+ private IBus.Emojier emojier = new IBus.Emojier(); -+ -+ private EmojiApplication() { -+ Object(application_id: "org.freedesktop.ibus.panel.emojier", -+ flags: ApplicationFlags.HANDLES_COMMAND_LINE); -+ set_inactivity_timeout(100000); -+ } -+ -+ private void show_dialog(ApplicationCommandLine command_line) { -+ Gdk.Event event = new Gdk.Event(Gdk.EventType.KEY_PRESS); -+ var display = Gdk.Display.get_default(); -+ var device_manager = display.get_device_manager(); -+ var device = device_manager.list_devices(Gdk.DeviceType.MASTER).data; -+ event.set_device(device); -+ string emoji = emojier.run(event, ""); -+ if (emoji == null) { -+ emojier.reset(); -+ command_line.print("%s\n", _("Canceled to choose an emoji.")); -+ return; -+ } -+ Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD); -+ clipboard.set_text(emoji, -1); -+ clipboard.store(); -+ emojier.reset(); -+ command_line.print("%s\n", _("Copied an emoji to your clipboard.")); -+ } -+ -+ public void activate_dialog(ApplicationCommandLine command_line) { -+ this.hold (); -+ -+ // show dialog -+ if (emojier.has_loaded_emoji_dict()) { -+ show_dialog(command_line); -+ } else { -+ emojier.loaded_emoji_dict.connect(() => { -+ // The signal is called when the language is changed. -+ if (emojier.is_running()) -+ return; -+ -+ show_dialog(command_line); -+ }); -+ } -+ -+ this.release (); -+ } -+ -+ private int _command_line (ApplicationCommandLine command_line) { -+ const OptionEntry[] options = { -+ { "font", 0, 0, OptionArg.STRING, out emoji_font, -+ N_("FONT for emoji chracters on emoji dialog."), -+ "FONT" }, -+ { "lang", 0, 0, OptionArg.STRING, out annotation_lang, -+ N_("LANG for annotations on emoji dialog. E.g. \"en\""), -+ "LANG" }, -+ { null } -+ }; -+ -+ var option = new OptionContext(); -+ option.add_main_entries(options, Config.GETTEXT_PACKAGE); -+ -+ // We have to make an extra copy of the array, -+ // since .parse assumes that it can remove strings -+ // from the array without freeing them. -+ string[] args = command_line.get_arguments(); -+ string*[] _args = new string[args.length]; -+ for (int i = 0; i < args.length; i++) { -+ _args[i] = args[i]; -+ } -+ -+ try { -+ unowned string[] tmp = _args; -+ option.parse(ref tmp); -+ } catch (OptionError e) { -+ stderr.printf("%s\n", e.message); -+ return Posix.EXIT_FAILURE; -+ } -+ -+ if (emoji_font == null) { -+ GLib.Settings settings_emoji = -+ new GLib.Settings("org.freedesktop.ibus.panel.emoji"); -+ emoji_font = settings_emoji.get_string("font"); -+ } -+ -+ if (annotation_lang == null) { -+ GLib.Settings settings_emoji = -+ new GLib.Settings("org.freedesktop.ibus.panel.emoji"); -+ annotation_lang = settings_emoji.get_string("lang"); -+ } -+ -+ if (emoji_font != null && emoji_font != "") -+ emojier.set_emoji_font(emoji_font); -+ if (annotation_lang != null && annotation_lang != "") -+ emojier.set_annotation_lang(annotation_lang); -+ -+ activate_dialog(command_line); -+ -+ return Posix.EXIT_SUCCESS; -+ } -+ -+ public override int command_line (ApplicationCommandLine command_line) { -+ // keep the application running until we are done with this commandline -+ this.hold(); -+ int result = _command_line(command_line); -+ this.release(); -+ return result; -+ } -+ -+ public static int main (string[] args) { -+ GLib.Intl.bindtextdomain(Config.GETTEXT_PACKAGE, -+ Config.GLIB_LOCALE_DIR); -+ GLib.Intl.bind_textdomain_codeset(Config.GETTEXT_PACKAGE, "UTF-8"); -+ GLib.Intl.textdomain(Config.GETTEXT_PACKAGE); -+ -+ IBus.init(); -+ -+ Gtk.init(ref args); -+ -+ EmojiApplication app = new EmojiApplication(); -+ int status = app.run(args); -+ return status; -+ } -+ -+} --- -2.9.3 - -From da33672bc738889dcd4ebbec51cb3aa67567f70c Mon Sep 17 00:00:00 2001 -From: Peng Wu -Date: Thu, 13 Apr 2017 12:40:22 +0900 -Subject: [PATCH] tools: spawn the process of ibus-ui-emojier - -BUG= -R=Shawn.P.Huang@gmail.com, takao.fujiwara1@gmail.com - -Review URL: https://codereview.appspot.com/324770043 - -Patch from Peng Wu . ---- - bindings/vala/config.vapi | 1 + - tools/Makefile.am | 1 + - tools/main.vala | 72 +++++++++-------------------------------------- - 3 files changed, 15 insertions(+), 59 deletions(-) - -diff --git a/bindings/vala/config.vapi b/bindings/vala/config.vapi -index f2195da..e3c43df 100644 ---- a/bindings/vala/config.vapi -+++ b/bindings/vala/config.vapi -@@ -7,6 +7,7 @@ namespace Config - public const string BINDIR; - public const string DATADIR; - public const string PKGDATADIR; -+ public const string LIBEXECDIR; - public const string GETTEXT_PACKAGE; - public const string GLIB_LOCALE_DIR; - } -diff --git a/tools/Makefile.am b/tools/Makefile.am -index bd655af..9d542bd 100644 ---- a/tools/Makefile.am -+++ b/tools/Makefile.am -@@ -39,6 +39,7 @@ AM_CFLAGS = \ - @GIO2_CFLAGS@ \ - @GTHREAD2_CFLAGS@ \ - -DG_LOG_DOMAIN=\"IBUS\" \ -+ -DLIBEXECDIR=\"$(libexecdir)\" \ - -DIBUS_DISABLE_DEPRECATED \ - -Wno-unused-variable \ - -Wno-unused-but-set-variable \ -diff --git a/tools/main.vala b/tools/main.vala -index 2bf1dc7..9aca4b0 100644 ---- a/tools/main.vala -+++ b/tools/main.vala -@@ -324,73 +324,27 @@ int reset_config(string[] argv) { - } - - #if EMOJI_DICT --private void run_dialog(IBus.Emojier emojier) { -- Gdk.Event event = new Gdk.Event(Gdk.EventType.KEY_PRESS); -- var display = Gdk.Display.get_default(); -- var device_manager = display.get_device_manager(); -- var device = device_manager.list_devices(Gdk.DeviceType.MASTER).data; -- event.set_device(device); -- string emoji = emojier.run(event, ""); -- if (emoji == null) { -- emojier.reset(); -- print("%s\n", _("Canceled to choose an emoji.")); -- return; -- } -- Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD); -- clipboard.set_text(emoji, -1); -- clipboard.store(); -- emojier.reset(); -- print("%s\n", _("Copied an emoji to your clipboard.")); --} -- - int emoji_dialog(string[] argv) { -- const OptionEntry[] options = { -- { "font", 0, 0, OptionArg.STRING, out emoji_font, -- N_("FONT for emoji chracters on emoji dialog."), "FONT" }, -- { "lang", 0, 0, OptionArg.STRING, out annotation_lang, -- N_("LANG for annotations on emoji dialog. E.g. \"en\""), "LANG" }, -- { null } -- }; -+ string cmd = Config.LIBEXECDIR + "/ibus-ui-emojier"; - -- var option = new OptionContext(); -- option.add_main_entries(options, Config.GETTEXT_PACKAGE); -+ var file = File.new_for_path(cmd); -+ if (!file.query_exists()) -+ cmd = "../ui/gtk3/ibus-ui-emojier"; -+ -+ argv[0] = cmd; -+ -+ string[] env = Environ.get(); - - try { -- option.parse(ref argv); -- } catch (OptionError e) { -+ // Non-blocking -+ Process.spawn_async(null, argv, env, -+ SpawnFlags.SEARCH_PATH, -+ null, null); -+ } catch (SpawnError e) { - stderr.printf("%s\n", e.message); - return Posix.EXIT_FAILURE; - } - -- Gtk.init(ref argv); -- if (emoji_font == null) { -- GLib.Settings settings_emoji = -- new GLib.Settings("org.freedesktop.ibus.panel.emoji"); -- emoji_font = settings_emoji.get_string("font"); -- } -- if (annotation_lang == null) { -- GLib.Settings settings_emoji = -- new GLib.Settings("org.freedesktop.ibus.panel.emoji"); -- annotation_lang = settings_emoji.get_string("lang"); -- } -- IBus.Emojier emojier = new IBus.Emojier(); -- if (emoji_font != null && emoji_font != "") -- emojier.set_emoji_font(emoji_font); -- if (annotation_lang != null && annotation_lang != "") -- emojier.set_annotation_lang(annotation_lang); -- if (emojier.has_loaded_emoji_dict()) { -- run_dialog(emojier); -- } else { -- GLib.MainLoop loop = new GLib.MainLoop(); -- emojier.loaded_emoji_dict.connect(() => { -- // The signal is called when the language is changed. -- if (emojier.is_running()) -- return; -- run_dialog(emojier); -- loop.quit(); -- }); -- loop.run(); -- } - return Posix.EXIT_SUCCESS; - } - #endif --- -2.9.3 - -From 290f786b82158e7c087b81946727606cd3424e94 Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Mon, 17 Apr 2017 16:00:26 +0900 -Subject: [PATCH] ui/gtk3: Fix build failures - -ibus-ui-emoijer needs to be built at last in ui/gtk3 -Also fixed po/POTFILES.in for `make dist` -Now I moved libibusemojidialog.so and IBusEmojiDialog-1.0.[gir|typelib] -and ibus-emoji-dialog-1.0.vapi to noinst because now -ibus-ui-emojier is available and they are private libraries. - -R=Shawn.P.Huang@gmail.com - -Review URL: https://codereview.appspot.com/312610043 ---- - po/POTFILES.in | 1 + - tools/Makefile.am | 19 ++----------- - ui/gtk3/Makefile.am | 72 ++++++++++++++++++++++--------------------------- - ui/gtk3/emojierapp.vala | 33 +++++++++++++---------- - 4 files changed, 54 insertions(+), 71 deletions(-) - -diff --git a/po/POTFILES.in b/po/POTFILES.in -index 65a3526..25be4f4 100644 ---- a/po/POTFILES.in -+++ b/po/POTFILES.in -@@ -31,6 +31,7 @@ ibus/object.py - ibus/panel.py - ibus/property.py - ibus/utility.py -+setup/emojilang.py - setup/engineabout.py - setup/enginecombobox.py - setup/enginedialog.py -diff --git a/tools/Makefile.am b/tools/Makefile.am -index 9d542bd..5c18d3d 100644 ---- a/tools/Makefile.am -+++ b/tools/Makefile.am -@@ -101,24 +101,9 @@ CLEANFILES = \ - - if ENABLE_EMOJI_DICT - if ENABLE_UI --AM_CPPFLAGS += \ -- -I$(top_srcdir)/ui/gtk3 \ -- -I$(top_builddir)/ui/gtk3 \ -- $(NULL) --AM_CFLAGS += \ -- @GTK3_CFLAGS@ \ -- $(NULL) --AM_LDADD += \ -- @GTK3_LIBS@ \ -- $(libibus_emoji_dialog) \ -- $(NULL) - AM_VALAFLAGS += \ -- --define=EMOJI_DICT \ -- --vapidir=$(top_builddir)/ui/gtk3 \ -- --vapidir=$(top_srcdir)/ui/gtk3 \ -- --pkg=ibus-emoji-dialog-1.0 \ -- --pkg=gtk+-3.0 \ -- $(NULL) -+ --define=EMOJI_DICT \ -+ $(NULL) - endif - endif - -diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am -index 96ed3e6..6f0fb62 100644 ---- a/ui/gtk3/Makefile.am -+++ b/ui/gtk3/Makefile.am -@@ -81,7 +81,9 @@ AM_VALAFLAGS = \ - --target-glib="$(VALA_TARGET_GLIB_VERSION)" \ - $(NULL) - -+MAINTAINERCLEANFILES = - CONFIG_CLEAN_FILES = -+noinst_DATA = - - if ENABLE_LIBNOTIFY - AM_CFLAGS += \ -@@ -108,10 +110,6 @@ endif - - libexec_PROGRAMS = ibus-ui-gtk3 - --if ENABLE_EMOJI_DICT --libexec_PROGRAMS += ibus-ui-emojier --endif -- - ibus_ui_gtk3_SOURCES = \ - application.vala \ - candidatearea.vala \ -@@ -136,26 +134,6 @@ ibus_ui_gtk3_LDADD = \ - $(AM_LDADD) \ - $(NULL) - --if ENABLE_EMOJI_DICT --AM_VALAFLAGS += \ -- --define=EMOJI_DICT \ -- --vapidir=$(top_builddir)/ui/gtk3 \ -- --vapidir=$(top_srcdir)/ui/gtk3 \ -- --pkg=ibus-emoji-dialog-1.0 \ -- --pkg=gtk+-3.0 \ -- $(NULL) -- -- --ibus_ui_emojier_SOURCES = \ -- emojierapp.vala \ -- $(NULL) -- --ibus_ui_emojier_LDADD = \ -- $(AM_LDADD) \ -- $(libibus_emoji_dialog) \ -- $(NULL) --endif -- - gen-%.xml.c: %.xml - echo "Building $@ from $<" - echo "const char * _$(subst -,_,$(subst .,_,$(basename $(notdir $<)))) = " > $@ -@@ -185,11 +163,17 @@ EXTRA_DIST = \ - $(NULL) - - if ENABLE_EMOJI_DICT --AM_VALAFLAGS += --define=EMOJI_DICT -+AM_VALAFLAGS += \ -+ --define=EMOJI_DICT \ -+ --vapidir=$(top_builddir)/ui/gtk3 \ -+ --vapidir=$(top_srcdir)/ui/gtk3 \ -+ --pkg=ibus-emoji-dialog-1.0 \ -+ --pkg=gtk+-3.0 \ -+ $(NULL) - - libibus_emoji_dialog = libibus-emoji-dialog-1.0.la - --lib_LTLIBRARIES = $(libibus_emoji_dialog) -+noinst_LTLIBRARIES = $(libibus_emoji_dialog) - - libibus_emoji_dialog_1_0_la_CFLAGS = $(AM_CFLAGS) - libibus_emoji_dialog_1_0_la_LDFLAGS = \ -@@ -198,11 +182,22 @@ libibus_emoji_dialog_1_0_la_LDFLAGS = \ - -version-info @LT_VERSION_INFO@ \ - $(NULL) - libibus_emoji_dialog_1_0_la_SOURCES = \ -- candidatearea.c \ -- emojier.c \ -- iconwidget.c \ -- pango.c \ -- separator.c \ -+ candidatearea.vala \ -+ emojier.vala \ -+ iconwidget.vala \ -+ pango.vala \ -+ separator.vala \ -+ $(NULL) -+ -+libexec_PROGRAMS += ibus-ui-emojier -+ -+ibus_ui_emojier_SOURCES = \ -+ $(libibus_emoji_dialog_1_0_la_SOURCES) \ -+ emojierapp.vala \ -+ $(NULL) -+ -+ibus_ui_emojier_LDADD = \ -+ $(AM_LDADD) \ - $(NULL) - - -include $(INTROSPECTION_MAKEFILE) -@@ -214,10 +209,6 @@ INTROSPECTION_COMPILER_ARGS = \ - $(NULL) - - if HAVE_INTROSPECTION --ibusincludedir = $(includedir)/ibus-@IBUS_API_VERSION@ --ibusinclude_HEADERS = \ -- $(emoji_headers) \ -- $(NULL) - introspection_sources = \ - $(emoji_headers) \ - $(NULL) -@@ -242,12 +233,13 @@ IBusEmojiDialog_1_0_gir_CFLAGS = \ - INTROSPECTION_GIRS = IBusEmojiDialog-1.0.gir - - girdir = $(datadir)/gir-1.0 --dist_gir_DATA = $(INTROSPECTION_GIRS) -+noinst_DATA += $(INTROSPECTION_GIRS) -+CLEANFILES += $(INTROSPECTION_GIRS) - - typelibsdir = $(libdir)/girepository-1.0 --typelibs_DATA = $(INTROSPECTION_GIRS:.gir=.typelib) -+noinst_DATA += $(INTROSPECTION_GIRS:.gir=.typelib) -+CLEANFILES += $(INTROSPECTION_GIRS:.gir=.typelib) - --CLEANFILES += $(dist_gir_DATA) $(typelibs_DATA) - - if ENABLE_VAPIGEN - -include $(VAPIGEN_MAKEFILE) -@@ -261,9 +253,9 @@ ibus_emoji_dialog_1_0_vapi_METADATADIRS = $(srcdir) - ibus_emoji_dialog_1_0_vapi_FILES = $(INTROSPECTION_GIRS) - - vapidir = $(datadir)/vala/vapi --vapi_DATA = $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps) -+noinst_DATA += $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps) - --MAINTAINERCLEANFILES = $(VAPIGEN_VAPIS) -+MAINTAINERCLEANFILES += $(VAPIGEN_VAPIS) - # for make distclean - CONFIG_CLEAN_FILES += $(VAPIGEN_VAPIS) - EXTRA_DIST += $(VAPIGEN_VAPIS) -diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala -index 4287bff..6349940 100644 ---- a/ui/gtk3/emojierapp.vala -+++ b/ui/gtk3/emojierapp.vala -@@ -24,52 +24,55 @@ string emoji_font = null; - string annotation_lang = null; - - public class EmojiApplication : Application { -- private IBus.Emojier emojier = new IBus.Emojier(); -+ private IBusEmojier m_emojier = new IBusEmojier(); - - private EmojiApplication() { - Object(application_id: "org.freedesktop.ibus.panel.emojier", -- flags: ApplicationFlags.HANDLES_COMMAND_LINE); -+ flags: ApplicationFlags.HANDLES_COMMAND_LINE); - set_inactivity_timeout(100000); - } - -+ - private void show_dialog(ApplicationCommandLine command_line) { - Gdk.Event event = new Gdk.Event(Gdk.EventType.KEY_PRESS); - var display = Gdk.Display.get_default(); - var device_manager = display.get_device_manager(); - var device = device_manager.list_devices(Gdk.DeviceType.MASTER).data; - event.set_device(device); -- string emoji = emojier.run(event, ""); -+ string emoji = m_emojier.run(event, ""); - if (emoji == null) { -- emojier.reset(); -+ m_emojier.reset(); - command_line.print("%s\n", _("Canceled to choose an emoji.")); - return; - } - Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD); - clipboard.set_text(emoji, -1); - clipboard.store(); -- emojier.reset(); -+ m_emojier.reset(); - command_line.print("%s\n", _("Copied an emoji to your clipboard.")); - } - -+ - public void activate_dialog(ApplicationCommandLine command_line) { - this.hold (); - - // show dialog -- if (emojier.has_loaded_emoji_dict()) { -+ if (m_emojier.has_loaded_emoji_dict()) { - show_dialog(command_line); - } else { -- emojier.loaded_emoji_dict.connect(() => { -- // The signal is called when the language is changed. -- if (emojier.is_running()) -+ m_emojier.loaded_emoji_dict.connect(() => { -+ // The signal is called when the language is changed. -+ if (m_emojier.is_running()) - return; - -- show_dialog(command_line); -- }); -+ show_dialog(command_line); -+ }); - } - - this.release (); - } - -+ - private int _command_line (ApplicationCommandLine command_line) { - const OptionEntry[] options = { - { "font", 0, 0, OptionArg.STRING, out emoji_font, -@@ -114,15 +117,16 @@ public class EmojiApplication : Application { - } - - if (emoji_font != null && emoji_font != "") -- emojier.set_emoji_font(emoji_font); -+ m_emojier.set_emoji_font(emoji_font); - if (annotation_lang != null && annotation_lang != "") -- emojier.set_annotation_lang(annotation_lang); -+ m_emojier.set_annotation_lang(annotation_lang); - - activate_dialog(command_line); - - return Posix.EXIT_SUCCESS; - } - -+ - public override int command_line (ApplicationCommandLine command_line) { - // keep the application running until we are done with this commandline - this.hold(); -@@ -131,9 +135,10 @@ public class EmojiApplication : Application { - return result; - } - -+ - public static int main (string[] args) { - GLib.Intl.bindtextdomain(Config.GETTEXT_PACKAGE, -- Config.GLIB_LOCALE_DIR); -+ Config.GLIB_LOCALE_DIR); - GLib.Intl.bind_textdomain_codeset(Config.GETTEXT_PACKAGE, "UTF-8"); - GLib.Intl.textdomain(Config.GETTEXT_PACKAGE); - --- -2.9.3 - -From 4b8d7baf627fb3fc9e4dde74ba33a867091f95b7 Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Tue, 18 Apr 2017 11:44:52 +0900 -Subject: [PATCH] ui/gtk3: Mark LANG and FONT as translatable strings - -BUG=https://github.com/ibus/ibus/issues/1920 - -Review URL: https://codereview.appspot.com/322880043 ---- - ui/gtk3/emojierapp.vala | 14 ++++++++++---- - 1 file changed, 10 insertions(+), 4 deletions(-) - -diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala -index 6349940..eac8d6a 100644 ---- a/ui/gtk3/emojierapp.vala -+++ b/ui/gtk3/emojierapp.vala -@@ -76,11 +76,17 @@ public class EmojiApplication : Application { - private int _command_line (ApplicationCommandLine command_line) { - const OptionEntry[] options = { - { "font", 0, 0, OptionArg.STRING, out emoji_font, -- N_("FONT for emoji chracters on emoji dialog."), -- "FONT" }, -+ /* TRANSLATORS: "FONT" should be capital and translatable. -+ * It's used for an argument command --font=FONT -+ */ -+ N_("\"FONT\" for emoji chracters on emoji dialog"), -+ N_("FONT") }, - { "lang", 0, 0, OptionArg.STRING, out annotation_lang, -- N_("LANG for annotations on emoji dialog. E.g. \"en\""), -- "LANG" }, -+ /* TRANSLATORS: "LANG" should be capital and translatable. -+ * It's used for an argument command --lang=LANG -+ */ -+ N_("\"LANG\" for annotations on emoji dialog. E.g. \"en\""), -+ N_("LANG") }, - { null } - }; - --- -2.9.3 - -From c6cdf21c7364cbb1e848e44cab0bff270e432e82 Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Wed, 19 Apr 2017 12:16:37 +0900 -Subject: [PATCH] ui/gtk3: Change modal dialog to focused dialog - -There are several problems with the current emoji modal dialog. -If keyboard is grabbed on the popup window, the focus out/in events -cannot be detected so the dialog cannot be closed by the focus changes. - -If mouse operation is supported on the popup window, need a custom -GtkHeaderBar with extended handle.vala but the behavior is unclear. [1] - -Also current popup window has several extensions for the keyboard grab. - -If the closed button is needed on the popup window since the focus events -cannot be detected, I think there is no merit to use the popup window. -Now IBusEmojier simply uses the focused window. - -[1] https://mail.gnome.org/archives/gtk-app-devel-list/2017-April/msg00017.html - -R=Shawn.P.Huang@gmail.com, alexepico@gmail.com - -Review URL: https://codereview.appspot.com/316510043 ---- - ui/gtk3/emojier.vala | 243 +++++++++++++++++++--------------------------- - ui/gtk3/emojierapp.vala | 55 ++++------- - ui/gtk3/ibusemojidialog.h | 54 +++++------ - ui/gtk3/panel.vala | 73 ++++++++++---- - 4 files changed, 194 insertions(+), 231 deletions(-) - -diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala -index 7b6107f..5a9bf8f 100644 ---- a/ui/gtk3/emojier.vala -+++ b/ui/gtk3/emojier.vala -@@ -137,47 +137,27 @@ class IBusEmojier : Gtk.Window { - pack_start(label, true, true, 0); - } - } -- private class ETitleLabelBox : Gtk.Box { -- EPaddedLabel m_lang_label; -- private Gtk.Button m_close_button; -- private ulong m_close_handler; -+ private class ETitleLabelBox : Gtk.HeaderBar { -+ private Gtk.Label m_lang_label; - -- public ETitleLabelBox(string text, -- Gtk.Align align) { -+ public ETitleLabelBox(string title) { - GLib.Object( - name : "IBusEmojierTitleLabelBox", -- orientation : Gtk.Orientation.HORIZONTAL, -- spacing : 0 -+ show_close_button: true, -+ decoration_layout: ":close", -+ title: title - ); -- EPaddedLabel label = new EPaddedLabel(text, align); -- pack_start(label, true, true, 0); -- Gtk.Separator separator = -- new Gtk.Separator (Gtk.Orientation.VERTICAL); -- pack_start(separator, false, true, 0); -- m_lang_label = new EPaddedLabel("", align); -- pack_start(m_lang_label, false, true, 0); -- IconWidget icon = new IconWidget("window-close", Gtk.IconSize.MENU); -- m_close_button = new Gtk.Button(); -- m_close_button.add(icon); -- pack_end(m_close_button, false, true, 0); -- } -- public void set_loop(GLib.MainLoop? loop) { -- if (m_close_handler > 0) -- GLib.SignalHandler.disconnect(m_close_button, m_close_handler); -- m_close_handler = m_close_button.button_press_event.connect((e) => { -- if (loop != null && loop.is_running()) -- loop.quit(); -- return true; -- }); -- } -- public void unset_loop() { -- if (m_close_handler > 0) { -- GLib.SignalHandler.disconnect(m_close_button, m_close_handler); -- m_close_handler = 0; -- } -+ var vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); -+ set_custom_title(vbox); -+ var label = new Gtk.Label(title); -+ label.get_style_context().add_class("title"); -+ vbox.pack_start(label, true, false, 0); -+ m_lang_label = new Gtk.Label(null); -+ m_lang_label.get_style_context().add_class("subtitle"); -+ vbox.pack_start(m_lang_label, true, false, 0); - } - public void set_lang_label(string str) { -- m_lang_label.set_label(str); -+ m_lang_label.set_text(str); - } - } - -@@ -186,7 +166,22 @@ class IBusEmojier : Gtk.Window { - BACKWARD, - } - -- private const uint EMOJI_GRID_PAGE = 10; -+ private static const uint EMOJI_GRID_PAGE = 10; -+ -+ // Set the actual default values in the constructor -+ // because these fields are used for class_init() and static functions, -+ // e.g. set_emoji_font(), can be called before class_init() is called. -+ private static string m_current_lang_id; -+ private static string m_emoji_font; -+ private static string[] m_favorites; -+ private static int m_emoji_max_seq_len; -+ private static GLib.HashTable? -+ m_annotation_to_emojis_dict; -+ private static GLib.HashTable? -+ m_emoji_to_data_dict; -+ private static GLib.HashTable? -+ m_category_to_emojis_dict; -+ - private ThemedRGBA m_rgba; - private Gtk.Box m_vbox; - private ETitleLabelBox m_title; -@@ -198,43 +193,38 @@ class IBusEmojier : Gtk.Window { - private string m_input_context_path = ""; - private GLib.MainLoop? m_loop; - private string? m_result; -- private string m_current_lang_id = "en"; - private string? m_unicode_point = null; - private bool m_candidate_panel_is_visible; -- private GLib.HashTable? -- m_annotation_to_emojis_dict = null; -- private GLib.HashTable? -- m_emoji_to_data_dict = null; -- private GLib.HashTable? -- m_category_to_emojis_dict = null; - int m_category_active_index; -- private int m_emoji_max_seq_len = 0; - private IBus.LookupTable m_lookup_table; - private Gtk.Label[] m_candidates; -- private string m_emoji_font = "Monospace 16"; -- private string[] m_favorites = {}; - private bool m_enter_notify_enable = true; - private uint m_entry_notify_show_id; - private uint m_entry_notify_disable_id; -- private uint m_reload_emoji_dict_id; - - public signal void candidate_clicked(uint index, uint button, uint state); -- public signal void loaded_emoji_dict(); - - public IBusEmojier() { - GLib.Object( -- type : Gtk.WindowType.POPUP, -+ type : Gtk.WindowType.TOPLEVEL, - events : Gdk.EventMask.KEY_PRESS_MASK | - Gdk.EventMask.KEY_RELEASE_MASK | -- Gdk.EventMask.BUTTON_PRESS_MASK, -+ Gdk.EventMask.BUTTON_PRESS_MASK | -+ Gdk.EventMask.BUTTON_RELEASE_MASK, - window_position : Gtk.WindowPosition.CENTER, -+ icon_name: "ibus-setup", - accept_focus : true, -- decorated : false, -- modal : true, - resizable : true, - focus_visible : true - ); - -+ if (m_current_lang_id == null) -+ m_current_lang_id = "en"; -+ if (m_emoji_font == null) -+ m_emoji_font = "Monospace 16"; -+ if (m_favorites == null) -+ m_favorites = {}; -+ - Gdk.Display display = Gdk.Display.get_default(); - Gdk.Screen screen = (display != null) ? - display.get_default_screen() : null; -@@ -283,12 +273,11 @@ class IBusEmojier : Gtk.Window { - css_provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); - -+ m_title = new ETitleLabelBox(_("Emoji Chooser")); -+ set_titlebar(m_title); - m_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); - add(m_vbox); - -- m_title = new ETitleLabelBox(_("Emoji Dialog"), -- Gtk.Align.CENTER); -- m_vbox.add(m_title); - m_entry = new EEntry(); - m_entry.set_placeholder_text(_("Type annotation or choose emoji")); - m_vbox.add(m_entry); -@@ -306,8 +295,6 @@ class IBusEmojier : Gtk.Window { - Atk.Object obj = m_entry.get_accessible(); - obj.set_role (Atk.Role.STATUSBAR); - -- grab_focus(); -- - // The constructor of IBus.LookupTable does not support more than - // 16 pages. - m_lookup_table = new IBus.LookupTable(1, 0, true, true); -@@ -325,15 +312,13 @@ class IBusEmojier : Gtk.Window { - hide_candidate_panel(); - }); - -- m_reload_emoji_dict_id = GLib.Idle.add(() => { -+ if (m_annotation_to_emojis_dict == null) { - reload_emoji_dict(); -- m_reload_emoji_dict_id = 0; -- return false; -- }); -+ } - } - - -- private void reload_emoji_dict() { -+ private static void reload_emoji_dict() { - init_emoji_dict(); - make_emoji_dict("en"); - if (m_current_lang_id != "en") { -@@ -344,11 +329,10 @@ class IBusEmojier : Gtk.Window { - } - make_emoji_dict(m_current_lang_id); - } -- loaded_emoji_dict(); - } - - -- private void init_emoji_dict() { -+ private static void init_emoji_dict() { - m_annotation_to_emojis_dict = - new GLib.HashTable(GLib.str_hash, - GLib.str_equal); -@@ -361,7 +345,7 @@ class IBusEmojier : Gtk.Window { - } - - -- private void make_emoji_dict(string lang) { -+ private static void make_emoji_dict(string lang) { - GLib.SList emoji_list = IBus.EmojiData.load( - Config.PKGDATADIR + "/dicts/emoji-" + lang + ".dict"); - if (emoji_list == null) -@@ -380,7 +364,7 @@ class IBusEmojier : Gtk.Window { - } - - -- private void update_annotation_to_emojis_dict(IBus.EmojiData data) { -+ private static void update_annotation_to_emojis_dict(IBus.EmojiData data) { - string emoji = (data.get_emoji_alternates() != "") ? - data.get_emoji_alternates() : data.get_emoji(); - unowned GLib.SList annotations = data.get_annotations(); -@@ -402,7 +386,7 @@ class IBusEmojier : Gtk.Window { - } - - -- private string utf8_down(string str) { -+ private static string utf8_down(string str) { - GLib.StringBuilder buff = new GLib.StringBuilder(); - int length = str.char_count(); - for (int i = 0; i < length; i++) { -@@ -413,7 +397,7 @@ class IBusEmojier : Gtk.Window { - } - - -- private string utf8_title(string str) { -+ private static string utf8_title(string str) { - StringBuilder buff = new StringBuilder(); - int length = str.char_count(); - for (int i = 0; i < length; i++) { -@@ -428,7 +412,7 @@ class IBusEmojier : Gtk.Window { - } - - -- private string utf8_code_point(string str) { -+ private static string utf8_code_point(string str) { - StringBuilder buff = new StringBuilder(); - int length = str.char_count(); - for (int i = 0; i < length; i++) { -@@ -443,8 +427,8 @@ class IBusEmojier : Gtk.Window { - } - - -- private void update_emoji_to_data_dict(IBus.EmojiData data, -- string lang) { -+ private static void update_emoji_to_data_dict(IBus.EmojiData data, -+ string lang) { - string emoji = (data.get_emoji_alternates() != "") ? - data.get_emoji_alternates() : data.get_emoji(); - if (lang == "en") { -@@ -496,8 +480,8 @@ class IBusEmojier : Gtk.Window { - } - - -- private void update_category_to_emojis_dict(IBus.EmojiData data, -- string lang) { -+ private static void update_category_to_emojis_dict(IBus.EmojiData data, -+ string lang) { - string emoji = (data.get_emoji_alternates() != "") ? - data.get_emoji_alternates() : data.get_emoji(); - string category = data.get_category(); -@@ -937,7 +921,8 @@ class IBusEmojier : Gtk.Window { - uint modifiers) { - assert (keyval == Gdk.Key.Left || keyval == Gdk.Key.Right); - -- if (m_candidate_panel_is_visible) { -+ uint ncandidates = m_lookup_table.get_number_of_candidates(); -+ if (m_candidate_panel_is_visible && ncandidates > 1) { - enter_notify_disable_with_timer(); - if (keyval == Gdk.Key.Left) - m_lookup_table.cursor_up(); -@@ -971,7 +956,8 @@ class IBusEmojier : Gtk.Window { - private bool key_press_cursor_vertical(uint keyval) { - assert (keyval == Gdk.Key.Down || keyval == Gdk.Key.Up); - -- if (m_candidate_panel_is_visible) { -+ uint ncandidates = m_lookup_table.get_number_of_candidates(); -+ if (m_candidate_panel_is_visible && ncandidates > 1) { - if (keyval == Gdk.Key.Down) - candidate_panel_cursor_down(); - else if (keyval == Gdk.Key.Up) -@@ -987,12 +973,12 @@ class IBusEmojier : Gtk.Window { - uint modifiers) { - assert (keyval == Gdk.Key.Home || keyval == Gdk.Key.End); - -- if (m_candidate_panel_is_visible) { -+ uint ncandidates = m_lookup_table.get_number_of_candidates(); -+ if (m_candidate_panel_is_visible && ncandidates > 1) { - enter_notify_disable_with_timer(); - if (keyval == Gdk.Key.Home) { - m_lookup_table.set_cursor_pos(0); - } else if (keyval == Gdk.Key.End) { -- uint ncandidates = m_lookup_table.get_number_of_candidates(); - m_lookup_table.set_cursor_pos(ncandidates - 1); - } - show_candidate_panel(); -@@ -1052,8 +1038,7 @@ class IBusEmojier : Gtk.Window { - } - - -- public string run(Gdk.Event event, -- string input_context_path) { -+ public string run(string input_context_path) { - assert (m_loop == null); - - m_is_running = true; -@@ -1066,65 +1051,22 @@ class IBusEmojier : Gtk.Window { - resize(1, 1); - - m_entry.set_text(""); -- m_entry.grab_focus(); -- -- Gdk.Device device = event.get_device(); -- if (device == null) { -- var display = get_display(); -- var device_manager = display.get_device_manager(); -- device = device_manager.list_devices(Gdk.DeviceType.MASTER).data; -- } -- -- Gdk.Device keyboard; -- Gdk.Device pointer; -- if (device.get_source() == Gdk.InputSource.KEYBOARD) { -- keyboard = device; -- pointer = device.get_associated_device(); -- } else { -- pointer = device; -- keyboard = device.get_associated_device(); -- } -+ //m_entry.grab_focus(); - - show_category_list(); - m_entry.set_activates_default(true); - show_all(); - -- Gdk.GrabStatus status; -- // Grab all keyboard events -- status = keyboard.grab(get_window(), -- Gdk.GrabOwnership.NONE, -- true, -- Gdk.EventMask.KEY_PRESS_MASK | -- Gdk.EventMask.KEY_RELEASE_MASK, -- null, -- Gdk.CURRENT_TIME); -- if (status != Gdk.GrabStatus.SUCCESS) -- warning("Grab keyboard failed! status = %d", status); -- // Grab all pointer events -- status = pointer.grab(get_window(), -- Gdk.GrabOwnership.NONE, -- true, -- Gdk.EventMask.BUTTON_PRESS_MASK | -- Gdk.EventMask.BUTTON_RELEASE_MASK, -- null, -- Gdk.CURRENT_TIME); -- if (status != Gdk.GrabStatus.SUCCESS) -- warning("Grab pointer failed! status = %d", status); -- - m_loop = new GLib.MainLoop(); -- m_title.set_loop(m_loop); - m_loop.run(); -- m_title.unset_loop(); - m_loop = null; - -- keyboard.ungrab(Gdk.CURRENT_TIME); -- pointer.ungrab(Gdk.CURRENT_TIME); -- - // Need focus-out on Gtk.Entry to send the emoji to applications. - Gdk.Event fevent = new Gdk.Event(Gdk.EventType.FOCUS_CHANGE); - fevent.focus_change.in = 0; - fevent.focus_change.window = get_window(); - m_entry.send_focus_change(fevent); -+ fevent.focus_change.window = null; - - hide(); - // Make sure the switcher is hidden before returning from this function. -@@ -1288,20 +1230,23 @@ class IBusEmojier : Gtk.Window { - } - - -- public void set_emoji_font(string emoji_font) { -- m_emoji_font = emoji_font; -- } -- -- -- public void set_favorites(string[]? unowned_favorites) { -- m_favorites = {}; -- foreach (string favorite in unowned_favorites) { -- m_favorites += favorite; -- } -+ public void present_centralize() { -+ present(); -+ m_entry.set_activates_default(true); -+ Gtk.Allocation allocation; -+ get_allocation(out allocation); -+ Gdk.Screen screen = Gdk.Screen.get_default(); -+ int monitor_num = screen.get_monitor_at_window(get_window()); -+ Gdk.Rectangle monitor_area; -+ screen.get_monitor_geometry(monitor_num, out monitor_area); -+ int x = (monitor_area.x + monitor_area.width - allocation.width)/2; -+ int y = (monitor_area.y + monitor_area.height -+ - allocation.height)/2; -+ move(x, y); - } - - -- public bool has_loaded_emoji_dict() { -+ public static bool has_loaded_emoji_dict() { - if (m_emoji_to_data_dict == null) - return false; - GLib.List keys = m_emoji_to_data_dict.get_keys(); -@@ -1311,18 +1256,26 @@ class IBusEmojier : Gtk.Window { - } - - -- public void set_annotation_lang(string lang) { -+ public static void set_annotation_lang(string? lang) { -+ if (lang == null || lang == "") -+ lang = "en"; - if (m_current_lang_id == lang) - return; -- if (m_reload_emoji_dict_id > 0) { -- GLib.Source.remove(m_reload_emoji_dict_id); -- m_reload_emoji_dict_id = 0; -- } - m_current_lang_id = lang; -- m_reload_emoji_dict_id = GLib.Idle.add(() => { -- reload_emoji_dict(); -- m_reload_emoji_dict_id = 0; -- return false; -- }); -+ reload_emoji_dict(); -+ } -+ -+ -+ public static void set_emoji_font(string? emoji_font) { -+ return_if_fail(emoji_font != null && emoji_font != ""); -+ m_emoji_font = emoji_font; -+ } -+ -+ -+ public static void set_favorites(string[]? unowned_favorites) { -+ m_favorites = {}; -+ foreach (string favorite in unowned_favorites) { -+ m_favorites += favorite; -+ } - } - } -diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala -index eac8d6a..4564a25 100644 ---- a/ui/gtk3/emojierapp.vala -+++ b/ui/gtk3/emojierapp.vala -@@ -24,7 +24,10 @@ string emoji_font = null; - string annotation_lang = null; - - public class EmojiApplication : Application { -- private IBusEmojier m_emojier = new IBusEmojier(); -+ private IBusEmojier? m_emojier; -+ GLib.Settings m_settings_emoji = -+ new GLib.Settings("org.freedesktop.ibus.panel.emoji"); -+ - - private EmojiApplication() { - Object(application_id: "org.freedesktop.ibus.panel.emojier", -@@ -34,41 +37,24 @@ public class EmojiApplication : Application { - - - private void show_dialog(ApplicationCommandLine command_line) { -- Gdk.Event event = new Gdk.Event(Gdk.EventType.KEY_PRESS); -- var display = Gdk.Display.get_default(); -- var device_manager = display.get_device_manager(); -- var device = device_manager.list_devices(Gdk.DeviceType.MASTER).data; -- event.set_device(device); -- string emoji = m_emojier.run(event, ""); -+ m_emojier = new IBusEmojier(); -+ string emoji = m_emojier.run(""); - if (emoji == null) { -- m_emojier.reset(); -+ m_emojier = null; - command_line.print("%s\n", _("Canceled to choose an emoji.")); - return; - } - Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD); - clipboard.set_text(emoji, -1); - clipboard.store(); -- m_emojier.reset(); -+ m_emojier = null; - command_line.print("%s\n", _("Copied an emoji to your clipboard.")); - } - - - public void activate_dialog(ApplicationCommandLine command_line) { - this.hold (); -- -- // show dialog -- if (m_emojier.has_loaded_emoji_dict()) { -- show_dialog(command_line); -- } else { -- m_emojier.loaded_emoji_dict.connect(() => { -- // The signal is called when the language is changed. -- if (m_emojier.is_running()) -- return; -- -- show_dialog(command_line); -- }); -- } -- -+ show_dialog(command_line); - this.release (); - } - -@@ -110,22 +96,18 @@ public class EmojiApplication : Application { - return Posix.EXIT_FAILURE; - } - -- if (emoji_font == null) { -- GLib.Settings settings_emoji = -- new GLib.Settings("org.freedesktop.ibus.panel.emoji"); -- emoji_font = settings_emoji.get_string("font"); -+ if (m_emojier != null && m_emojier.is_running()) { -+ m_emojier.present_centralize(); -+ return Posix.EXIT_SUCCESS; - } - -- if (annotation_lang == null) { -- GLib.Settings settings_emoji = -- new GLib.Settings("org.freedesktop.ibus.panel.emoji"); -- annotation_lang = settings_emoji.get_string("lang"); -- } -+ if (emoji_font == null) -+ emoji_font = m_settings_emoji.get_string("font"); -+ if (annotation_lang == null) -+ annotation_lang = m_settings_emoji.get_string("lang"); - -- if (emoji_font != null && emoji_font != "") -- m_emojier.set_emoji_font(emoji_font); -- if (annotation_lang != null && annotation_lang != "") -- m_emojier.set_annotation_lang(annotation_lang); -+ IBusEmojier.set_annotation_lang(annotation_lang); -+ IBusEmojier.set_emoji_font(emoji_font); - - activate_dialog(command_line); - -@@ -156,5 +138,4 @@ public class EmojiApplication : Application { - int status = app.run(args); - return status; - } -- - } -diff --git a/ui/gtk3/ibusemojidialog.h b/ui/gtk3/ibusemojidialog.h -index 0f84a48..1499a3c 100644 ---- a/ui/gtk3/ibusemojidialog.h -+++ b/ui/gtk3/ibusemojidialog.h -@@ -69,7 +69,6 @@ IBusEmojier * ibus_emojier_new (void); - /** - * ibus_emojier_run: - * @self: An #IBusEmojier -- * @event: An #GdkEvent - * @input_context_path: An input context path of #IBusInputContext - * of the focused application. - * -@@ -78,7 +77,6 @@ IBusEmojier * ibus_emojier_new (void); - * Returns: A selected emoji character. - */ - gchar * ibus_emojier_run (IBusEmojier* self, -- GdkEvent* event, - const gchar* - input_context_path); - -@@ -116,49 +114,49 @@ gchar * ibus_emojier_get_selected_string (IBusEmojier* self); - void ibus_emojier_reset (IBusEmojier* self); - - /** -- * ibus_emojier_set_emoji_font: -+ * ibus_emojier_present_centralize: - * @self: An #IBusEmojier -- * @emoji_font: font name for emoji characters - * -- * Set emoji font on the emoji dialog -+ * Move the window to the toplevel on the screen and centralize it. - */ --void ibus_emojier_set_emoji_font (IBusEmojier* self, -- const gchar* emoji_font); -- --#if 0 --/* TODO: set customized annotations */ --/** -- * ibus_emojier_set_favorites: -- * @self: An #IBusEmojier -- * @favorites: (array length=favorites_length): A custom emoji list. -- * @favorites_length: A length of @favorites -- * -- * Set emoji font on the emoji dialog -- */ --void ibus_emojier_set_favorites (IBusEmojier* self, -- gchar** favorites, -- int -- favorites_length); --#endif -+void ibus_emojier_present_centralize (IBusEmojier* self); - - /** - * ibus_emojier_has_loaded_emoji_dict: -- * @self: An #IBusEmojier - * - * Returns: %TRUE if the emoji dict is loaded, otherwise %FALSE. - */ --gboolean ibus_emojier_has_loaded_emoji_dict (IBusEmojier* self); -+gboolean ibus_emojier_has_loaded_emoji_dict (void); - - /** - * ibus_emojier_set_annotation_lang: -- * @self: An #IBusEmojier - * @lang: A langauge id for emoji annotations. - * - * Set a language id for emoji annotations. #IBusEmojier will load - * $PKGDATADIR/dicts/emoji-@lang.dict. The default is "en". - */ --void ibus_emojier_set_annotation_lang (IBusEmojier* self, -- const gchar* lang); -+void ibus_emojier_set_annotation_lang (const gchar* lang); - -+/** -+ * ibus_emojier_set_emoji_font: -+ * @emoji_font: font name for emoji characters -+ * -+ * Set emoji font on the emoji dialog -+ */ -+void ibus_emojier_set_emoji_font (const gchar* emoji_font); -+ -+#if 0 -+/* TODO: set customized annotations */ -+/** -+ * ibus_emojier_set_favorites: -+ * @favorites: (array length=favorites_length): A custom emoji list. -+ * @favorites_length: A length of @favorites -+ * -+ * Set emoji font on the emoji dialog -+ */ -+void ibus_emojier_set_favorites (gchar** favorites, -+ int -+ favorites_length); -+#endif - G_END_DECLS - #endif -diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala -index 7350dcc..2375734 100644 ---- a/ui/gtk3/panel.vala -+++ b/ui/gtk3/panel.vala -@@ -73,7 +73,8 @@ class Panel : IBus.PanelService { - private CandidatePanel m_candidate_panel; - private Switcher m_switcher; - private uint m_switcher_focus_set_engine_id; -- private IBusEmojier m_emojier; -+ private IBusEmojier? m_emojier; -+ private uint m_emojier_set_emoji_lang_id; - private uint m_emojier_focus_commit_text_id; - private PropertyManager m_property_manager; - private PropertyPanel m_property_panel; -@@ -137,7 +138,6 @@ class Panel : IBus.PanelService { - m_switcher.set_popup_delay_time((uint) m_switcher_delay_time); - } - -- m_emojier = new IBusEmojier(); - bind_emoji_shortcut(); - - m_property_manager = new PropertyManager(); -@@ -595,7 +595,7 @@ class Panel : IBus.PanelService { - warning("No config emoji:font."); - return; - } -- m_emojier.set_emoji_font(emoji_font); -+ IBusEmojier.set_emoji_font(emoji_font); - - bool use_custom_font = m_settings_panel.get_boolean("use-custom-font"); - -@@ -766,11 +766,20 @@ class Panel : IBus.PanelService { - } - - private void set_emoji_favorites() { -- m_emojier.set_favorites(m_settings_emoji.get_strv("favorites")); -+ IBusEmojier.set_favorites(m_settings_emoji.get_strv("favorites")); - } - - private void set_emoji_lang() { -- m_emojier.set_annotation_lang(m_settings_emoji.get_string("lang")); -+ if (m_emojier_set_emoji_lang_id > 0) { -+ GLib.Source.remove(m_emojier_set_emoji_lang_id); -+ m_emojier_set_emoji_lang_id = 0; -+ } -+ m_emojier_set_emoji_lang_id = GLib.Idle.add(() => { -+ IBusEmojier.set_annotation_lang( -+ m_settings_emoji.get_string("lang")); -+ m_emojier_set_emoji_lang_id = 0; -+ return false; -+ }); - } - - private int compare_versions(string version1, string version2) { -@@ -984,15 +993,24 @@ class Panel : IBus.PanelService { - } - } - -- private void handle_emoji_typing(Gdk.Event event) { -- if (m_emojier.is_running()) -- return; -- string emoji = m_emojier.run(event, m_real_current_context_path); -- if (emoji == null) -+ private void show_emojier() { -+ m_emojier = new IBusEmojier(); -+ string emoji = m_emojier.run(m_real_current_context_path); -+ if (emoji == null) { -+ m_emojier = null; - return; -+ } - this.emojier_focus_commit(); - } - -+ private void handle_emoji_typing(Gdk.Event event) { -+ if (m_emojier != null && m_emojier.is_running()) { -+ m_emojier.present_centralize(); -+ return; -+ } -+ show_emojier(); -+ } -+ - private void run_preload_engines(IBus.EngineDesc[] engines, int index) { - string[] names = {}; - -@@ -1361,29 +1379,34 @@ class Panel : IBus.PanelService { - if (selected_engine == null && - prev_context_path != "" && - m_switcher.is_running()) { -- if (m_switcher_focus_set_engine_id > 0) { -+ var context = GLib.MainContext.default(); -+ if (m_switcher_focus_set_engine_id > 0 && -+ context.find_source_by_id(m_switcher_focus_set_engine_id) -+ != null) { - GLib.Source.remove(m_switcher_focus_set_engine_id); - } - m_switcher_focus_set_engine_id = GLib.Timeout.add(100, () => { - // focus_in is comming before switcher returns - switcher_focus_set_engine_real(); -- if (m_switcher_focus_set_engine_id > 0) { -- GLib.Source.remove(m_switcher_focus_set_engine_id); -- m_switcher_focus_set_engine_id = -1; -- } -+ m_switcher_focus_set_engine_id = -1; - return false; - }); - } else { - if (switcher_focus_set_engine_real()) { -- if (m_switcher_focus_set_engine_id > 0) { -+ var context = GLib.MainContext.default(); -+ if (m_switcher_focus_set_engine_id > 0 && -+ context.find_source_by_id(m_switcher_focus_set_engine_id) -+ != null) { - GLib.Source.remove(m_switcher_focus_set_engine_id); -- m_switcher_focus_set_engine_id = -1; - } -+ m_switcher_focus_set_engine_id = -1; - } - } - } - - private bool emojier_focus_commit_real() { -+ if (m_emojier == null) -+ return true; - string selected_string = m_emojier.get_selected_string(); - string prev_context_path = m_emojier.get_input_context_path(); - if (selected_string != null && -@@ -1391,7 +1414,7 @@ class Panel : IBus.PanelService { - prev_context_path == m_current_context_path) { - IBus.Text text = new IBus.Text.from_string(selected_string); - commit_text(text); -- m_emojier.reset(); -+ m_emojier = null; - return true; - } - -@@ -1399,12 +1422,17 @@ class Panel : IBus.PanelService { - } - - private void emojier_focus_commit() { -+ if (m_emojier == null) -+ return; - string selected_string = m_emojier.get_selected_string(); - string prev_context_path = m_emojier.get_input_context_path(); - if (selected_string == null && - prev_context_path != "" && - m_emojier.is_running()) { -- if (m_emojier_focus_commit_text_id > 0) { -+ var context = GLib.MainContext.default(); -+ if (m_emojier_focus_commit_text_id > 0 && -+ context.find_source_by_id(m_emojier_focus_commit_text_id) -+ != null) { - GLib.Source.remove(m_emojier_focus_commit_text_id); - } - m_emojier_focus_commit_text_id = GLib.Timeout.add(100, () => { -@@ -1415,10 +1443,13 @@ class Panel : IBus.PanelService { - }); - } else { - if (emojier_focus_commit_real()) { -- if (m_emojier_focus_commit_text_id > 0) { -+ var context = GLib.MainContext.default(); -+ if (m_emojier_focus_commit_text_id > 0 && -+ context.find_source_by_id(m_emojier_focus_commit_text_id) -+ != null) { - GLib.Source.remove(m_emojier_focus_commit_text_id); -- m_emojier_focus_commit_text_id = -1; - } -+ m_emojier_focus_commit_text_id = -1; - } - } - } --- -2.9.3 - -From aba81ace1487bea57e80406413ae31cbb37ac7d2 Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Thu, 20 Apr 2017 12:10:47 +0900 -Subject: [PATCH] ui/gtk3: Fix build failures - -Add-on fix to 290f786 -if /usr/share/vala*/vapi/ibus-emoji-dialog*.vapi does not exist. - -R=Shawn.P.Huang@gmail.com - -Review URL: https://codereview.appspot.com/323780043 ---- - ui/gtk3/Makefile.am | 8 +------- - 1 file changed, 1 insertion(+), 7 deletions(-) - -diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am -index 6f0fb62..d2ae0d6 100644 ---- a/ui/gtk3/Makefile.am -+++ b/ui/gtk3/Makefile.am -@@ -163,13 +163,7 @@ EXTRA_DIST = \ - $(NULL) - - if ENABLE_EMOJI_DICT --AM_VALAFLAGS += \ -- --define=EMOJI_DICT \ -- --vapidir=$(top_builddir)/ui/gtk3 \ -- --vapidir=$(top_srcdir)/ui/gtk3 \ -- --pkg=ibus-emoji-dialog-1.0 \ -- --pkg=gtk+-3.0 \ -- $(NULL) -+AM_VALAFLAGS += --define=EMOJI_DICT - - libibus_emoji_dialog = libibus-emoji-dialog-1.0.la - --- -2.9.3 - -From 61fa8eabd812a75b3cadd883ad4a4e92cf6877d3 Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Tue, 25 Apr 2017 12:10:00 +0900 -Subject: [PATCH] ui/gtk3: Get emoji font size from gsettings - -If Emojier CLI does not specify the font size likes ---font 'Noto Emoji Color Regular', get the default size from GSettings. - -R=Shawn.P.Huang@gmail.com, alexepico@gmail.com - -Review URL: https://codereview.appspot.com/317540043 ---- - setup/setup.ui | 2 +- - ui/gtk3/emojier.vala | 41 ++++++++++++++++++++++++++--------------- - ui/gtk3/emojierapp.vala | 14 ++++++++++---- - ui/gtk3/panel.vala | 2 +- - 4 files changed, 38 insertions(+), 21 deletions(-) - -diff --git a/setup/setup.ui b/setup/setup.ui -index 4ef3423..54ef916 100644 ---- a/setup/setup.ui -+++ b/setup/setup.ui -@@ -137,7 +137,7 @@ - False - The shortcut keys for showing emoji dialog - start -- Emoji dialog: -+ Emoji choice: - - - 5 -diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala -index 5a9bf8f..76dca6c 100644 ---- a/ui/gtk3/emojier.vala -+++ b/ui/gtk3/emojier.vala -@@ -166,13 +166,14 @@ class IBusEmojier : Gtk.Window { - BACKWARD, - } - -- private static const uint EMOJI_GRID_PAGE = 10; -+ private const uint EMOJI_GRID_PAGE = 10; - - // Set the actual default values in the constructor - // because these fields are used for class_init() and static functions, - // e.g. set_emoji_font(), can be called before class_init() is called. - private static string m_current_lang_id; -- private static string m_emoji_font; -+ private static string m_emoji_font_family; -+ private static int m_emoji_font_size; - private static string[] m_favorites; - private static int m_emoji_max_seq_len; - private static GLib.HashTable? -@@ -220,8 +221,10 @@ class IBusEmojier : Gtk.Window { - - if (m_current_lang_id == null) - m_current_lang_id = "en"; -- if (m_emoji_font == null) -- m_emoji_font = "Monospace 16"; -+ if (m_emoji_font_family == null) -+ m_emoji_font_family = "Monospace"; -+ if (m_emoji_font_size == 0) -+ m_emoji_font_size = 16; - if (m_favorites == null) - m_favorites = {}; - -@@ -242,6 +245,8 @@ class IBusEmojier : Gtk.Window { - "#IBusEmojierWhiteLabel { background-color: " + - "rgba(%u, %u, %u, %lf); ".printf( - bg_red, bg_green, bg_blue, bg_alpha) + -+ "font-family: %s; font-size: %dpt; ".printf( -+ m_emoji_font_family, m_emoji_font_size) + - "border-width: 4px; border-radius: 3px; } "; - - uint fg_red = (uint)(m_rgba.selected_fg.red * 255); -@@ -255,6 +260,8 @@ class IBusEmojier : Gtk.Window { - data += "#IBusEmojierSelectedLabel { color: " + - "rgba(%u, %u, %u, %lf); ".printf( - fg_red, fg_green, fg_blue, fg_alpha) + -+ "font-family: %s; font-size: %dpt; ".printf( -+ m_emoji_font_family, m_emoji_font_size) + - "background-color: " + - "rgba(%u, %u, %u, %lf); ".printf( - bg_red, bg_green, bg_blue, bg_alpha) + -@@ -273,7 +280,7 @@ class IBusEmojier : Gtk.Window { - css_provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); - -- m_title = new ETitleLabelBox(_("Emoji Chooser")); -+ m_title = new ETitleLabelBox(_("Emoji Choice")); - set_titlebar(m_title); - m_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); - add(m_vbox); -@@ -734,17 +741,14 @@ class IBusEmojier : Gtk.Window { - label = new ESelectedLabel(candidate.text) as Gtk.Label; - else - label = new EWhiteLabel(candidate.text) as Gtk.Label; -- string emoji_font = m_emoji_font; - if (candidate.text.char_count() > 2) { -- Pango.FontDescription font_desc = -- Pango.FontDescription.from_string(emoji_font); -- string font_family = font_desc.get_family(); -- int font_size = font_desc.get_size() / Pango.SCALE; -- emoji_font = "%s %d".printf(font_family, font_size -2); -+ string font_family = m_emoji_font_family; -+ int font_size = m_emoji_font_size - 2; -+ string emoji_font = "%s %d".printf(font_family, font_size); -+ string markup = "%s". -+ printf(emoji_font, candidate.get_text()); -+ label.set_markup(markup); - } -- string markup = "%s". -- printf(emoji_font, candidate.get_text()); -- label.set_markup(markup); - label.set_halign(Gtk.Align.FILL); - label.set_valign(Gtk.Align.FILL); - Gtk.EventBox candidate_ebox = new Gtk.EventBox(); -@@ -1268,7 +1272,14 @@ class IBusEmojier : Gtk.Window { - - public static void set_emoji_font(string? emoji_font) { - return_if_fail(emoji_font != null && emoji_font != ""); -- m_emoji_font = emoji_font; -+ Pango.FontDescription font_desc = -+ Pango.FontDescription.from_string(emoji_font); -+ string font_family = font_desc.get_family(); -+ if (font_family != null) -+ m_emoji_font_family = font_family; -+ int font_size = font_desc.get_size() / Pango.SCALE; -+ if (font_size != 0) -+ m_emoji_font_size = font_size; - } - - -diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala -index 4564a25..cbdd8ba 100644 ---- a/ui/gtk3/emojierapp.vala -+++ b/ui/gtk3/emojierapp.vala -@@ -60,6 +60,9 @@ public class EmojiApplication : Application { - - - private int _command_line (ApplicationCommandLine command_line) { -+ // Set default font size -+ IBusEmojier.set_emoji_font(m_settings_emoji.get_string("font")); -+ - const OptionEntry[] options = { - { "font", 0, 0, OptionArg.STRING, out emoji_font, - /* TRANSLATORS: "FONT" should be capital and translatable. -@@ -88,6 +91,10 @@ public class EmojiApplication : Application { - _args[i] = args[i]; - } - -+ // Need to initialize for the second instance. -+ emoji_font = null; -+ annotation_lang = null; -+ - try { - unowned string[] tmp = _args; - option.parse(ref tmp); -@@ -101,13 +108,12 @@ public class EmojiApplication : Application { - return Posix.EXIT_SUCCESS; - } - -- if (emoji_font == null) -- emoji_font = m_settings_emoji.get_string("font"); - if (annotation_lang == null) - annotation_lang = m_settings_emoji.get_string("lang"); -- - IBusEmojier.set_annotation_lang(annotation_lang); -- IBusEmojier.set_emoji_font(emoji_font); -+ -+ if (emoji_font != null) -+ IBusEmojier.set_emoji_font(emoji_font); - - activate_dialog(command_line); - -diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala -index 2375734..745489b 100644 ---- a/ui/gtk3/panel.vala -+++ b/ui/gtk3/panel.vala -@@ -1239,7 +1239,7 @@ class Panel : IBus.PanelService { - m_sys_menu.append(item); - - #if EMOJI_DICT -- item = new Gtk.MenuItem.with_label(_("Emoji Dialog")); -+ item = new Gtk.MenuItem.with_label(_("Emoji Choice")); - item.activate.connect((i) => { - Gdk.Event event = new Gdk.Event(Gdk.EventType.KEY_PRESS); - handle_emoji_typing(event); --- -2.9.3 - -From 2f73064d03481c9f34d278dd19d1a4427d6ecb27 Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Fri, 28 Apr 2017 13:10:03 +0900 -Subject: [PATCH] Change to use Unicode Emoji files instead of EmojiOne - files - -EmojiOne Json file have changed the file format since 3.0 [1] -Now IBus migrated to use Unicode Emoji files since the EmojiOne file -had been used for the emoji categories but it can be done with -Unicode emoji-test.txt [2] -Use Unicode Emoji 4.0 until 5.0 will be released officially. -Unicode Emoji 5.0 is available but fonts does not support it yet. - -[1] https://github.com/Ranks/emojione/releases/tag/v3.0.0 -[2] http://www.unicode.org/Public/emoji/4.0/ - -Review URL: https://codereview.appspot.com/321860043 ---- - bindings/vala/Makefile.am | 3 +- - configure.ac | 27 +- - data/annotations/Makefile.am | 3 +- - data/annotations/en_ascii.xml | 56 ++++ - po/POTFILES.in | 3 +- - po/POTFILES.skip | 2 +- - src/Makefile.am | 17 +- - src/emoji-parser.c | 632 ++++++++++++++++++++++++++++++++++++++---- - src/ibusemoji.c | 47 +--- - src/ibusemoji.h | 14 - - src/ibusemojigen.h | 39 +++ - ui/gtk3/Makefile.am | 5 +- - ui/gtk3/emojier.vala | 106 +++++-- - 13 files changed, 780 insertions(+), 174 deletions(-) - create mode 100644 data/annotations/en_ascii.xml - create mode 100644 src/ibusemojigen.h - -diff --git a/bindings/vala/Makefile.am b/bindings/vala/Makefile.am -index f3eb0d4..4e34afc 100644 ---- a/bindings/vala/Makefile.am -+++ b/bindings/vala/Makefile.am -@@ -43,8 +43,7 @@ vapidir = $(datadir)/vala/vapi - vapi_DATA = $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps) - - MAINTAINERCLEANFILES = $(VAPIGEN_VAPIS) --# for make distclean --CONFIG_CLEAN_FILES = $(VAPIGEN_VAPIS) -+DISTCLEANFILES = $(VAPIGEN_VAPIS) - - EXTRA_DIST = \ - $(VAPIGEN_VAPIS) \ -diff --git a/configure.ac b/configure.ac -index 0a5f2d5..ea568a0 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -612,14 +612,14 @@ AC_ARG_ENABLE(emoji-dict, - ) - AM_CONDITIONAL([ENABLE_EMOJI_DICT], [test x"$enable_emoji_dict" = x"yes"]) - --AC_ARG_WITH(emoji-json-file, -- AS_HELP_STRING([--with-emoji-json-file[=DIR/emoji.json]], -- [Set emoji.json. (default: "/usr/lib/node_modules/emojione/emoji.json") -- ]), -- EMOJI_JSON_FILE=$with_emoji_json_file, -- EMOJI_JSON_FILE="/usr/lib/node_modules/emojione/emoji.json" -+AC_ARG_WITH(unicode-emoji-dir, -+ AS_HELP_STRING([--with-unicode-emoji-dir[=DIR]], -+ [Set the directory of Unicode Emoji. -+ (default: "/usr/share/unicode/emoji")]), -+ UNICODE_EMOJI_DIR=$with_unicode_emoji_dir, -+ UNICODE_EMOJI_DIR="/usr/share/unicode/emoji" - ) --AC_SUBST(EMOJI_JSON_FILE) -+AC_SUBST(UNICODE_EMOJI_DIR) - - AC_ARG_WITH(emoji-annotation-dir, - AS_HELP_STRING([--with-emoji-annotation-dir[=DIR]], -@@ -631,17 +631,14 @@ AC_ARG_WITH(emoji-annotation-dir, - AC_SUBST(EMOJI_ANNOTATION_DIR) - - if test x"$enable_emoji_dict" = x"yes"; then -- if test ! -f $EMOJI_JSON_FILE ; then -- AC_MSG_ERROR(Not found $EMOJI_JSON_FILE. You can get emoji.json \ --with "npm install -g emojione".) -+ if test ! -f $UNICODE_EMOJI_DIR/emoji-test.txt ; then -+ AC_MSG_ERROR(Not found $UNICODE_EMOJI_DIR/emoji-test.txt. You can get \ -+the emoji files from http://www.unicode.org/Public/emoji/4.0/) - fi - if test ! -f $EMOJI_ANNOTATION_DIR/en.xml ; then - AC_MSG_ERROR(Not found $EMOJI_ANNOTATION_DIR/en.xml. You can get \ - https://github.com/fujiwarat/cldr-emoji-annotation) - fi -- PKG_CHECK_MODULES(JSON_GLIB1, [ -- json-glib-1.0 -- ]) - enable_emoji_dict="yes (enabled, use --disable-emoji-dict to disable)" - fi - -@@ -730,8 +727,8 @@ Build options: - Enable surrounding-text $enable_surrounding_text - Enable libnotify $enable_libnotify - Enable Emoji dict $enable_emoji_dict -- emoji.json path $EMOJI_JSON_FILE -- CLDR annotation dir $EMOJI_ANNOTATION_DIR -+ Uicode Emoji directory $UNICODE_EMOJI_DIR -+ CLDR annotation directory $EMOJI_ANNOTATION_DIR - Run test cases $enable_tests - ]) - -diff --git a/data/annotations/Makefile.am b/data/annotations/Makefile.am -index d87b933..0b7779b 100644 ---- a/data/annotations/Makefile.am -+++ b/data/annotations/Makefile.am -@@ -2,7 +2,7 @@ - # - # ibus - The Input Bus - # --# Copyright (c) 2016 Takao Fujiwara -+# Copyright (c) 2016-2017 Takao Fujiwara - # Copyright (c) 2016 Red Hat, Inc. - # - # This library is free software; you can redistribute it and/or -@@ -22,6 +22,7 @@ - - EXTRA_DIST = \ - en.xml \ -+ en_ascii.xml \ - $(NULL) - - -include $(top_srcdir)/git.mk -diff --git a/data/annotations/en_ascii.xml b/data/annotations/en_ascii.xml -new file mode 100644 -index 0000000..f1bdcbb ---- /dev/null -+++ b/data/annotations/en_ascii.xml -@@ -0,0 +1,56 @@ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ :') | :'-) -+ :D | :-D | =D -+ ':) | ':-) | '=) | ':D | ':-D | '=D -+ >:) | >;) | >:-) | >=) -+ ;) | ;-) | *-) | *) | ;-] | ;] | ;D | ;^) -+ B-) | B) | 8) | 8-) | B-D | 8-D -+ :* | :-* | =* | :^* -+ :) | :-) | =] | =) | :] -+ -_- | -__- | -___- -+ :-X | :X | :-# | :# | =X | =x | :x | :-x | =# -+ >.< -+ :-O | :O | :-o | :o | O_O | >:O -+ :P | :-P | =P | :-p | :p | =p | :-Þ | :Þ | :þ | :-þ | :-b | :b | d: -+ >:P | X-P | x-p -+ ':( | ':-( | '=( -+ >:\ | >:/ | :-/ | :-. | :/ | :\ | =/ | =\ | :L | =L -+ >:[ | :-( | :( | :-[ | :[ | =( -+ :'( | :'-( | ;( | ;-( -+ D: -+ :$ | =$ -+ #-) | #) | %-) | %) | X) | X-) -+ >:( | >:-( | :@ -+ O:-) | 0:-3 | 0:3 | 0:-) | 0:) | 0;^) | O:) | O;-) | O=) | 0;-) | O:-3 | O:3 -+ *\0/* | \0/ | *\O/* | \O/ -+ <3 -+ </3 -+ -+ -diff --git a/po/POTFILES.in b/po/POTFILES.in -index 25be4f4..00f7c7f 100644 ---- a/po/POTFILES.in -+++ b/po/POTFILES.in -@@ -41,6 +41,7 @@ setup/keyboardshortcut.py - setup/main.py - src/ibusbus.c - src/ibusconfig.c -+src/ibusemojigen.h - src/ibusengine.c - src/ibusfactory.c - src/ibushotkey.c -@@ -57,6 +58,7 @@ ui/gtk3/application.vala - ui/gtk3/candidatearea.vala - ui/gtk3/candidatepanel.vala - ui/gtk3/emojier.vala -+ui/gtk3/emojierapp.vala - ui/gtk3/handle.vala - ui/gtk3/iconwidget.vala - ui/gtk3/keybindingmanager.vala -@@ -66,4 +68,3 @@ ui/gtk3/property.vala - ui/gtk3/propertypanel.vala - ui/gtk3/separator.vala - ui/gtk3/switcher.vala --ui/gtk3/emojierapp.vala -diff --git a/po/POTFILES.skip b/po/POTFILES.skip -index 891d2cc..7190221 100644 ---- a/po/POTFILES.skip -+++ b/po/POTFILES.skip -@@ -12,6 +12,7 @@ ui/gtk3/application.c - ui/gtk3/candidatearea.c - ui/gtk3/candidatepanel.c - ui/gtk3/emojier.c -+ui/gtk3/emojierapp.c - ui/gtk3/handle.c - ui/gtk3/iconwidget.c - ui/gtk3/keybindingmanager.c -@@ -21,4 +22,3 @@ ui/gtk3/property.c - ui/gtk3/propertypanel.c - ui/gtk3/separator.c - ui/gtk3/switcher.c --ui/gtk3/emojierapp.c -diff --git a/src/Makefile.am b/src/Makefile.am -index 7053e3e..27cd168 100644 ---- a/src/Makefile.am -+++ b/src/Makefile.am -@@ -166,6 +166,7 @@ ibusinclude_HEADERS = \ - ibus_private_headers = \ - gtkimcontextsimpleseqs.h \ - ibuscomposetable.h \ -+ ibusemojigen.h \ - ibusenginesimpleprivate.h \ - ibusinternal.h \ - keyname-table.h \ -@@ -247,17 +248,26 @@ dicts/emoji-en.dict: emoji-parser - echo "WARNING: Not found $(EMOJI_ANNOTATION_DIR)/en.xml" 1>&2; \ - fi; \ - for f in $(LANG_FILES) ; do \ -+ if test -f \ -+ "$(EMOJI_ANNOTATION_DIR)/../annotationsDerived/$$f.xml" ; then \ -+ xml_derived_option="--xml-derived $(EMOJI_ANNOTATION_DIR)/../annotationsDerived/$$f.xml"; \ -+ plus_comment="derived"; \ -+ fi; \ - if test x"$$f" = xen ; then \ - $(builddir)/emoji-parser \ -+ --unicode-emoji-dir $(UNICODE_EMOJI_DIR) \ - --xml $(EMOJI_ANNOTATION_DIR)/$$f.xml \ -- --json $(EMOJI_JSON_FILE) \ -+ $$xml_derived_option \ -+ --xml-ascii $(top_srcdir)/data/annotations/en_ascii.xml \ -+ --out-category ibusemojigen.h \ - --out $@; \ - else \ - $(builddir)/emoji-parser \ - --xml $(EMOJI_ANNOTATION_DIR)/$$f.xml \ -+ $$xml_derived_option \ - --out dicts/emoji-$$f.dict; \ - fi; \ -- echo "Generated dicts/emoji-$$f.dict"; \ -+ echo "Generated $$plus_comment dicts/emoji-$$f.dict"; \ - done - - install-data-hook: $(dict_DATA) -@@ -301,11 +311,9 @@ emoji_parser_SOURCES = \ - $(NULL) - emoji_parser_CFLAGS = \ - $(GLIB2_CFLAGS) \ -- $(JSON_GLIB1_CFLAGS) \ - $(NULL) - emoji_parser_LDADD = \ - $(GLIB2_LIBS) \ -- $(JSON_GLIB1_LIBS) \ - $(libibus) \ - $(NULL) - -@@ -329,6 +337,7 @@ CLEANFILES += \ - $(NULL) - - DISTCLEANFILES = \ -+ ibusemojigen.h \ - ibusversion.h \ - $(NULL) - -diff --git a/src/emoji-parser.c b/src/emoji-parser.c -index e5dce3f..5e6155b 100644 ---- a/src/emoji-parser.c -+++ b/src/emoji-parser.c -@@ -21,34 +21,52 @@ - */ - - /* Convert /usr/share/unicode/cldr/common/annotations/\*.xml and -- * /usr/lib/node_modules/emojione/emoji.json -+ * /usr/share/unicode/emoji/emoji-test.txt - * to the dictionary file which look up the Emoji from the annotation. - * Get *.xml from https://github.com/fujiwarat/cldr-emoji-annotation - * or http://www.unicode.org/repos/cldr/trunk/common/annotations . -- * Get emoji.json with 'npm install -g emojione'. -- * en.xml is used for the Unicode annotations and emoji.json is used -- * for the aliases_ascii, e.g. ":)", and category, e.g. "people". -+ * Get emoji-test.txt from http://unicode.org/Public/emoji/4.0/ . -+ * en.xml is used for the Unicode annotations and emoji-test.txt is used -+ * for the category, e.g. "Smileys & People". -+ * ASCII emoji annotations are saved in ../data/annotations/en_ascii.xml - */ - - #include -+ -+#ifdef HAVE_JSON_GLIB1 - #include -+#endif - - #include - - #include "ibusemoji.h" - -+/* This file has 21 lines about the license at the top of the file. */ -+#define LICENSE_LINES 21 -+ -+typedef enum { -+ EMOJI_STRICT, -+ EMOJI_VARIANT, -+ EMOJI_NOVARIANT -+} EmojiDataSearchType; -+ - typedef struct _EmojiData EmojiData; - struct _EmojiData { -- gchar *emoji; -- gchar *emoji_alternates; -- GSList *annotations; -- gboolean is_annotation; -- gchar *description; -- gboolean is_tts; -- gchar *category; -- GSList *list; -+ gchar *emoji; -+ gchar *emoji_alternates; -+ GSList *annotations; -+ gboolean is_annotation; -+ gchar *description; -+ gboolean is_tts; -+ gchar *category; -+ gchar *subcategory; -+ gboolean is_derived; -+ GSList *list; -+ EmojiDataSearchType search_type; - }; - -+static gchar *unicode_emoji_version; -+ - static void - reset_emoji_element (EmojiData *data) - { -@@ -59,68 +77,175 @@ reset_emoji_element (EmojiData *data) - g_slist_free_full (data->annotations, g_free); - data->annotations = NULL; - g_clear_pointer (&data->description, g_free); -- g_clear_pointer (&data->category, g_free); -+} -+ -+gint -+strcmp_novariant (const gchar *a, -+ const gchar *b, -+ gunichar a_variant, -+ gunichar b_variant) -+{ -+ gint retval; -+ gchar *p = NULL; -+ GString *buff = NULL;; -+ gchar *substr = NULL; -+ -+ if (a_variant > 0) { -+ if ((p = g_utf8_strchr (a, -1, a_variant)) != NULL) { -+ buff = g_string_new (NULL); -+ if (a != p) { -+ substr = g_strndup (a, p - a); -+ g_string_append (buff, substr); -+ g_free (substr); -+ } -+ p = g_utf8_next_char (p); -+ if (*p != '\0') -+ g_string_append (buff, p); -+ retval = g_strcmp0 (buff->str, b); -+ g_string_free (buff, TRUE); -+ return retval; -+ } else { -+ return -1; -+ } -+ } else if (b_variant > 0) { -+ if ((p = g_utf8_strchr (b, -1, b_variant)) != NULL) { -+ buff = g_string_new (NULL); -+ if (b != p) { -+ substr = g_strndup (b, p - b); -+ g_string_append (buff, substr); -+ g_free (substr); -+ } -+ p = g_utf8_next_char (p); -+ if (*p != '\0') -+ g_string_append (buff, p); -+ retval = g_strcmp0 (a, buff->str); -+ g_string_free (buff, TRUE); -+ return retval; -+ } else { -+ return -1; -+ } -+ } -+ return g_strcmp0 (a, b); - } - - gint - find_emoji_data_list (IBusEmojiData *a, -- const gchar *b) -+ EmojiData *b) - { -- g_return_val_if_fail (IBUS_IS_EMOJI_DATA (a), 0); -- return g_strcmp0 (ibus_emoji_data_get_emoji (a), b); -+ const gchar *a_str; -+ -+ g_return_val_if_fail (IBUS_IS_EMOJI_DATA (a), 0); -+ -+ a_str = ibus_emoji_data_get_emoji (a); -+ switch (b->search_type) { -+ case EMOJI_VARIANT: -+ if (strcmp_novariant (a_str, b->emoji, 0xfe0e, 0) == 0) -+ return 0; -+ else if (strcmp_novariant (a_str, b->emoji, 0xfe0f, 0) == 0) -+ return 0; -+ else -+ return g_strcmp0 (a_str, b->emoji); -+ break; -+ case EMOJI_NOVARIANT: -+ if (strcmp_novariant (a_str, b->emoji, 0, 0xfe0e) == 0) -+ return 0; -+ else if (strcmp_novariant (a_str, b->emoji, 0, 0xfe0f) == 0) -+ return 0; -+ else -+ return g_strcmp0 (a_str, b->emoji); -+ break; -+ default:; -+ } -+ return g_strcmp0 (a_str, b->emoji); - } - - static void --update_emoji_list (EmojiData *data) -+emoji_data_update_object (EmojiData *data, -+ IBusEmojiData *emoji) - { -- GSList *list = g_slist_find_custom ( -- data->list, -- data->emoji, -- (GCompareFunc) find_emoji_data_list); -- if (list) { -- IBusEmojiData *emoji = list->data; -- GSList *src_annotations = data->annotations; -- GSList *dest_annotations = ibus_emoji_data_get_annotations (emoji); -- GSList *l; -- gboolean updated_annotations = FALSE; -- for (l = src_annotations; l; l = l->next) { -- GSList *duplicated = g_slist_find_custom (dest_annotations, -- l->data, -- (GCompareFunc) g_strcmp0); -- if (duplicated == NULL) { -- dest_annotations = g_slist_append (dest_annotations, -- g_strdup (l->data)); -- updated_annotations = TRUE; -- } -+ GSList *src_annotations = data->annotations; -+ GSList *dest_annotations = ibus_emoji_data_get_annotations (emoji); -+ GSList *l; -+ gboolean updated_annotations = FALSE; -+ for (l = src_annotations; l; l = l->next) { -+ GSList *duplicated = g_slist_find_custom (dest_annotations, -+ l->data, -+ (GCompareFunc) g_strcmp0); -+ if (duplicated == NULL) { -+ dest_annotations = g_slist_append (dest_annotations, -+ g_strdup (l->data)); -+ updated_annotations = TRUE; - } -- if (updated_annotations) { -- ibus_emoji_data_set_annotations ( -+ } -+ if (updated_annotations) { -+ ibus_emoji_data_set_annotations ( - emoji, - g_slist_copy_deep (dest_annotations, - (GCopyFunc) g_strdup, - NULL)); -+ } -+ if (data->description) -+ ibus_emoji_data_set_description (emoji, data->description); -+} -+ -+static void -+emoji_data_new_object (EmojiData *data) -+{ -+ IBusEmojiData *emoji = -+ ibus_emoji_data_new ("emoji", -+ data->emoji, -+ "annotations", -+ data->annotations, -+ "description", -+ data->description ? data->description -+ : g_strdup (""), -+ "category", -+ data->category ? data->category -+ : g_strdup (""), -+ NULL); -+ data->list = g_slist_append (data->list, emoji); -+} -+ -+static void -+update_emoji_list (EmojiData *data, -+ gboolean base_update) -+{ -+ GSList *list; -+ data->search_type = EMOJI_STRICT; -+ list = g_slist_find_custom ( -+ data->list, -+ data, -+ (GCompareFunc) find_emoji_data_list); -+ if (list) { -+ emoji_data_update_object (data, list->data); -+ return; -+ } else if (base_update) { -+ emoji_data_new_object (data); -+ return; -+ } -+ if (g_utf8_strchr (data->emoji, -1, 0xfe0e) == NULL && -+ g_utf8_strchr (data->emoji, -1, 0xfe0f) == NULL) { -+ data->search_type = EMOJI_VARIANT; -+ list = g_slist_find_custom ( -+ data->list, -+ data, -+ (GCompareFunc) find_emoji_data_list); -+ if (list) { -+ emoji_data_update_object (data, list->data); -+ return; - } -- if (data->description) -- ibus_emoji_data_set_description (emoji, data->description); - } else { -- IBusEmojiData *emoji = -- ibus_emoji_data_new ("emoji", -- data->emoji, -- "annotations", -- data->annotations, -- "description", -- data->description ? data->description -- : g_strdup (""), -- "category", -- data->category ? data->category -- : g_strdup (""), -- "emoji-alternates", -- data->emoji_alternates -- ? data->emoji_alternates -- : g_strdup (""), -- NULL); -- data->list = g_slist_append (data->list, emoji); -+ data->search_type = EMOJI_NOVARIANT; -+ list = g_slist_find_custom ( -+ data->list, -+ data, -+ (GCompareFunc) find_emoji_data_list); -+ if (list) { -+ emoji_data_update_object (data, list->data); -+ return; -+ } - } -+ emoji_data_new_object (data); - } - - static void -@@ -183,7 +308,7 @@ unicode_annotations_end_element_cb (GMarkupParseContext *context, - if (!data->is_annotation) - return; - -- update_emoji_list (data); -+ update_emoji_list (data, FALSE); - data->is_annotation = FALSE; - data->is_tts = FALSE; - } -@@ -227,7 +352,8 @@ unicode_annotations_text_cb (GMarkupParseContext *context, - - static gboolean - unicode_annotations_parse_xml_file (const gchar *filename, -- GSList **list) -+ GSList **list, -+ gboolean is_derived) - { - gchar *content = NULL; - gsize length = 0; -@@ -251,6 +377,7 @@ unicode_annotations_parse_xml_file (const gchar *filename, - } - - data.list = *list; -+ data.is_derived = is_derived; - - context = g_markup_parse_context_new (&parser, 0, &data, NULL); - if (!g_markup_parse_context_parse (context, content, length, &error)) { -@@ -276,6 +403,233 @@ failed_to_parse_unicode_annotations: - } - - static gboolean -+unicode_emoji_test_parse_unicode (const gchar *line, -+ EmojiData *data) -+{ -+ GString *emoji = NULL; -+ gchar *endptr = NULL; -+ guint32 uch; -+ static gchar outbuf[8] = { 0, }; -+ -+ g_return_val_if_fail (line != NULL, FALSE); -+ -+ emoji = g_string_new (NULL); -+ while (line && *line) { -+ uch = g_ascii_strtoull (line, &endptr, 16); -+ outbuf[g_unichar_to_utf8 (uch, outbuf)] = '\0'; -+ g_string_append (emoji, outbuf); -+ if (*endptr == '\0') { -+ break; -+ } -+ line = endptr + 1; -+ while (*line == ' ') -+ line++; -+ endptr = NULL; -+ } -+ -+ data->emoji = g_string_free (emoji, FALSE); -+ return TRUE; -+} -+ -+static gboolean -+unicode_emoji_test_parse_description (const gchar *line, -+ EmojiData *data) -+{ -+ g_return_val_if_fail (line != NULL, FALSE); -+ -+ /* skip spaces */ -+ while (*line == ' ') -+ line++; -+ /* skip emoji */ -+ while (*line != ' ') -+ line++; -+ /* skip spaces */ -+ while (*line == ' ') -+ line++; -+ if (*line == '\0') -+ return FALSE; -+ data->description = g_strdup (line); -+ return TRUE; -+} -+ -+#define EMOJI_VERSION_TAG "# Version: " -+#define EMOJI_GROUP_TAG "# group: " -+#define EMOJI_SUBGROUP_TAG "# subgroup: " -+#define EMOJI_NON_FULLY_QUALIFIED_TAG "non-fully-qualified" -+ -+static gboolean -+unicode_emoji_test_parse_line (const gchar *line, -+ EmojiData *data) -+{ -+ int tag_length; -+ gchar **segments = NULL; -+ -+ g_return_val_if_fail (line != NULL, FALSE); -+ -+ tag_length = strlen (EMOJI_VERSION_TAG); -+ if (strlen (line) > tag_length && -+ g_ascii_strncasecmp (line, EMOJI_VERSION_TAG, tag_length) == 0) { -+ unicode_emoji_version = g_strdup (line + tag_length); -+ return TRUE; -+ } -+ tag_length = strlen (EMOJI_GROUP_TAG); -+ if (strlen (line) > tag_length && -+ g_ascii_strncasecmp (line, EMOJI_GROUP_TAG, tag_length) == 0) { -+ g_free (data->category); -+ g_clear_pointer (&data->subcategory, g_free); -+ data->category = g_strdup (line + tag_length); -+ return TRUE; -+ } -+ tag_length = strlen (EMOJI_SUBGROUP_TAG); -+ if (strlen (line) > tag_length && -+ g_ascii_strncasecmp (line, EMOJI_SUBGROUP_TAG, tag_length) == 0) { -+ g_free (data->subcategory); -+ data->subcategory = g_strdup (line + tag_length); -+ return TRUE; -+ } -+ if (*line == '#') -+ return TRUE; -+ segments = g_strsplit (line, "; ", 2); -+ if (segments[1] == NULL) { -+ g_warning ("No qualified line\n"); -+ goto failed_to_parse_unicode_emoji_test_line; -+ return FALSE; -+ } -+ tag_length = strlen (EMOJI_NON_FULLY_QUALIFIED_TAG); -+ /* Ignore the non-fully-qualified emoji */ -+ if (g_ascii_strncasecmp (segments[1], EMOJI_NON_FULLY_QUALIFIED_TAG, -+ tag_length) == 0) { -+ g_strfreev (segments); -+ return TRUE; -+ } -+ unicode_emoji_test_parse_unicode (segments[0], data); -+ g_strfreev (segments); -+ segments = g_strsplit (line, "# ", 2); -+ if (segments[1] == NULL) { -+ g_warning ("No description line\n"); -+ goto failed_to_parse_unicode_emoji_test_line; -+ return FALSE; -+ } -+ unicode_emoji_test_parse_description (segments[1], data); -+ g_strfreev (segments); -+ if (data->annotations == NULL) { -+ if (data->subcategory) { -+ int i; -+ gchar *amp; -+ segments = g_strsplit(data->subcategory, "-", -1); -+ for (i = 0; segments[i]; i++) { -+ if ((amp = strchr (segments[i], '&')) != NULL) { -+ if (amp - segments[i] <= 1) { -+ g_warning ("Wrong ampersand"); -+ goto failed_to_parse_unicode_emoji_test_line; -+ } -+ data->annotations = g_slist_append ( -+ data->annotations, -+ g_strndup (segments[i], amp - segments[i] - 1)); -+ data->annotations = g_slist_append ( -+ data->annotations, -+ g_strdup (amp + 1)); -+ continue; -+ } -+ data->annotations = g_slist_append (data->annotations, -+ g_strdup (segments[i])); -+ } -+ g_strfreev (segments); -+ } else { -+ g_warning ("No subcategory line\n"); -+ goto failed_to_parse_unicode_emoji_test_line; -+ } -+ } -+ update_emoji_list (data, TRUE); -+ reset_emoji_element (data); -+ return TRUE; -+ -+failed_to_parse_unicode_emoji_test_line: -+ if (segments) -+ g_strfreev (segments); -+ reset_emoji_element (data); -+ return FALSE; -+} -+ -+#undef EMOJI_VERSION_TAG -+#undef EMOJI_GROUP_TAG -+#undef EMOJI_SUBGROUP_TAG -+#undef EMOJI_NON_FULLY_QUALIFIED_TAG -+ -+static gboolean -+unicode_emoji_test_parse_file (const gchar *filename, -+ GSList **list) -+{ -+ gchar *content = NULL; -+ gsize length = 0; -+ GError *error = NULL; -+ gchar *head, *end, *line; -+ int n = 1; -+ EmojiData data = { 0, }; -+ -+ g_return_val_if_fail (filename != NULL, FALSE); -+ g_return_val_if_fail (list != NULL, FALSE); -+ -+ if (!g_file_get_contents (filename, &content, &length, &error)) { -+ g_warning ("Failed to load %s: %s", filename, error->message); -+ goto failed_to_parse_unicode_emoji_test; -+ } -+ head = end = content; -+ while (*end == '\n' && end - content < length) { -+ end++; -+ n++; -+ } -+ head = end; -+ data.list = *list; -+ while (end - content < length) { -+ while (*end != '\n' && end - content < length) -+ end++; -+ if (end - content >= length) -+ break; -+ line = g_strndup (head, end - head); -+ if (!unicode_emoji_test_parse_line (line, &data)) -+ g_warning ("parse error #%d in %s version %s: %s", -+ n, filename, -+ unicode_emoji_version ? unicode_emoji_version : "(null)", -+ line); -+ while (*end == '\n' && end - content < length) { -+ end++; -+ n++; -+ } -+ g_free (line); -+ head = end; -+ } -+ g_free (content); -+ g_free (unicode_emoji_version); -+ *list = data.list; -+ return TRUE; -+ -+failed_to_parse_unicode_emoji_test: -+ if (error) -+ g_error_free (error); -+ g_clear_pointer (&content, g_free); -+ return FALSE; -+} -+ -+static gboolean -+unicode_emoji_parse_dir (const gchar *dirname, -+ GSList **list) -+{ -+ gchar *filename = NULL; -+ g_return_val_if_fail (dirname != NULL, FALSE); -+ g_return_val_if_fail (list != NULL, FALSE); -+ -+ filename = g_build_path ("/", dirname, "emoji-test.txt", NULL); -+ if (!unicode_emoji_test_parse_file (filename, list)) { -+ g_free (filename); -+ return FALSE; -+ } -+ g_free (filename); -+ return TRUE; -+} -+ -+#ifdef HAVE_JSON_GLIB1 -+static gboolean - parse_emojione_unicode (JsonNode *node, - EmojiData *data, - gboolean is_alternates) -@@ -426,6 +780,31 @@ parse_emojione_category (JsonNode *node, - return TRUE; - } - -+#ifdef EMOJIONE_ALIASES_ASCII_PRINT -+static gchar * -+text_to_entity (const gchar *text) -+{ -+ gchar *p; -+ GString *buff = g_string_new (NULL); -+ for (p = text; *p; p++) { -+ switch (*p) { -+ case '<': -+ g_string_append (buff, "<"); -+ break; -+ case '>': -+ g_string_append (buff, ">"); -+ break; -+ case '&': -+ g_string_append (buff, "&"); -+ break; -+ default: -+ g_string_append_c (buff, *p); -+ } -+ } -+ g_string_free (buff, FALSE); -+} -+#endif -+ - static gboolean - parse_emojione_aliases_ascii (JsonNode *node, - EmojiData *data) -@@ -441,11 +820,23 @@ parse_emojione_aliases_ascii (JsonNode *node, - aliases_ascii = json_node_get_array (node); - length = json_array_get_length (aliases_ascii); - for (i = 0; i < length; i++) { -+#ifdef EMOJIONE_ALIASES_ASCII_PRINT -+ if (i == 0) -+ printf (" ", data->emoji); -+#endif - const gchar *alias = json_array_get_string_element (aliases_ascii, i); - GSList *duplicated = g_slist_find_custom (data->annotations, - alias, - (GCompareFunc) g_strcmp0); - if (duplicated == NULL) { -+#ifdef EMOJIONE_ALIASES_ASCII_PRINT -+ gchar *entity = text_to_entity (alias); -+ if (i != length - 1) -+ printf ("%s | ", entity); -+ else -+ printf ("%s\n", entity); -+ g_free (entity); -+#endif - data->annotations = g_slist_prepend (data->annotations, - g_strdup (alias)); - } -@@ -535,7 +926,7 @@ parse_emojione_element (JsonNode *node, - } - g_list_free (members); - -- update_emoji_list (data); -+ update_emoji_list (data, TRUE); - - return TRUE; - } -@@ -591,27 +982,136 @@ fail_to_json_file: - g_object_unref (parser); - return FALSE; - } -+#endif /* HAVE_JSON_GLIB1 */ -+ -+static void -+emoji_data_list_unify_categories (IBusEmojiData *data, -+ GSList **list) -+{ -+ g_return_if_fail (IBUS_IS_EMOJI_DATA (data)); -+ g_return_if_fail (list != NULL); -+ -+ const gchar *category = ibus_emoji_data_get_category (data); -+ if (*category == '\0') -+ return; -+ if (g_slist_find_custom (*list, category, (GCompareFunc)g_strcmp0) == NULL) -+ *list = g_slist_append (*list, g_strdup (category)); -+} -+ -+static void -+category_list_dump (const gchar *category, -+ GString *buff) -+{ -+ g_return_if_fail (buff != NULL); -+ -+ const gchar *line = g_strdup_printf (" N_(\"%s\"),\n", category); -+ g_string_append (buff, line); -+} -+ -+static void -+category_file_save (const gchar *filename, -+ GSList *list) -+{ -+ gchar *content = NULL; -+ gsize length = 0; -+ GError *error = NULL; -+ gchar *p; -+ GString *buff = NULL; -+ int i; -+ GSList *list_categories = NULL; -+ -+ g_return_if_fail (filename != NULL); -+ g_return_if_fail (list != NULL); -+ -+ g_slist_foreach (list, (GFunc)emoji_data_list_unify_categories, &list_categories); -+ if (list_categories == NULL) { -+ g_warning ("Not found categories in IBusEmojiData list"); -+ return; -+ } -+ -+ if (!g_file_get_contents (__FILE__, &content, &length, &error)) { -+ g_warning ("Failed to load %s: %s", __FILE__, error->message); -+ g_clear_pointer (&error, g_error_free); -+ } -+ buff = g_string_new (NULL); -+ p = content; -+ for (i = 0; i < LICENSE_LINES; i++, p++) { -+ if ((p = strchr (p, '\n')) == NULL) -+ break; -+ } -+ if (p != NULL) { -+ g_string_append (buff, g_strndup (content, p - content)); -+ g_string_append_c (buff, '\n'); -+ } -+ g_clear_pointer (&content, g_free); -+ -+ g_string_append (buff, g_strdup ("\n")); -+ g_string_append (buff, g_strdup_printf ("/* This file is generated by %s. */", __FILE__)); -+ g_string_append (buff, g_strdup ("\n")); -+ g_string_append (buff, g_strdup ("include \n")); -+ g_string_append (buff, g_strdup ("\n")); -+ g_string_append (buff, g_strdup ("#ifndef __IBUS_EMOJI_GEN_H_\n")); -+ g_string_append (buff, g_strdup ("#define __IBUS_EMOJI_GEN_H_\n")); -+ g_string_append (buff, g_strdup ("const static char *unicode_emoji_categories[] = {\n")); -+ list_categories = g_slist_sort (list_categories, (GCompareFunc)g_strcmp0); -+ g_slist_foreach (list_categories, (GFunc)category_list_dump, buff); -+ g_slist_free (list_categories); -+ g_string_append (buff, g_strdup ("};\n")); -+ g_string_append (buff, g_strdup ("#endif\n")); -+ -+ if (!g_file_set_contents (filename, buff->str, -1, &error)) { -+ g_warning ("Failed to save emoji category file %s: %s", filename, error->message); -+ g_error_free (error); -+ } -+ -+ g_string_free (buff, TRUE); -+} - - int - main (int argc, char *argv[]) - { - gchar *prgname; -+#ifdef HAVE_JSON_GLIB1 - gchar *json_file = NULL; -+#endif -+ gchar *emoji_dir = NULL; - gchar *xml_file = NULL; -+ gchar *xml_derived_file = NULL; -+ gchar *xml_ascii_file = NULL; - gchar *output = NULL; -+ gchar *output_category = NULL; - GOptionEntry entries[] = { -+#ifdef HAVE_JSON_GLIB1 - { "json", 'j', 0, G_OPTION_ARG_STRING, &json_file, - "Parse Emoji One JSON file", - "JSON" - }, -+#endif -+ { "unicode-emoji-dir", 'd', 0, G_OPTION_ARG_STRING, &emoji_dir, -+ "Parse Emoji files in DIRECTORY which includes emoji-test.txt " \ -+ "emoji-sequences.txt emoji-zwj-sequences.txt in unicode.org", -+ "DIRECTORY" -+ }, - { "out", 'o', 0, G_OPTION_ARG_STRING, &output, - "Save the emoji data as FILE", - "FILE" - }, -+ { "out-category", 'C', 0, G_OPTION_ARG_STRING, &output_category, -+ "Save the translatable categories as FILE", -+ "FILE" -+ }, - { "xml", 'x', 0, G_OPTION_ARG_STRING, &xml_file, - "Parse Unocode.org ANNOTATIONS file", - "ANNOTATIONS" - }, -+ { "xml-derived", 'X', 0, G_OPTION_ARG_STRING, &xml_derived_file, -+ "Parse Unocode.org derived ANNOTATIONS file", -+ "ANNOTATIONS" -+ }, -+ { "xml-ascii", 'A', 0, G_OPTION_ARG_STRING, &xml_ascii_file, -+ "Parse ASCII ANNOTATIONS file", -+ "ANNOTATIONS" -+ }, - { NULL } - }; - GOptionContext *context; -@@ -638,12 +1138,22 @@ main (int argc, char *argv[]) - } - g_option_context_free (context); - -+#ifdef HAVE_JSON_GLIB1 - if (json_file) - emojione_parse_json_file (json_file, &list); -+#endif -+ if (emoji_dir) -+ unicode_emoji_parse_dir (emoji_dir, &list); - if (xml_file) -- unicode_annotations_parse_xml_file (xml_file, &list); -+ unicode_annotations_parse_xml_file (xml_file, &list, FALSE); -+ if (xml_derived_file) -+ unicode_annotations_parse_xml_file (xml_derived_file, &list, TRUE); -+ if (xml_ascii_file) -+ unicode_annotations_parse_xml_file (xml_ascii_file, &list, FALSE); - if (list != NULL && output) - ibus_emoji_data_save (output, list); -+ if (list != NULL && output_category) -+ category_file_save (output_category, list); - if (list) - g_slist_free (list); - -diff --git a/src/ibusemoji.c b/src/ibusemoji.c -index 4be092a..d2e16c5 100644 ---- a/src/ibusemoji.c -+++ b/src/ibusemoji.c -@@ -29,7 +29,7 @@ - #include "ibusinternal.h" - - #define IBUS_EMOJI_DATA_MAGIC "IBusEmojiData" --#define IBUS_EMOJI_DATA_VERSION (3) -+#define IBUS_EMOJI_DATA_VERSION (4) - - enum { - PROP_0 = 0, -@@ -37,7 +37,6 @@ enum { - PROP_ANNOTATIONS, - PROP_DESCRIPTION, - PROP_CATEGORY, -- PROP_EMOJI_ALTERNATES - }; - - struct _IBusEmojiDataPrivate { -@@ -45,7 +44,6 @@ struct _IBusEmojiDataPrivate { - GSList *annotations; - gchar *description; - gchar *category; -- gchar *emoji_alternates; - }; - - #define IBUS_EMOJI_DATA_GET_PRIVATE(o) \ -@@ -108,7 +106,7 @@ ibus_emoji_data_class_init (IBusEmojiDataClass *class) - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - - /** -- * IBusEmojiData:annotations: -+ * IBusEmojiData:annotations: (transfer container) (element-type utf8): - * - * The emoji annotations - */ -@@ -117,7 +115,7 @@ ibus_emoji_data_class_init (IBusEmojiDataClass *class) - g_param_spec_pointer ("annotations", - "emoji annotations", - "The emoji annotation list", -- G_PARAM_READWRITE)); -+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - - /** - * IBusEmojiData:description: -@@ -130,7 +128,7 @@ ibus_emoji_data_class_init (IBusEmojiDataClass *class) - "emoji description", - "The emoji description", - "", -- G_PARAM_READWRITE)); -+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - - /** - * IBusEmojiData:category: -@@ -144,19 +142,6 @@ ibus_emoji_data_class_init (IBusEmojiDataClass *class) - "The emoji category", - "", - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); -- -- /** -- * IBusEmojiData:emoji_alternates: -- * -- * The emoji alternate characters -- */ -- g_object_class_install_property (gobject_class, -- PROP_EMOJI_ALTERNATES, -- g_param_spec_string ("emoji-alternates", -- "emoji alternate charasters", -- "The emoji alternate characters UTF-8", -- "", -- G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - } - - static void -@@ -209,10 +194,6 @@ ibus_emoji_data_set_property (IBusEmojiData *emoji, - g_assert (emoji->priv->category == NULL); - emoji->priv->category = g_value_dup_string (value); - break; -- case PROP_EMOJI_ALTERNATES: -- g_assert (emoji->priv->emoji_alternates == NULL); -- emoji->priv->emoji_alternates = g_value_dup_string (value); -- break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (emoji, prop_id, pspec); - } -@@ -240,9 +221,6 @@ ibus_emoji_data_get_property (IBusEmojiData *emoji, - case PROP_CATEGORY: - g_value_set_string (value, ibus_emoji_data_get_category (emoji)); - break; -- case PROP_EMOJI_ALTERNATES: -- g_value_set_string (value, ibus_emoji_data_get_emoji_alternates(emoji)); -- break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (emoji, prop_id, pspec); - } -@@ -270,8 +248,6 @@ ibus_emoji_data_serialize (IBusEmojiData *emoji, - } - g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->description)); - g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->category)); -- g_variant_builder_add (builder, "s", -- NOTNULL (emoji->priv->emoji_alternates)); - #undef NOTNULL - return TRUE; - } -@@ -303,10 +279,6 @@ ibus_emoji_data_deserialize (IBusEmojiData *emoji, - &emoji->priv->description); - ibus_g_variant_get_child_string (variant, retval++, - &emoji->priv->category); -- if (g_variant_n_children (variant) < retval + 1) -- return retval; -- ibus_g_variant_get_child_string (variant, retval++, -- &emoji->priv->emoji_alternates); - return retval; - } - -@@ -325,7 +297,6 @@ ibus_emoji_data_copy (IBusEmojiData *dest, - NULL); - dest->priv->description = g_strdup (src->priv->description); - dest->priv->category = g_strdup (src->priv->category); -- dest->priv->emoji_alternates = g_strdup (src->priv->emoji_alternates); - return TRUE; - } - -@@ -345,7 +316,6 @@ ibus_emoji_data_new (const gchar *first_property_name, ...) - g_assert (emoji->priv->emoji != NULL); - g_assert (emoji->priv->description != NULL); - g_assert (emoji->priv->category != NULL); -- g_assert (emoji->priv->emoji_alternates != NULL); - return emoji; - } - -@@ -402,15 +372,6 @@ ibus_emoji_data_get_category (IBusEmojiData *emoji) - return emoji->priv->category; - } - --const gchar * --ibus_emoji_data_get_emoji_alternates (IBusEmojiData *emoji) --{ -- g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL); -- -- return emoji->priv->emoji_alternates; --} -- -- - static void - variant_foreach_add_emoji (IBusEmojiData *emoji, - GVariantBuilder *builder) -diff --git a/src/ibusemoji.h b/src/ibusemoji.h -index 233cadd..eb24fdd 100644 ---- a/src/ibusemoji.h -+++ b/src/ibusemoji.h -@@ -156,20 +156,6 @@ const gchar * ibus_emoji_data_get_category (IBusEmojiData *emoji); - - - /** -- * ibus_emoji_data_get_emoji_alternates: -- * @emoji : An #IBusEmojiData -- * -- * Gets the emoji alternate characters in #IBusEmojiData. It should not be -- * freed. The alternates are defined in "unicode_alt" in EmojiOne json. -- * -- * Returns: emoji alternates property in #IBusEmojiData -- * -- */ --const gchar * ibus_emoji_data_get_emoji_alternates -- (IBusEmojiData *emoji); -- -- --/** - * ibus_emoji_dict_save: - * @path: A path of the saved dictionary file. - * @dict: (element-type utf8 gpointer) (transfer none): An Emoji dictionary -diff --git a/src/ibusemojigen.h b/src/ibusemojigen.h -new file mode 100644 -index 0000000..74a6015 ---- /dev/null -+++ b/src/ibusemojigen.h -@@ -0,0 +1,39 @@ -+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */ -+/* vim:set et sts=4: */ -+/* ibus - The Input Bus -+ * Copyright (C) 2016-2017 Takao Fujiwara -+ * Copyright (C) 2016 Red Hat, Inc. -+ * -+ * This library is free software; you can redistribute it and/or -+ * modify it under the terms of the GNU Lesser General Public -+ * License as published by the Free Software Foundation; either -+ * version 2.1 of the License, or (at your option) any later version. -+ * -+ * This library 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 -+ * Lesser General Public License for more details. -+ * -+ * You should have received a copy of the GNU Lesser General Public -+ * License along with this library; if not, write to the Free Software -+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -+ * USA -+ */ -+ -+ -+/* This file is generated by emoji-parser.c. */ -+include -+ -+#ifndef __IBUS_EMOJI_GEN_H_ -+#define __IBUS_EMOJI_GEN_H_ -+const static char *unicode_emoji_categories[] = { -+ N_("Activities"), -+ N_("Animals & Nature"), -+ N_("Flags"), -+ N_("Food & Drink"), -+ N_("Objects"), -+ N_("Smileys & People"), -+ N_("Symbols"), -+ N_("Travel & Places"), -+}; -+#endif -diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am -index d2ae0d6..c557474 100644 ---- a/ui/gtk3/Makefile.am -+++ b/ui/gtk3/Makefile.am -@@ -82,7 +82,7 @@ AM_VALAFLAGS = \ - $(NULL) - - MAINTAINERCLEANFILES = --CONFIG_CLEAN_FILES = -+DISTCLEANFILES = - noinst_DATA = - - if ENABLE_LIBNOTIFY -@@ -250,8 +250,7 @@ vapidir = $(datadir)/vala/vapi - noinst_DATA += $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps) - - MAINTAINERCLEANFILES += $(VAPIGEN_VAPIS) --# for make distclean --CONFIG_CLEAN_FILES += $(VAPIGEN_VAPIS) -+DISTCLEANFILES += $(VAPIGEN_VAPIS) - EXTRA_DIST += $(VAPIGEN_VAPIS) - - endif -diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala -index 76dca6c..8f55bd4 100644 ---- a/ui/gtk3/emojier.vala -+++ b/ui/gtk3/emojier.vala -@@ -167,6 +167,7 @@ class IBusEmojier : Gtk.Window { - } - - private const uint EMOJI_GRID_PAGE = 10; -+ private const string EMOJI_CATEGORY_OTHERS = N_("Others"); - - // Set the actual default values in the constructor - // because these fields are used for class_init() and static functions, -@@ -372,8 +373,7 @@ class IBusEmojier : Gtk.Window { - - - private static void update_annotation_to_emojis_dict(IBus.EmojiData data) { -- string emoji = (data.get_emoji_alternates() != "") ? -- data.get_emoji_alternates() : data.get_emoji(); -+ string emoji = data.get_emoji(); - unowned GLib.SList annotations = data.get_annotations(); - foreach (string annotation in annotations) { - bool has_emoji = false; -@@ -434,23 +434,70 @@ class IBusEmojier : Gtk.Window { - } - - -+ private static string utf8_entity(string str) { -+ GLib.StringBuilder buff = new GLib.StringBuilder(); -+ int length = str.char_count(); -+ for (int i = 0; i < length; i++) { -+ unichar ch = str.get_char(0); -+ switch(ch) { -+ case '<': -+ buff.append("<"); -+ break; -+ case '>': -+ buff.append(">"); -+ break; -+ case '&': -+ buff.append("&"); -+ break; -+ default: -+ buff.append_unichar(ch); -+ break; -+ } -+ str = str.next_char(); -+ } -+ return buff.str; -+ } -+ -+ -+ private static void -+ update_annotations_with_description (IBus.EmojiData data, -+ string description) { -+ unowned GLib.SList annotations = data.get_annotations(); -+ bool update_annotations = false; -+ string former = null; -+ string later = null; -+ int index = description.index_of(": "); -+ if (index > 0) { -+ former = description.substring(0, index); -+ if (annotations.find_custom(former, GLib.strcmp) == null) { -+ annotations.append(former); -+ update_annotations = true; -+ } -+ later = description.substring(index + 2); -+ } else { -+ later = description.dup(); -+ } -+ var words = later.split(" "); -+ // If the description has less than 3 words, add it to annotations -+ // FIXME: How to cast GLib.CompareFunc to strcmp? -+ if (words.length < 3 && -+ annotations.find_custom( -+ later, -+ GLib.strcmp) == null) { -+ annotations.append(later); -+ update_annotations = true; -+ } -+ if (update_annotations) -+ data.set_annotations(annotations.copy_deep(GLib.strdup)); -+ } -+ -+ - private static void update_emoji_to_data_dict(IBus.EmojiData data, - string lang) { -- string emoji = (data.get_emoji_alternates() != "") ? -- data.get_emoji_alternates() : data.get_emoji(); -+ string emoji = data.get_emoji(); - if (lang == "en") { - string description = utf8_down(data.get_description()); -- unowned GLib.SList annotations = data.get_annotations(); -- var words = description.split(" "); -- // If the description has less than 3 words, add it to annotations -- // FIXME: How to cast GLib.CompareFunc to strcmp? -- if (words.length < 3 && -- annotations.find_custom( -- description, -- GLib.strcmp) == null) { -- annotations.append(description); -- data.set_annotations(annotations.copy_deep(GLib.strdup)); -- } -+ update_annotations_with_description (data, description); - m_emoji_to_data_dict.replace(emoji, data); - } else { - unowned IBus.EmojiData? en_data = -@@ -462,16 +509,8 @@ class IBusEmojier : Gtk.Window { - string trans_description = data.get_description(); - en_data.set_description(trans_description); - trans_description = utf8_down(trans_description); -+ update_annotations_with_description (data, trans_description); - unowned GLib.SList annotations = data.get_annotations(); -- var words = trans_description.split(" "); -- // If the description has less than 3 words, add it to annotations -- // FIXME: How to cast GLib.CompareFunc to strcmp? -- if (words.length < 3 && -- annotations.find_custom( -- trans_description, -- GLib.strcmp) == null) { -- annotations.append(trans_description); -- } - unowned GLib.SList en_annotations - = en_data.get_annotations(); - foreach (string annotation in en_annotations) { -@@ -489,10 +528,11 @@ class IBusEmojier : Gtk.Window { - - private static void update_category_to_emojis_dict(IBus.EmojiData data, - string lang) { -- string emoji = (data.get_emoji_alternates() != "") ? -- data.get_emoji_alternates() : data.get_emoji(); -+ string emoji = data.get_emoji(); - string category = data.get_category(); -- if (lang == "en" && category != "") { -+ if (category == "") -+ category = EMOJI_CATEGORY_OTHERS; -+ if (lang == "en") { - bool has_emoji = false; - unowned GLib.SList hits = - m_category_to_emojis_dict.lookup(category); -@@ -562,9 +602,17 @@ class IBusEmojier : Gtk.Window { - m_category_to_emojis_dict.get_keys(); - // FIXME: How to cast GLib.CompareFunc to strcmp? - categories.sort((a, b) => { -+ if (a == EMOJI_CATEGORY_OTHERS && b != EMOJI_CATEGORY_OTHERS) -+ return 1; -+ else if (a != EMOJI_CATEGORY_OTHERS && b == EMOJI_CATEGORY_OTHERS) -+ return -1; - return GLib.strcmp(_(a), _(b)); - }); - foreach (unowned string category in categories) { -+ // "Others" category includes next unicode chars and fonts do not support -+ // the base and varints yet. -+ if (category == EMOJI_CATEGORY_OTHERS) -+ continue; - EBoxRow row = new EBoxRow(category); - string locale_category = _(category); - EPaddedLabelBox widget = -@@ -718,7 +766,7 @@ class IBusEmojier : Gtk.Window { - uint page_end_pos = uint.min(page_start_pos + page_size, ncandidates); - if (m_backward != null) { - string backward_desc = -- "%s (%u / %u)".printf(m_backward, cursor, ncandidates - 1); -+ "%s (%u / %u)".printf(_(m_backward), cursor, ncandidates - 1); - EPaddedLabelBox label = - new EPaddedLabelBox(backward_desc, - Gtk.Align.CENTER, -@@ -746,7 +794,7 @@ class IBusEmojier : Gtk.Window { - int font_size = m_emoji_font_size - 2; - string emoji_font = "%s %d".printf(font_family, font_size); - string markup = "%s". -- printf(emoji_font, candidate.get_text()); -+ printf(emoji_font, utf8_entity(candidate.get_text())); - label.set_markup(markup); - } - label.set_halign(Gtk.Align.FILL); --- -2.9.3 - -From 0836c3ffd6764969dfaa9953f52a24c1cbf2c983 Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Sat, 6 May 2017 00:22:50 +0900 -Subject: [PATCH] Support to match emoji annotations with partial string - -Now ibus-setup has a new tab of "Emoji" and it provides -the options to choose a condtion and length to match -emoji annotations partically. - -R=alexepico@gmail.com, penghuang@google.com - -Review URL: https://codereview.appspot.com/319700043 ---- - data/ibus.schemas.in | 39 ++++ - setup/main.py | 121 ++++++++++-- - setup/setup.ui | 491 ++++++++++++++++++++++++++++++------------------ - ui/gtk3/emojier.vala | 64 ++++++- - ui/gtk3/emojierapp.vala | 52 ++++- - ui/gtk3/panel.vala | 22 +++ - 6 files changed, 582 insertions(+), 207 deletions(-) - -diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in -index 096dd71..0647004 100644 ---- a/data/ibus.schemas.in -+++ b/data/ibus.schemas.in -@@ -404,6 +404,45 @@ - - - -+ /schemas/desktop/ibus/panel/emoji/has-partial-match -+ /desktop/ibus/panel/emoji/has-partial-match -+ ibus -+ bool -+ false -+ -+ Whether emoji annotaions can be match partially or not -+ Whether emoji annotations can be matched with a partial -+ string instead of the exact match or not. -+ -+ -+ -+ /schemas/desktop/ibus/panel/emoji/partial-match-length -+ /desktop/ibus/panel/emoji/partial-match-length -+ ibus -+ int -+ 3 -+ -+ Match emoji annotaions with the specified length -+ Match emoji annotations partially with more than -+ the specified number of characters instead of -+ the exact match. -+ -+ -+ -+ /schemas/desktop/ibus/panel/emoji/partial-match-condition -+ /desktop/ibus/panel/emoji/partial-match-condition -+ ibus -+ int -+ 0 -+ -+ Choose a condition to match emoji annotations partially -+ Choose one of the following conditions to match emoji -+ annotations partially: -+ 0 == Prefix match, 1 == Suffix match, 2 == Containing match -+ -+ -+ -+ - /schemas/desktop/ibus/general/embed_preedit_text - /desktop/ibus/general/embed_preedit_text - ibus -diff --git a/setup/main.py b/setup/main.py -index 7839cea..f0eee99 100644 ---- a/setup/main.py -+++ b/setup/main.py -@@ -187,19 +187,6 @@ class Setup(object): - self.__fontbutton_custom_font, - 'sensitive', - Gio.SettingsBindFlags.GET) -- self.__fontbutton_emoji_font = self.__builder.get_object( -- 'fontbutton_emoji_font') -- self.__fontbutton_emoji_font.set_preview_text('🙂🍎🚃💓📧⚽🐳'); -- self.__settings_emoji.bind('font', -- self.__fontbutton_emoji_font, -- 'font-name', -- Gio.SettingsBindFlags.DEFAULT) -- self.__button_emoji_lang = self.__builder.get_object( -- 'button_emoji_lang') -- self.__settings_emoji.bind('lang', -- self.__button_emoji_lang, -- 'lang', -- Gio.SettingsBindFlags.DEFAULT) - - # show icon on system tray - self.__checkbutton_show_icon_on_systray = self.__builder.get_object( -@@ -287,6 +274,113 @@ class Setup(object): - self.__treeview.connect("notify::active-engine", self.__treeview_notify_cb) - self.__treeview.connect("notify::engines", self.__treeview_notify_cb) - -+ def __init_emoji(self): -+ self.__fontbutton_emoji_font = self.__builder.get_object( -+ 'fontbutton_emoji_font') -+ self.__fontbutton_emoji_font.set_preview_text('🙂🍎🚃💓📧⚽🐳'); -+ self.__settings_emoji.bind('font', -+ self.__fontbutton_emoji_font, -+ 'font-name', -+ Gio.SettingsBindFlags.DEFAULT) -+ self.__button_emoji_lang = self.__builder.get_object( -+ 'button_emoji_lang') -+ self.__settings_emoji.bind('lang', -+ self.__button_emoji_lang, -+ 'lang', -+ Gio.SettingsBindFlags.DEFAULT) -+ self.__checkbutton_emoji_partial_match = self.__builder.get_object( -+ 'checkbutton_emoji_partial_match') -+ checkbutton_label = self.__checkbutton_emoji_partial_match.get_child() -+ if type(checkbutton_label) == Gtk.Label: -+ checkbutton_label.set_property('wrap', True) -+ checkbutton_label.set_property('max-width-chars', 74) -+ self.__spinbutton_emoji_partial_match = self.__builder.get_object( -+ 'spinbutton_emoji_partial_match') -+ self.__settings_emoji.bind('has-partial-match', -+ self.__checkbutton_emoji_partial_match, -+ 'active', -+ Gio.SettingsBindFlags.DEFAULT) -+ self.__settings_emoji.bind('has-partial-match', -+ self.__spinbutton_emoji_partial_match, -+ 'sensitive', -+ Gio.SettingsBindFlags.GET) -+ -+ def adjustment_value_changed_cb(obj): -+ key = 'partial-match-length' -+ value = int(adjustment.get_value()) -+ if value == self.__settings_emoji.get_int(key): -+ return -+ self.__settings_emoji.set_int(key, value) -+ def settings_emoji_partial_match_length_cb(settings, key): -+ value = self.__settings_emoji.get_int(key) -+ old_value = int(self.__spinbutton_emoji_partial_match.get_value()) -+ if value == old_value: -+ return -+ self.__spinbutton_emoji_partial_match.set_value(value) -+ settings_emoji_partial_match_length_cb(None, 'partial-match-length') -+ adjustment = self.__spinbutton_emoji_partial_match.get_adjustment() -+ adjustment.connect('value-changed', adjustment_value_changed_cb) -+ self.__settings_emoji.connect('changed::partial-match-length', -+ settings_emoji_partial_match_length_cb) -+ -+ self.__hbox_emoji_partial_match = self.__builder.get_object( -+ 'hbox_emoji_partial_match') -+ self.__settings_emoji.bind('has-partial-match', -+ self.__hbox_emoji_partial_match, -+ 'sensitive', -+ Gio.SettingsBindFlags.GET) -+ self.__radiobutton_emoji_prefix_match = self.__builder.get_object( -+ 'radiobutton_emoji_prefix_match') -+ self.__radiobutton_emoji_suffix_match = self.__builder.get_object( -+ 'radiobutton_emoji_suffix_match') -+ self.__radiobutton_emoji_contain_match = self.__builder.get_object( -+ 'radiobutton_emoji_contain_match') -+ -+ def radiobuton_emoji_partial_match_cb(obj): -+ key = 'partial-match-condition' -+ condition = 0 -+ if not obj.get_active(): -+ return -+ if obj == self.__radiobutton_emoji_prefix_match: -+ condition = 0 -+ elif obj == self.__radiobutton_emoji_suffix_match: -+ condition = 1 -+ elif obj == self.__radiobutton_emoji_contain_match: -+ condition = 2 -+ else: -+ print('Wrong emoji partial match object') -+ return -+ self.__settings_emoji.set_int(key, condition) -+ def settings_emoji_partial_match_condition_cb(settings, key): -+ value = self.__settings_emoji.get_int(key) -+ obj = None -+ if value == 0: -+ obj = self.__radiobutton_emoji_prefix_match -+ elif value == 1: -+ obj = self.__radiobutton_emoji_suffix_match -+ elif value == 2: -+ obj = self.__radiobutton_emoji_contain_match -+ else: -+ print('Wrong emoji partial match condition') -+ return -+ if obj.get_active(): -+ return -+ obj.set_active(True) -+ -+ settings_emoji_partial_match_condition_cb(None, -+ 'partial-match-condition') -+ self.__radiobutton_emoji_prefix_match.connect( -+ 'toggled', -+ radiobuton_emoji_partial_match_cb) -+ self.__radiobutton_emoji_suffix_match.connect( -+ 'toggled', -+ radiobuton_emoji_partial_match_cb) -+ self.__radiobutton_emoji_contain_match.connect( -+ 'toggled', -+ radiobuton_emoji_partial_match_cb) -+ self.__settings_emoji.connect('changed::partial-match-condition', -+ settings_emoji_partial_match_condition_cb) -+ - def __init_ui(self): - # add icon search path - self.__window = self.__builder.get_object("window_preferences") -@@ -306,6 +400,7 @@ class Setup(object): - self.__init_hotkeys() - self.__init_panel() - self.__init_general() -+ self.__init_emoji() - - def __gdk_window_set_cb(self, object, pspec): - window = object.get_window() -diff --git a/setup/setup.ui b/setup/setup.ui -index 54ef916..322f514 100644 ---- a/setup/setup.ui -+++ b/setup/setup.ui -@@ -55,6 +55,14 @@ - - - -+ -+ 3.0 -+ 1.0 -+ 255.0 -+ 1.0 -+ 10.0 -+ 10.0 -+ - - False - 12 -@@ -109,8 +117,6 @@ - Next input method: - - -- 3 -- 4 - GTK_FILL - GTK_FILL - -@@ -125,69 +131,13 @@ - Previous input method: - - -- 4 -- 5 -- GTK_FILL -- GTK_FILL -- -- -- -- -- True -- False -- The shortcut keys for showing emoji dialog -- start -- Emoji choice: -- -- -- 5 -- 6 -+ 1 -+ 2 - GTK_FILL - GTK_FILL - - - -- -- horizontal -- True -- False -- 6 -- -- -- True -- True -- False -- -- -- True -- True -- 0 -- -- -- -- -- ... -- False -- True -- True -- False -- False -- True -- -- -- False -- True -- 1 -- -- -- -- -- 1 -- 2 -- GTK_FILL -- -- -- - - horizontal - True -@@ -225,8 +175,6 @@ - - 1 - 2 -- 3 -- 4 - GTK_FILL - - -@@ -270,51 +218,8 @@ - - 1 - 2 -- 4 -- 5 -- GTK_FILL -- -- -- -- -- horizontal -- True -- False -- 6 -- -- -- True -- True -- False -- -- -- True -- True -- 0 -- -- -- -- -- ... -- False -- True -- True -- False -- False -- True -- -- -- False -- True -- 1 -- -- -- -- -- 1 -- 2 -- 5 -- 6 -+ 1 -+ 2 - GTK_FILL - - -@@ -327,6 +232,8 @@ - Enable or disable: - - -+ 2 -+ 3 - GTK_FILL - GTK_FILL - -@@ -339,8 +246,8 @@ - Enable: - - -- 1 -- 2 -+ 3 -+ 4 - GTK_FILL - GTK_FILL - -@@ -383,8 +290,8 @@ - - 1 - 2 -- 1 -- 2 -+ 3 -+ 4 - GTK_FILL - - -@@ -396,8 +303,8 @@ - Disable: - - -- 2 -- 3 -+ 4 -+ 5 - GTK_FILL - GTK_FILL - -@@ -440,8 +347,8 @@ - - 1 - 2 -- 2 -- 3 -+ 4 -+ 5 - GTK_FILL - - -@@ -675,68 +582,6 @@ - GTK_FILL - - -- -- -- True -- False -- Set a font of emoji candidates on the emoji dialog -- start -- Emoji font: -- right -- -- -- 7 -- 8 -- GTK_FILL -- GTK_FILL -- -- -- -- -- True -- True -- True -- False -- -- -- 1 -- 2 -- 7 -- 8 -- GTK_FILL -- -- -- -- -- True -- False -- Set a language of emoji annotations on the emoji dialog -- start -- Emoji annotation language: -- right -- -- -- 8 -- 9 -- GTK_FILL -- GTK_FILL -- -- -- -- -- True -- True -- True -- False -- -- -- 1 -- 2 -- 8 -- 9 -- GTK_FILL -- -- - - - -@@ -1027,6 +872,292 @@ - - - -+ -+ vertical -+ True -+ False -+ 18 -+ 12 -+ 12 -+ 12 -+ 12 -+ -+ -+ True -+ False -+ 0 -+ none -+ -+ -+ True -+ False -+ 5 -+ 2 -+ 12 -+ 6 -+ 6 -+ 12 -+ -+ -+ True -+ False -+ The shortcut keys for showing emoji dialog -+ start -+ Emoji choice: -+ -+ -+ GTK_FILL -+ GTK_FILL -+ -+ -+ -+ -+ horizontal -+ True -+ False -+ 6 -+ -+ -+ True -+ True -+ False -+ -+ -+ True -+ True -+ 0 -+ -+ -+ -+ -+ ... -+ False -+ True -+ True -+ False -+ False -+ True -+ -+ -+ False -+ True -+ 1 -+ -+ -+ -+ -+ 1 -+ 2 -+ GTK_FILL -+ -+ -+ -+ -+ -+ -+ True -+ False -+ <b>Keyboard Shortcuts</b> -+ True -+ -+ -+ -+ -+ False -+ True -+ 0 -+ -+ -+ -+ -+ True -+ False -+ 0 -+ none -+ -+ -+ True -+ False -+ 12 -+ 6 -+ 6 -+ 12 -+ false -+ -+ -+ True -+ False -+ Set a font of emoji candidates on the emoji dialog -+ start -+ Emoji font: -+ right -+ -+ -+ 0 -+ -+ -+ -+ -+ True -+ True -+ True -+ False -+ -+ -+ 1 -+ -+ -+ -+ -+ -+ True -+ False -+ Set a language of emoji annotations on the emoji dialog -+ start -+ Emoji annotation language: -+ right -+ -+ -+ 0 -+ 1 -+ -+ -+ -+ -+ True -+ True -+ True -+ False -+ -+ -+ 1 -+ 1 -+ -+ -+ -+ -+ Match emoji annotations partially with the following condition and more than the number of characters: -+ False -+ True -+ True -+ False -+ If emoji annotations can be matched with a partial string instead of the exact match -+ False -+ start -+ True -+ -+ -+ 0 -+ 2 -+ 2 -+ -+ -+ -+ -+ adjustment_emoji_partial_match -+ 3.0 -+ True -+ True -+ False -+ start -+ -+ -+ 0 -+ 2 -+ 3 -+ -+ -+ -+ -+ horizontal -+ True -+ False -+ 6 -+ -+ -+ Prefix match -+ True -+ True -+ False -+ start -+ -+ -+ False -+ True -+ 0 -+ -+ -+ -+ -+ Suffix match -+ radiobutton_emoji_prefix_match -+ True -+ True -+ False -+ start -+ -+ -+ False -+ True -+ 1 -+ -+ -+ -+ -+ Containing match -+ radiobutton_emoji_prefix_match -+ True -+ True -+ False -+ start -+ -+ -+ False -+ True -+ 2 -+ -+ -+ -+ -+ 0 -+ 2 -+ 4 -+ -+ -+ -+ -+ -+ -+ True -+ False -+ <b>Font and Style</b> -+ True -+ -+ -+ -+ -+ False -+ True -+ 1 -+ -+ -+ -+ -+ 2 -+ -+ -+ -+ -+ True -+ False -+ Emoji -+ -+ -+ 2 -+ False -+ -+ -+ - - vertical - True -@@ -1134,7 +1265,7 @@ - - - -- 2 -+ 3 - - - -@@ -1214,7 +1345,7 @@ Homepage: https://github.com/ibus/ibus/wiki - - - -- 3 -+ 4 - - - -@@ -1224,7 +1355,7 @@ Homepage: https://github.com/ibus/ibus/wiki - About - - -- 3 -+ 4 - False - - -diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala -index 8f55bd4..76d5fed 100644 ---- a/ui/gtk3/emojier.vala -+++ b/ui/gtk3/emojier.vala -@@ -177,6 +177,9 @@ class IBusEmojier : Gtk.Window { - private static int m_emoji_font_size; - private static string[] m_favorites; - private static int m_emoji_max_seq_len; -+ private static bool m_has_partial_match; -+ private static uint m_partial_match_length; -+ private static uint m_partial_match_condition; - private static GLib.HashTable? - m_annotation_to_emojis_dict; - private static GLib.HashTable? -@@ -738,16 +741,53 @@ class IBusEmojier : Gtk.Window { - } - // Call check_unicode_point() to get m_unicode_point - check_unicode_point(); -- unowned GLib.SList? emojis = -- m_annotation_to_emojis_dict.lookup(annotation); -- if (emojis == null && m_unicode_point == null) { -+ GLib.SList? total_emojis = null; -+ unowned GLib.SList? sub_emojis = null; -+ int length = annotation.length; -+ if (m_has_partial_match && length >= m_partial_match_length) { -+ foreach (unowned string key in -+ m_annotation_to_emojis_dict.get_keys()) { -+ if (key.length < length) -+ continue; -+ bool matched = false; -+ switch(m_partial_match_condition) { -+ case 0: -+ if (key.has_prefix(annotation)) -+ matched = true; -+ break; -+ case 1: -+ if (key.has_suffix(annotation)) -+ matched = true; -+ break; -+ case 2: -+ if (key.str(annotation) != null) -+ matched = true; -+ break; -+ default: -+ break; -+ } -+ if (!matched) -+ continue; -+ sub_emojis = m_annotation_to_emojis_dict.lookup(key); -+ foreach (unowned string emoji in sub_emojis) { -+ if (total_emojis.find_custom(emoji, GLib.strcmp) == null) { -+ total_emojis.append(emoji); -+ } -+ } -+ } -+ } else { -+ sub_emojis = m_annotation_to_emojis_dict.lookup(annotation); -+ foreach (unowned string emoji in sub_emojis) -+ total_emojis.append(emoji); -+ } -+ if (total_emojis == null && m_unicode_point == null) { - hide_candidate_panel(); - return; - } - m_lookup_table.clear(); - // Call check_unicode_point() to update m_lookup_table - check_unicode_point(); -- foreach (unowned string emoji in emojis) { -+ foreach (unowned string emoji in total_emojis) { - IBus.Text text = new IBus.Text.from_string(emoji); - m_lookup_table.append_candidate(text); - } -@@ -1331,6 +1371,22 @@ class IBusEmojier : Gtk.Window { - } - - -+ public static void set_partial_match(bool has_partial_match) { -+ m_has_partial_match = has_partial_match; -+ } -+ -+ public static void set_partial_match_length(int length) { -+ if (length < 1) -+ return; -+ m_partial_match_length = length; -+ } -+ -+ public static void set_partial_match_condition(int condition) { -+ if (condition < 0) -+ return; -+ m_partial_match_condition = condition; -+ } -+ - public static void set_favorites(string[]? unowned_favorites) { - m_favorites = {}; - foreach (string favorite in unowned_favorites) { -diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala -index cbdd8ba..dbdbc27 100644 ---- a/ui/gtk3/emojierapp.vala -+++ b/ui/gtk3/emojierapp.vala -@@ -22,6 +22,9 @@ - - string emoji_font = null; - string annotation_lang = null; -+bool partial_match = false; -+int partial_match_length = -1; -+int partial_match_condition = -1; - - public class EmojiApplication : Application { - private IBusEmojier? m_emojier; -@@ -65,17 +68,28 @@ public class EmojiApplication : Application { - - const OptionEntry[] options = { - { "font", 0, 0, OptionArg.STRING, out emoji_font, -- /* TRANSLATORS: "FONT" should be capital and translatable. -- * It's used for an argument command --font=FONT -- */ -- N_("\"FONT\" for emoji chracters on emoji dialog"), -- N_("FONT") }, -+ /* TRANSLATORS: "FONT" should be capital and translatable. -+ * It's used for an argument command --font=FONT -+ */ -+ N_("\"FONT\" for emoji chracters on emoji dialog"), -+ N_("FONT") }, - { "lang", 0, 0, OptionArg.STRING, out annotation_lang, -- /* TRANSLATORS: "LANG" should be capital and translatable. -- * It's used for an argument command --lang=LANG -- */ -- N_("\"LANG\" for annotations on emoji dialog. E.g. \"en\""), -- N_("LANG") }, -+ /* TRANSLATORS: "LANG" should be capital and translatable. -+ * It's used for an argument command --lang=LANG -+ */ -+ N_("\"LANG\" for annotations on emoji dialog. E.g. \"en\""), -+ N_("LANG") }, -+ { "partial-match", 0, 0, OptionArg.NONE, out partial_match, -+ N_("Emoji annotaions can be match partially"), -+ null }, -+ { "partial-match-length", 0, 0, OptionArg.INT, -+ out partial_match_length, -+ N_("Match with the length of the specified integer"), -+ null }, -+ { "partial-match-condition", 0, 0, OptionArg.INT, -+ out partial_match_condition, -+ N_("Match with the condition of the specified integer"), -+ null }, - { null } - }; - -@@ -111,6 +125,24 @@ public class EmojiApplication : Application { - if (annotation_lang == null) - annotation_lang = m_settings_emoji.get_string("lang"); - IBusEmojier.set_annotation_lang(annotation_lang); -+ IBusEmojier.set_partial_match(partial_match); -+ if (partial_match_length > 0) { -+ IBusEmojier.set_partial_match_length(partial_match_length); -+ } else { -+ IBusEmojier.set_partial_match_length( -+ m_settings_emoji.get_int("partial-match-length")); -+ } -+ if (partial_match_condition > 2) { -+ warning("Need condition between 0 and 2."); -+ IBusEmojier.set_partial_match_condition( -+ m_settings_emoji.get_int("partial-match-condition")); -+ } -+ else if (partial_match_condition >= 0) { -+ IBusEmojier.set_partial_match_condition(partial_match_condition); -+ } else { -+ IBusEmojier.set_partial_match_condition( -+ m_settings_emoji.get_int("partial-match-condition")); -+ } - - if (emoji_font != null) - IBusEmojier.set_emoji_font(emoji_font); -diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala -index 745489b..5f0009e 100644 ---- a/ui/gtk3/panel.vala -+++ b/ui/gtk3/panel.vala -@@ -248,6 +248,18 @@ class Panel : IBus.PanelService { - m_settings_emoji.changed["lang"].connect((key) => { - set_emoji_lang(); - }); -+ -+ m_settings_emoji.changed["has-partial-match"].connect((key) => { -+ set_emoji_partial_match(); -+ }); -+ -+ m_settings_emoji.changed["partial-match-length"].connect((key) => { -+ set_emoji_partial_match(); -+ }); -+ -+ m_settings_emoji.changed["partial-match-condition"].connect((key) => { -+ set_emoji_partial_match(); -+ }); - } - - #if INDICATOR -@@ -782,6 +794,15 @@ class Panel : IBus.PanelService { - }); - } - -+ private void set_emoji_partial_match() { -+ IBusEmojier.set_partial_match( -+ m_settings_emoji.get_boolean("has-partial-match")); -+ IBusEmojier.set_partial_match_length( -+ m_settings_emoji.get_int("partial-match-length")); -+ IBusEmojier.set_partial_match_condition( -+ m_settings_emoji.get_int("partial-match-condition")); -+ } -+ - private int compare_versions(string version1, string version2) { - string[] version1_list = version1.split("."); - string[] version2_list = version2.split("."); -@@ -897,6 +918,7 @@ class Panel : IBus.PanelService { - set_property_icon_delay_time(); - set_emoji_favorites(); - set_emoji_lang(); -+ set_emoji_partial_match(); - } - - private void engine_contexts_insert(IBus.EngineDesc engine) { --- -2.9.3 - -From edcb4a0259d2833904e7d1aac7c74e1aa49cea5b Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Mon, 8 May 2017 12:25:54 +0900 -Subject: [PATCH] ui/gtk3: Hide emoji variants by default - -The emoji variants, i.e. skin colors and additional items, would not -be used in most cases and it is nice to hide it by default. -Now added GtkMenu and changed GtkWindow to GtkApplicationWindow -for the menu. - -R=penghuang@google.com - -Review URL: https://codereview.appspot.com/322970043 ---- - ui/gtk3/emojier.vala | 299 ++++++++++++++++++++++++++++++++++++--------------- - 1 file changed, 211 insertions(+), 88 deletions(-) - -diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala -index 76d5fed..db7520a 100644 ---- a/ui/gtk3/emojier.vala -+++ b/ui/gtk3/emojier.vala -@@ -20,7 +20,7 @@ - * USA - */ - --class IBusEmojier : Gtk.Window { -+class IBusEmojier : Gtk.ApplicationWindow { - private class EEntry : Gtk.SearchEntry { - public EEntry() { - GLib.Object( -@@ -97,6 +97,15 @@ class IBusEmojier : Gtk.Window { - set_label(text); - } - } -+ private class EGoldLabel : Gtk.Label { -+ public EGoldLabel(string text) { -+ GLib.Object( -+ name : "IBusEmojierGoldLabel" -+ ); -+ if (text != "") -+ set_label(text); -+ } -+ } - private class EPaddedLabel : Gtk.Label { - public EPaddedLabel(string text, - Gtk.Align align) { -@@ -150,11 +159,21 @@ class IBusEmojier : Gtk.Window { - var vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0); - set_custom_title(vbox); - var label = new Gtk.Label(title); -- label.get_style_context().add_class("title"); -+ label.get_style_context().add_class(Gtk.STYLE_CLASS_TITLE); - vbox.pack_start(label, true, false, 0); - m_lang_label = new Gtk.Label(null); -- m_lang_label.get_style_context().add_class("subtitle"); -+ m_lang_label.get_style_context().add_class( -+ Gtk.STYLE_CLASS_SUBTITLE); - vbox.pack_start(m_lang_label, true, false, 0); -+ -+ var menu = new GLib.Menu(); -+ menu.append(_("Show emoji variants"), "win.variant"); -+ var menu_button = new Gtk.MenuButton(); -+ menu_button.set_direction(Gtk.ArrowType.NONE); -+ menu_button.set_valign(Gtk.Align.CENTER); -+ menu_button.set_menu_model(menu); -+ menu_button.set_tooltip_text(_("Menu")); -+ pack_end(menu_button); - } - public void set_lang_label(string str) { - m_lang_label.set_text(str); -@@ -167,7 +186,13 @@ class IBusEmojier : Gtk.Window { - } - - private const uint EMOJI_GRID_PAGE = 10; -+ private const string EMOJI_CATEGORY_FAVORITES = N_("Favorites"); - private const string EMOJI_CATEGORY_OTHERS = N_("Others"); -+ private const unichar[] EMOJI_VARIANT_LIST = { -+ 0x1f3fb, 0x1f3fc, 0x1f3fd, 0x1f3fe, 0x1f3ff, 0x200d }; -+ private const GLib.ActionEntry[] m_action_entries = { -+ { "variant", check_action_variant_cb, null, "false", null } -+ }; - - // Set the actual default values in the constructor - // because these fields are used for class_init() and static functions, -@@ -180,18 +205,22 @@ class IBusEmojier : Gtk.Window { - private static bool m_has_partial_match; - private static uint m_partial_match_length; - private static uint m_partial_match_condition; -- private static GLib.HashTable? -+ private static bool m_show_emoji_variant = false; -+ private static GLib.HashTable>? - m_annotation_to_emojis_dict; - private static GLib.HashTable? - m_emoji_to_data_dict; -- private static GLib.HashTable? -+ private static GLib.HashTable>? - m_category_to_emojis_dict; -+ private static GLib.HashTable>? -+ m_emoji_to_emoji_variants_dict; - - private ThemedRGBA m_rgba; - private Gtk.Box m_vbox; - private ETitleLabelBox m_title; - private EEntry m_entry; - private string? m_backward; -+ private int m_backward_index = -1; - private EScrolledWindow? m_scrolled_window = null; - private EListBox m_list_box; - private bool m_is_running = false; -@@ -200,7 +229,7 @@ class IBusEmojier : Gtk.Window { - private string? m_result; - private string? m_unicode_point = null; - private bool m_candidate_panel_is_visible; -- int m_category_active_index; -+ private int m_category_active_index; - private IBus.LookupTable m_lookup_table; - private Gtk.Label[] m_candidates; - private bool m_enter_notify_enable = true; -@@ -223,6 +252,7 @@ class IBusEmojier : Gtk.Window { - focus_visible : true - ); - -+ add_action_entries(m_action_entries, this); - if (m_current_lang_id == null) - m_current_lang_id = "en"; - if (m_emoji_font_family == null) -@@ -270,6 +300,13 @@ class IBusEmojier : Gtk.Window { - "rgba(%u, %u, %u, %lf); ".printf( - bg_red, bg_green, bg_blue, bg_alpha) + - "border-width: 4px; border-radius: 3px; }"; -+ data += "#IBusEmojierGoldLabel { color: " + -+ "rgba(%u, %u, %u, %lf); ".printf( -+ fg_red, fg_green, fg_blue, fg_alpha) + -+ "font-family: %s; font-size: %dpt; ".printf( -+ m_emoji_font_family, m_emoji_font_size) + -+ "background-color: #b09c5f; " + -+ "border-width: 4px; border-radius: 3px; }"; - - Gtk.CssProvider css_provider = new Gtk.CssProvider(); - try { -@@ -317,10 +354,7 @@ class IBusEmojier : Gtk.Window { - }); - - candidate_clicked.connect((i, b, s) => { -- IBus.Text candidate = m_lookup_table.get_candidate(i); -- m_result = candidate.text; -- m_loop.quit(); -- hide_candidate_panel(); -+ candidate_panel_select_index(i); - }); - - if (m_annotation_to_emojis_dict == null) { -@@ -345,14 +379,17 @@ class IBusEmojier : Gtk.Window { - - private static void init_emoji_dict() { - m_annotation_to_emojis_dict = -- new GLib.HashTable(GLib.str_hash, -- GLib.str_equal); -+ new GLib.HashTable>(GLib.str_hash, -+ GLib.str_equal); - m_emoji_to_data_dict = - new GLib.HashTable(GLib.str_hash, - GLib.str_equal); - m_category_to_emojis_dict = -- new GLib.HashTable(GLib.str_hash, -- GLib.str_equal); -+ new GLib.HashTable>(GLib.str_hash, -+ GLib.str_equal); -+ m_emoji_to_emoji_variants_dict = -+ new GLib.HashTable>(GLib.str_hash, -+ GLib.str_equal); - } - - -@@ -390,14 +427,16 @@ class IBusEmojier : Gtk.Window { - } - if (!has_emoji) { - hits.append(emoji); -- m_annotation_to_emojis_dict.replace(annotation, hits.copy()); -+ m_annotation_to_emojis_dict.replace( -+ annotation, -+ hits.copy_deep(GLib.strdup)); - } - } - } - - - private static string utf8_down(string str) { -- GLib.StringBuilder buff = new GLib.StringBuilder(); -+ var buff = new GLib.StringBuilder(); - int length = str.char_count(); - for (int i = 0; i < length; i++) { - buff.append_unichar(str.get_char(0).tolower()); -@@ -407,23 +446,8 @@ class IBusEmojier : Gtk.Window { - } - - -- private static string utf8_title(string str) { -- StringBuilder buff = new StringBuilder(); -- int length = str.char_count(); -- for (int i = 0; i < length; i++) { -- unichar ch = str.get_char(0); -- if (i == 0) -- buff.append_unichar(ch.toupper()); -- else -- buff.append_unichar(ch); -- str = str.next_char(); -- } -- return buff.str; -- } -- -- - private static string utf8_code_point(string str) { -- StringBuilder buff = new StringBuilder(); -+ var buff = new GLib.StringBuilder(); - int length = str.char_count(); - for (int i = 0; i < length; i++) { - unichar ch = str.get_char(0); -@@ -438,7 +462,7 @@ class IBusEmojier : Gtk.Window { - - - private static string utf8_entity(string str) { -- GLib.StringBuilder buff = new GLib.StringBuilder(); -+ var buff = new GLib.StringBuilder(); - int length = str.char_count(); - for (int i = 0; i < length; i++) { - unichar ch = str.get_char(0); -@@ -503,8 +527,19 @@ class IBusEmojier : Gtk.Window { - update_annotations_with_description (data, description); - m_emoji_to_data_dict.replace(emoji, data); - } else { -- unowned IBus.EmojiData? en_data = -- m_emoji_to_data_dict.lookup(emoji); -+ unowned IBus.EmojiData? en_data = null; -+ // If emoji presentation (+= 0xfe0f) is already saved in dict, -+ // update it instead of no presentation. -+ // emoji-test.txt has all emoji presentations but $lang.xml has -+ // some no emoji presentations. -+ if (emoji.chr(-1, 0xfe0f) == null) { -+ var buff = new GLib.StringBuilder(); -+ buff.append(emoji); -+ buff.append_unichar(0xfe0f); -+ en_data = m_emoji_to_data_dict.lookup(buff.str); -+ } -+ if (en_data == null) -+ en_data = m_emoji_to_data_dict.lookup(emoji); - if (en_data == null) { - m_emoji_to_data_dict.insert(emoji, data); - return; -@@ -536,6 +571,36 @@ class IBusEmojier : Gtk.Window { - if (category == "") - category = EMOJI_CATEGORY_OTHERS; - if (lang == "en") { -+ bool has_variant = false; -+ foreach (unichar ch in EMOJI_VARIANT_LIST) { -+ if (emoji.chr(-1, ch) != null) { -+ has_variant = true; -+ break; -+ } -+ } -+ // If emoji includes variants (skin colors and items), -+ // it's escaped in m_emoji_to_emoji_variants_dict and -+ // not shown by default. -+ if (has_variant) { -+ unichar base_ch = emoji.get_char(); -+ string base_emoji = base_ch.to_string(); -+ var buff = new GLib.StringBuilder(); -+ buff.append_unichar(base_ch); -+ buff.append_unichar(0xfe0f); -+ if (m_emoji_to_data_dict.lookup(buff.str) != null) -+ base_emoji = buff.str; -+ unowned GLib.SList? variants = -+ m_emoji_to_emoji_variants_dict.lookup(base_emoji); -+ if (variants.find_custom(emoji, GLib.strcmp) == null) { -+ if (variants == null) -+ variants.append(base_emoji); -+ variants.append(emoji); -+ m_emoji_to_emoji_variants_dict.replace( -+ base_emoji, -+ variants.copy_deep(GLib.strdup)); -+ } -+ return; -+ } - bool has_emoji = false; - unowned GLib.SList hits = - m_category_to_emojis_dict.lookup(category); -@@ -547,7 +612,8 @@ class IBusEmojier : Gtk.Window { - } - if (!has_emoji) { - hits.append(emoji); -- m_category_to_emojis_dict.replace(category, hits.copy()); -+ m_category_to_emojis_dict.replace(category, -+ hits.copy_deep(GLib.strdup)); - } - } - } -@@ -588,14 +654,15 @@ class IBusEmojier : Gtk.Window { - m_list_box.row_activated.connect((box, gtkrow) => { - m_category_active_index = 0; - EBoxRow row = gtkrow as EBoxRow; -- show_emoji_for_category(row); -+ show_emoji_for_category(row.text); - }); - - uint n = 1; - if (m_favorites.length > 0) { -- EBoxRow row = new EBoxRow("@favorites"); -+ EBoxRow row = new EBoxRow(EMOJI_CATEGORY_FAVORITES); - EPaddedLabelBox widget = -- new EPaddedLabelBox(_("Favorites"), Gtk.Align.CENTER); -+ new EPaddedLabelBox(_(EMOJI_CATEGORY_FAVORITES), -+ Gtk.Align.CENTER); - row.add(widget); - m_list_box.add(row); - if (n++ == m_category_active_index) -@@ -617,10 +684,8 @@ class IBusEmojier : Gtk.Window { - if (category == EMOJI_CATEGORY_OTHERS) - continue; - EBoxRow row = new EBoxRow(category); -- string locale_category = _(category); - EPaddedLabelBox widget = -- new EPaddedLabelBox(utf8_title(locale_category), -- Gtk.Align.CENTER); -+ new EPaddedLabelBox(_(category), Gtk.Align.CENTER); - row.add(widget); - m_list_box.add(row); - if (n++ == m_category_active_index) -@@ -635,23 +700,40 @@ class IBusEmojier : Gtk.Window { - } - - -- private void show_emoji_for_category(EBoxRow row) { -- if (row.text == "@favorites") { -+ private void show_emoji_for_category(string category) { -+ if (category == EMOJI_CATEGORY_FAVORITES) { - m_lookup_table.clear(); - foreach (unowned string favorate in m_favorites) { - IBus.Text text = new IBus.Text.from_string(favorate); - m_lookup_table.append_candidate(text); - } -- m_backward = _("Favorites"); -+ m_backward = category; - } else { - unowned GLib.SList emojis = -- m_category_to_emojis_dict.lookup(row.text); -+ m_category_to_emojis_dict.lookup(category); - m_lookup_table.clear(); - foreach (unowned string emoji in emojis) { - IBus.Text text = new IBus.Text.from_string(emoji); - m_lookup_table.append_candidate(text); - } -- m_backward = utf8_title(row.text); -+ m_backward = category; -+ } -+ // Restore the cursor position before the special table of -+ // emoji variants is shown. -+ if (m_backward_index >= 0) { -+ m_lookup_table.set_cursor_pos((uint)m_backward_index); -+ m_backward_index = -1; -+ } -+ show_candidate_panel(); -+ } -+ -+ -+ private void show_emoji_variants(GLib.SList? emojis) { -+ m_backward_index = (int)m_lookup_table.get_cursor_pos(); -+ m_lookup_table.clear(); -+ foreach (unowned string emoji in emojis) { -+ IBus.Text text = new IBus.Text.from_string(emoji); -+ m_lookup_table.append_candidate(text); - } - show_candidate_panel(); - } -@@ -696,8 +778,8 @@ class IBusEmojier : Gtk.Window { - private bool check_unicode_point() { - string annotation = m_entry.get_text(); - m_unicode_point = null; -- GLib.StringBuilder buff = new GLib.StringBuilder(); -- GLib.StringBuilder retval = new GLib.StringBuilder(); -+ var buff = new GLib.StringBuilder(); -+ var retval = new GLib.StringBuilder(); - for (int i = 0; i < annotation.char_count(); i++) { - unichar ch = annotation.get_char(i); - if (ch == 0) -@@ -727,7 +809,7 @@ class IBusEmojier : Gtk.Window { - } - - -- public void update_candidate_window() { -+ private void update_candidate_window() { - string annotation = m_entry.get_text(); - if (annotation.length == 0) { - hide_candidate_panel(); -@@ -795,6 +877,22 @@ class IBusEmojier : Gtk.Window { - } - - -+ private void show_code_point_description(string text) { -+ EPaddedLabelBox widget_code = new EPaddedLabelBox( -+ _("Code point: %s").printf(utf8_code_point(text)), -+ Gtk.Align.START); -+ m_vbox.add(widget_code); -+ widget_code.show_all(); -+ if (m_emoji_to_emoji_variants_dict.lookup(text) != null) { -+ EPaddedLabelBox widget_has_variant = new EPaddedLabelBox( -+ _("Has emoji variants"), -+ Gtk.Align.START); -+ m_vbox.add(widget_has_variant); -+ widget_has_variant.show_all(); -+ } -+ } -+ -+ - private void show_candidate_panel() { - remove_all_children(); - set_fixed_size(); -@@ -823,18 +921,27 @@ class IBusEmojier : Gtk.Window { - EGrid grid = new EGrid(); - int n = 0; - for (uint i = page_start_pos; i < page_end_pos; i++) { -- IBus.Text candidate = m_lookup_table.get_candidate(i); -+ string text = m_lookup_table.get_candidate(i).text; -+ bool has_variant = -+ (m_emoji_to_emoji_variants_dict.lookup(text) != null); - Gtk.Label label; -- if (i == cursor) -- label = new ESelectedLabel(candidate.text) as Gtk.Label; -- else -- label = new EWhiteLabel(candidate.text) as Gtk.Label; -- if (candidate.text.char_count() > 2) { -+ // If 'i' is the cursor position, use the selected color. -+ // If the emoji has emoji variants, use the gold color. -+ // Otherwise the white color. -+ if (i == cursor) { -+ label = new ESelectedLabel(text) as Gtk.Label; -+ } else if (m_show_emoji_variant && has_variant && -+ m_backward_index < 0) { -+ label = new EGoldLabel(text) as Gtk.Label; -+ } else { -+ label = new EWhiteLabel(text) as Gtk.Label; -+ } -+ if (text.char_count() > 2) { - string font_family = m_emoji_font_family; - int font_size = m_emoji_font_size - 2; - string emoji_font = "%s %d".printf(font_family, font_size); - string markup = "%s". -- printf(emoji_font, utf8_entity(candidate.get_text())); -+ printf(emoji_font, utf8_entity(text)); - label.set_markup(markup); - } - label.set_halign(Gtk.Align.FILL); -@@ -879,9 +986,8 @@ class IBusEmojier : Gtk.Window { - show_arrow_buttons(); - m_vbox.add(grid); - grid.show_all(); -- IBus.Text candidate = m_lookup_table.get_candidate(cursor); -- unowned IBus.EmojiData? data = -- m_emoji_to_data_dict.lookup(candidate.text); -+ string text = m_lookup_table.get_candidate(cursor).text; -+ unowned IBus.EmojiData? data = m_emoji_to_data_dict.lookup(text); - if (data == null) { - // TODO: Provide a custom description and annotation for - // the favorite emojis. -@@ -890,12 +996,7 @@ class IBusEmojier : Gtk.Window { - Gtk.Align.START); - m_vbox.add(widget); - widget.show_all(); -- EPaddedLabelBox widget_code = new EPaddedLabelBox( -- _("Code point: %s").printf( -- utf8_code_point(candidate.text)), -- Gtk.Align.START); -- m_vbox.add(widget_code); -- widget_code.show_all(); -+ show_code_point_description(text); - return; - } else { - unowned string description = data.get_description(); -@@ -907,7 +1008,7 @@ class IBusEmojier : Gtk.Window { - } - unowned GLib.SList? annotations = - data.get_annotations(); -- GLib.StringBuilder buff = new GLib.StringBuilder(); -+ var buff = new GLib.StringBuilder(); - int i = 0; - foreach (unowned string annotation in annotations) { - if (i++ == 0) -@@ -929,12 +1030,7 @@ class IBusEmojier : Gtk.Window { - m_vbox.add(widget); - widget.show_all(); - } -- EPaddedLabelBox widget_code = new EPaddedLabelBox( -- _("Code point: %s").printf( -- utf8_code_point(candidate.text)), -- Gtk.Align.START); -- m_vbox.add(widget_code); -- widget_code.show_all(); -+ show_code_point_description(text); - } - } - -@@ -961,6 +1057,20 @@ class IBusEmojier : Gtk.Window { - } - - -+ private void candidate_panel_select_index(uint index) { -+ string text = m_lookup_table.get_candidate(index).text; -+ unowned GLib.SList? emojis = -+ m_emoji_to_emoji_variants_dict.lookup(text); -+ if (m_show_emoji_variant && emojis != null && -+ m_backward_index < 0) { -+ show_emoji_variants(emojis); -+ } else { -+ m_result = text; -+ m_loop.quit(); -+ hide_candidate_panel(); -+ } -+ } -+ - private void candidate_panel_cursor_down() { - enter_notify_disable_with_timer(); - uint ncandidates = m_lookup_table.get_number_of_candidates(); -@@ -1094,8 +1204,11 @@ class IBusEmojier : Gtk.Window { - } - - -- private bool key_press_cursor_escape() { -- if (m_candidate_panel_is_visible) { -+ private bool key_press_escape() { -+ if (m_backward_index >= 0 && m_backward != null) { -+ show_emoji_for_category(m_backward); -+ return true; -+ } else if (m_candidate_panel_is_visible) { - hide_candidate_panel(); - return true; - } else if (m_entry.get_text().length == 0) { -@@ -1108,6 +1221,19 @@ class IBusEmojier : Gtk.Window { - } - - -+ private bool key_press_enter() { -+ if (m_candidate_panel_is_visible) { -+ uint index = m_lookup_table.get_cursor_pos(); -+ candidate_panel_select_index(index); -+ } else if (m_category_active_index > 0) { -+ Gtk.ListBoxRow gtkrow = m_list_box.get_selected_row(); -+ EBoxRow row = gtkrow as EBoxRow; -+ show_emoji_for_category(row.text); -+ } -+ return true; -+ } -+ -+ - private void entry_enter_keyval(uint keyval) { - unichar ch = IBus.keyval_to_unicode(keyval); - if (ch.iscntrl()) -@@ -1130,6 +1256,13 @@ class IBusEmojier : Gtk.Window { - } - - -+ private void check_action_variant_cb(GLib.SimpleAction action, -+ GLib.Variant? parameter) { -+ m_show_emoji_variant = !action.get_state().get_boolean(); -+ action.set_state(new GLib.Variant.boolean(m_show_emoji_variant)); -+ } -+ -+ - public string run(string input_context_path) { - assert (m_loop == null); - -@@ -1187,21 +1320,11 @@ class IBusEmojier : Gtk.Window { - */ - switch (keyval) { - case Gdk.Key.Escape: -- if (key_press_cursor_escape()) -+ if (key_press_escape()) - return true; - break; - case Gdk.Key.Return: -- if (m_candidate_panel_is_visible) { -- uint index = m_lookup_table.get_cursor_pos(); -- IBus.Text text = m_lookup_table.get_candidate(index); -- m_result = text.text; -- m_loop.quit(); -- hide_candidate_panel(); -- } else if (m_category_active_index > 0) { -- Gtk.ListBoxRow gtkrow = m_list_box.get_selected_row(); -- EBoxRow row = gtkrow as EBoxRow; -- show_emoji_for_category(row); -- } -+ key_press_enter(); - return true; - case Gdk.Key.BackSpace: - if (m_entry.get_text().len() > 0) { -@@ -1283,7 +1406,7 @@ class IBusEmojier : Gtk.Window { - return true; - break; - case Gdk.Key.u: -- if (key_press_cursor_escape()) -+ if (key_press_escape()) - return true; - break; - case Gdk.Key.a: --- -2.9.3 - -From 5f653d3102ada9139a2bc7504d6beaee1bc9984b Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Tue, 9 May 2017 13:54:01 +0900 -Subject: [PATCH] ui/gtk3: Add ibus-emoji man mapge - -R=Shawn.P.Huang@gmail.com - -Review URL: https://codereview.appspot.com/323810043 ---- - setup/ibus-setup.1.in | 21 +++++++++++-- - tools/ibus.1.in | 20 ++++++++++-- - ui/gtk3/Makefile.am | 22 ++++++++++++++ - ui/gtk3/ibus-emoji.1.in | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ - 4 files changed, 140 insertions(+), 4 deletions(-) - create mode 100644 ui/gtk3/ibus-emoji.1.in - -diff --git a/setup/ibus-setup.1.in b/setup/ibus-setup.1.in -index dab032e..f9b5f71 100644 ---- a/setup/ibus-setup.1.in -+++ b/setup/ibus-setup.1.in -@@ -1,10 +1,10 @@ - .\" This file is distributed under the same license as the ibus - .\" package. - .\" Copyright (C) LI Daobing , 2008. --.\" Copyright (C) Takao Fujiwara , 2013-2015. -+.\" Copyright (C) Takao Fujiwara , 2013-2017. - .\" Copyright (c) Peng Huang , 2013. - .\" --.TH "IBUS-SETUP" 1 "November 2008" "@VERSION@" "User Commands" -+.TH "IBUS-SETUP" 1 "November 2017" "@VERSION@" "User Commands" - .SH NAME - .B ibus-setup - \- configuration program for ibus -@@ -26,6 +26,23 @@ is the configuration program for IBus. - .PP - Homepage: https://github.com/ibus/ibus/wiki - -+.SH "ADVANCED" -+.TP -+\fBUse system keyboard layout\fR -+If this option is enabled, use your system keymap instead and IBus -+does not change it with any input method engines. The system keymap is -+decided by your desktop session. IBus calls -+.B setxkbmap (1) -+command internally to set the selected keymap and override the -+system keymap if this options is disabled. -+You can check the current keymap with -+.B setxkbmap -query -+command. -+.TP -+\fBShare the same input method among all applications\fR -+If this option is disabled, You can choose an input metod engine -+by input context. -+ - .SH BUGS - If you find a bug, please report it at http://code.google.com/p/ibus/issues/list - -diff --git a/tools/ibus.1.in b/tools/ibus.1.in -index a420567..d34a77a 100644 ---- a/tools/ibus.1.in -+++ b/tools/ibus.1.in -@@ -1,9 +1,9 @@ - .\" This file is distributed under the same license as the ibus - .\" package. --.\" Copyright (C) Takao Fujiwara , 2013. -+.\" Copyright (C) Takao Fujiwara , 2013-2017. - .\" Copyright (c) Peng Huang , 2013. - .\" --.TH "IBUS" 1 "May 2013" "@VERSION@" "User Commands" -+.TH "IBUS" 1 "May 2017" "@VERSION@" "User Commands" - .SH NAME - .B ibus - \- command line utility for ibus -@@ -95,6 +95,22 @@ configuration file. - \fBwatch\fR - Under construction. - .TP -+\fBemoji\fR [\fB\-\-font=FONT|\-\-lang=LANG|\-\-help|\-\-partial\-match\fR] -+Launch IBus Emojier ( -+.B ibus\-emoji (1) -+) and save the selected emoji to your clipboard. Can choose an emoji font with -+.B \-\-font -+option. Can choose a language of emoji annotations with -+.B \-\-lang -+option. If LANG="en", IBus Emojier loads /usr/share/ibus/dicts/emoji-en.dict . -+.B \-\-partial\-match -+option enables to match annotations with a partial string. These settings -+are avaiable with -+.B ibus\-setup (1) -+utility. - - .SH BUGS - If you find a bug, please report it at https://github.com/ibus/ibus/issues -+ -+.SH "SEE ALSO" -+.BR ibus-emoji (1) -diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am -index c557474..270ad1c 100644 ---- a/ui/gtk3/Makefile.am -+++ b/ui/gtk3/Makefile.am -@@ -148,6 +148,8 @@ emoji_headers = \ - ibusemojidialog.h \ - $(NULL) - -+man_one_in_files = ibus-emoji.1.in -+ - # References: - # libappindicator/src/notification-item.xml - # libappindicator/src/notification-watcher.xml -@@ -155,6 +157,7 @@ emoji_headers = \ - # kdelibs/kdeui/knotifications/src/org.kde.StatusNotifierWatcher.xml - EXTRA_DIST = \ - $(emoji_headers) \ -+ $(man_one_in_files) \ - IBusEmojiDialog-1.0.metadata \ - gtkpanel.xml.in \ - ibus-emoji-dialog-1.0.deps \ -@@ -253,8 +256,27 @@ MAINTAINERCLEANFILES += $(VAPIGEN_VAPIS) - DISTCLEANFILES += $(VAPIGEN_VAPIS) - EXTRA_DIST += $(VAPIGEN_VAPIS) - -+# end of ENABLE_VAPIGEN - endif -+# end of HAVE_INTROSPECTION - endif -+ -+man_one_files = $(man_one_in_files:.1.in=.1) -+man_one_DATA =$(man_one_files:.1=.1.gz) -+man_onedir = $(mandir)/man1 -+%.1: %.1.in -+ $(AM_V_GEN) sed \ -+ -e 's|@VERSION[@]|$(VERSION)|g' $< > $@.tmp && \ -+ mv $@.tmp $@ -+%.1.gz: %.1 -+ $(AM_V_GEN) gzip -c $< > $@.tmp && mv $@.tmp $@ -+ -+CLEANFILES += \ -+ $(man_one_DATA) \ -+ $(man_one_files) \ -+ $(NULL) -+ -+# end of ENABLE_EMOJI_DICT - endif - - -include $(top_srcdir)/git.mk -diff --git a/ui/gtk3/ibus-emoji.1.in b/ui/gtk3/ibus-emoji.1.in -new file mode 100644 -index 0000000..3523271 ---- /dev/null -+++ b/ui/gtk3/ibus-emoji.1.in -@@ -0,0 +1,81 @@ -+.\" This file is distributed under the same license as the ibus -+.\" package. -+.\" Copyright (C) Takao Fujiwara , 2017. -+.\" -+.TH "IBUS EMOJI" 1 "May 2017" "@VERSION@" "User Commands" -+.SH NAME -+.B ibus emoji utility -+\- Call the IBus emoji utility by -+.B IBus Emojier -+ -+.SH "SYNOPSIS" -+.B /usr/libexec/ibus\-ui\-emojier -+[\fIOPTION\fR]... -+ -+.SH "DESCRIPTION" -+ -+.PP -+IBus Emojier provides a GUI to select an emoji by typing an emoji annotaion -+or choosing a character with mouse click and it's designed to work as -+an extended IBus lookup window using Space, Enter, and Arrow keys. -+The text entry accepts an emoji annotation or Unicode points. -+If IBus panel is launched, IBus panel calls IBus Emojier with a -+shortcut key or the right click menu and output the selected emoji -+on the previous focused text on an application. -+If IBus panel is not available likes GNOME desktop, You can still use -+.B ibus emoji -+command to launch IBus Emojier and it saves the selected emoji in -+your clipboard and you can paste the emoji by a paste key likes -+Control\-v. -+ -+.PP -+Refer -+.B emoji -+section in -+.B ibus (1) -+for the command line options. -+ -+.PP -+Homepage: https://github.com/ibus/ibus/wiki -+ -+.SH "SETTINGS" -+.TP -+\fBANNOTATION LANGUAGE\fR -+You can choose a language of emoji annotations with -+.B ibus\-setup (1). -+.TP -+\fBFONT\fR -+You can choose an emoji font with -+.B ibus\-setup (1). -+E.g. "Noto Color Emoji", "Android Emoji" font. -+ -+.SH "KEYBOARD OPERATIONS" -+.TP -+\fBControl-Shift-e\fR -+Launch IBus Emojier. The shortcut key can be customized by -+.B ibus\-setup (1). -+.TP -+\fBLeft or right arrow\fR -+Move the selected category or emoji if an annotation is not typed. -+Otherwise move the cursor in typed annotation. -+.TP -+\fBDown or up arrow\fR -+Move the selected category or emoji. -+.TP -+\fBEnter\fR -+Commit the selected emoji. -+.TP -+\fBEscape\fR -+Go back to the category list from emoji list, erase a typed annotation, -+and close IBus Emojier. -+.TP -+\fBSpace\fR -+Move the selected category or emoji. If Shift\-Space is typed, -+a space chararacter can be inserted between words in an annotation or -+Unicode points. E.g. 1f466 Shift\-Space 1f3fb -+.TP -+\fBPageDown or PageUp\fR -+Move to the next or previous page in emoji list. -+ -+.SH BUGS -+If you find a bug, please report it at https://github.com/ibus/ibus/issues --- -2.9.3 - -From 1159093dea3fdb8ceb62157f06b3560f8fc28378 Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Tue, 9 May 2017 13:58:02 +0900 -Subject: [PATCH] Update favorites category automatically by selecting - emoji - -When you commit an emoji with Emojier, Favorite category -will be shown on GUI. -Now a custom annotation also can be assigned to a favorite emoji -by gsetting command. - -R=penghuang@google.com - -Review URL: https://codereview.appspot.com/312660043 ---- - data/ibus.schemas.in | 13 ++++++++++ - src/ibusemoji.h | 2 +- - ui/gtk3/emojier.vala | 63 +++++++++++++++++++++++++++++++++++++++++++++-- - ui/gtk3/emojierapp.vala | 18 ++++++++++++++ - ui/gtk3/ibusemojidialog.h | 20 ++++++++++----- - ui/gtk3/panel.vala | 21 +++++++++++++++- - 6 files changed, 127 insertions(+), 10 deletions(-) - -diff --git a/data/ibus.schemas.in b/data/ibus.schemas.in -index 0647004..09b6b2c 100644 ---- a/data/ibus.schemas.in -+++ b/data/ibus.schemas.in -@@ -404,6 +404,19 @@ - - - -+ /schemas/desktop/ibus/panel/emoji/favorite-annotations -+ /desktop/ibus/panel/emoji/favorite-annotations -+ ibus -+ list -+ [] -+ string -+ -+ favorite emoji annotation list on emoji dialog -+ You can assign an annotation for a favorite emoji -+ in this list. -+ -+ -+ - /schemas/desktop/ibus/panel/emoji/has-partial-match - /desktop/ibus/panel/emoji/has-partial-match - ibus -diff --git a/src/ibusemoji.h b/src/ibusemoji.h -index eb24fdd..4edee72 100644 ---- a/src/ibusemoji.h -+++ b/src/ibusemoji.h -@@ -188,7 +188,7 @@ GHashTable * ibus_emoji_dict_load (const gchar *path); - * @dict: (element-type utf8 IBusEmojiData) (transfer full): An Emoji dictionary - * @emoji: an emoji character - * -- * Returns: An #IBusEmojiData of @emoji. -+ * Returns: (transfer none): An #IBusEmojiData of @emoji. - * This API was prepared for the old dict foramat with Gir and Vala - * but no longer needed. - * Use ibus_emoji_data_load() instead. -diff --git a/ui/gtk3/emojier.vala b/ui/gtk3/emojier.vala -index db7520a..4169550 100644 ---- a/ui/gtk3/emojier.vala -+++ b/ui/gtk3/emojier.vala -@@ -201,6 +201,7 @@ class IBusEmojier : Gtk.ApplicationWindow { - private static string m_emoji_font_family; - private static int m_emoji_font_size; - private static string[] m_favorites; -+ private static string[] m_favorite_annotations; - private static int m_emoji_max_seq_len; - private static bool m_has_partial_match; - private static uint m_partial_match_length; -@@ -261,6 +262,8 @@ class IBusEmojier : Gtk.ApplicationWindow { - m_emoji_font_size = 16; - if (m_favorites == null) - m_favorites = {}; -+ if (m_favorite_annotations == null) -+ m_favorite_annotations = {}; - - Gdk.Display display = Gdk.Display.get_default(); - Gdk.Screen screen = (display != null) ? -@@ -374,6 +377,7 @@ class IBusEmojier : Gtk.ApplicationWindow { - } - make_emoji_dict(m_current_lang_id); - } -+ update_favorite_emoji_dict(); - } - - -@@ -1263,6 +1267,49 @@ class IBusEmojier : Gtk.ApplicationWindow { - } - - -+ public static void update_favorite_emoji_dict() { -+ if (m_emoji_to_data_dict == null || -+ m_annotation_to_emojis_dict == null) -+ return; -+ -+ for(int i = 0; i < m_favorites.length; i++) { -+ var favorite = m_favorites[i]; -+ -+ string? annotation = ""; -+ if (i < m_favorite_annotations.length) { -+ annotation = m_favorite_annotations[i]; -+ } -+ if (annotation == "") -+ continue; -+ unowned IBus.EmojiData? data = -+ m_emoji_to_data_dict.lookup(favorite); -+ if (data == null) { -+ GLib.SList new_annotations = new GLib.SList(); -+ new_annotations.append(annotation); -+ IBus.EmojiData new_data = GLib.Object.new( -+ typeof(IBus.EmojiData), -+ "emoji", favorite.dup(), -+ "annotations", new_annotations, -+ "description", annotation.dup() -+ ) as IBus.EmojiData; -+ m_emoji_to_data_dict.insert(favorite, new_data); -+ } else { -+ unowned GLib.SList annotations = data.get_annotations(); -+ if (annotations.find_custom(annotation, GLib.strcmp) == null) { -+ annotations.append(annotation); -+ data.set_annotations(annotations.copy()); -+ } -+ } -+ unowned GLib.SList emojis = -+ m_annotation_to_emojis_dict.lookup(annotation); -+ if (emojis.find_custom(favorite, GLib.strcmp) == null) { -+ emojis.append(favorite); -+ m_annotation_to_emojis_dict.replace(annotation, emojis.copy()); -+ } -+ } -+ } -+ -+ - public string run(string input_context_path) { - assert (m_loop == null); - -@@ -1510,10 +1557,22 @@ class IBusEmojier : Gtk.ApplicationWindow { - m_partial_match_condition = condition; - } - -- public static void set_favorites(string[]? unowned_favorites) { -+ public static void set_favorites(string[]? unowned_favorites, -+ string[]? unowned_favorite_annotations) { - m_favorites = {}; -- foreach (string favorite in unowned_favorites) { -+ m_favorite_annotations = {}; -+ for(int i = 0; i < unowned_favorites.length; i++) { -+ string? favorite = unowned_favorites[i]; -+ // Avoid gsetting value error by manual setting -+ GLib.return_if_fail(favorite != null); -+ GLib.return_if_fail(favorite != ""); - m_favorites += favorite; - } -+ for(int i = 0; i < unowned_favorite_annotations.length; i++) { -+ string? favorite_annotation = unowned_favorite_annotations[i]; -+ GLib.return_if_fail(favorite_annotation != null); -+ m_favorite_annotations += favorite_annotation; -+ } -+ update_favorite_emoji_dict(); - } - } -diff --git a/ui/gtk3/emojierapp.vala b/ui/gtk3/emojierapp.vala -index dbdbc27..24816bb 100644 ---- a/ui/gtk3/emojierapp.vala -+++ b/ui/gtk3/emojierapp.vala -@@ -50,6 +50,20 @@ public class EmojiApplication : Application { - Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD); - clipboard.set_text(emoji, -1); - clipboard.store(); -+ -+ var emojier_favorites = m_settings_emoji.get_strv("favorites"); -+ bool has_favorite = false; -+ foreach (unowned string favorite in emojier_favorites) { -+ if (favorite == emoji) { -+ has_favorite = true; -+ break; -+ } -+ } -+ if (!has_favorite) { -+ emojier_favorites += emoji; -+ m_settings_emoji.set_strv("favorites", emojier_favorites); -+ } -+ - m_emojier = null; - command_line.print("%s\n", _("Copied an emoji to your clipboard.")); - } -@@ -147,6 +161,10 @@ public class EmojiApplication : Application { - if (emoji_font != null) - IBusEmojier.set_emoji_font(emoji_font); - -+ IBusEmojier.set_favorites( -+ m_settings_emoji.get_strv("favorites"), -+ m_settings_emoji.get_strv("favorite-annotations")); -+ - activate_dialog(command_line); - - return Posix.EXIT_SUCCESS; -diff --git a/ui/gtk3/ibusemojidialog.h b/ui/gtk3/ibusemojidialog.h -index 1499a3c..fb0f5a6 100644 ---- a/ui/gtk3/ibusemojidialog.h -+++ b/ui/gtk3/ibusemojidialog.h -@@ -145,18 +145,26 @@ void ibus_emojier_set_annotation_lang (const gchar* lang); - */ - void ibus_emojier_set_emoji_font (const gchar* emoji_font); - --#if 0 --/* TODO: set customized annotations */ - /** - * ibus_emojier_set_favorites: - * @favorites: (array length=favorites_length): A custom emoji list. - * @favorites_length: A length of @favorites -- * -- * Set emoji font on the emoji dialog -+ * @favorite_annotations: (array length=favorite_annotations_length): -+ * (nullable): -+ * A custom annotation listfor @favorites. -+ * @favorite_annotations_length: A length of @favorite_annotations -+ * -+ * Set custom emojis on the emoji dialog. @favorite_annotations -+ * can be null. If you don't assign an annotation for a specific emoji, -+ * you can pass the annotation as "", e.g. -+ * favorite_annotations = { "", "", "my annotation" }; - */ - void ibus_emojier_set_favorites (gchar** favorites, - int -- favorites_length); --#endif -+ favorites_length, -+ gchar** -+ favorite_annotations, -+ int -+ favorite_annotations_length); - G_END_DECLS - #endif -diff --git a/ui/gtk3/panel.vala b/ui/gtk3/panel.vala -index 5f0009e..e4936df 100644 ---- a/ui/gtk3/panel.vala -+++ b/ui/gtk3/panel.vala -@@ -76,6 +76,7 @@ class Panel : IBus.PanelService { - private IBusEmojier? m_emojier; - private uint m_emojier_set_emoji_lang_id; - private uint m_emojier_focus_commit_text_id; -+ private string[] m_emojier_favorites = {}; - private PropertyManager m_property_manager; - private PropertyPanel m_property_panel; - private GLib.Pid m_setup_pid = 0; -@@ -245,6 +246,10 @@ class Panel : IBus.PanelService { - set_emoji_favorites(); - }); - -+ m_settings_emoji.changed["favorite-annotations"].connect((key) => { -+ set_emoji_favorites(); -+ }); -+ - m_settings_emoji.changed["lang"].connect((key) => { - set_emoji_lang(); - }); -@@ -778,7 +783,10 @@ class Panel : IBus.PanelService { - } - - private void set_emoji_favorites() { -- IBusEmojier.set_favorites(m_settings_emoji.get_strv("favorites")); -+ m_emojier_favorites = m_settings_emoji.get_strv("favorites"); -+ IBusEmojier.set_favorites( -+ m_emojier_favorites, -+ m_settings_emoji.get_strv("favorite-annotations")); - } - - private void set_emoji_lang() { -@@ -1437,6 +1445,17 @@ class Panel : IBus.PanelService { - IBus.Text text = new IBus.Text.from_string(selected_string); - commit_text(text); - m_emojier = null; -+ bool has_favorite = false; -+ foreach (unowned string favorite in m_emojier_favorites) { -+ if (favorite == selected_string) { -+ has_favorite = true; -+ break; -+ } -+ } -+ if (!has_favorite) { -+ m_emojier_favorites += selected_string; -+ m_settings_emoji.set_strv("favorites", m_emojier_favorites); -+ } - return true; - } - --- -2.9.3 - -From a897dab2957568f842b78d54dc40dd294fb6191f Mon Sep 17 00:00:00 2001 -From: fujiwarat -Date: Tue, 9 May 2017 14:47:08 +0900 -Subject: [PATCH] ui/gtk3: Rename ibus-emoji.1.in to ibus-emoji.7.in - ---- - tools/ibus.1.in | 2 +- - ui/gtk3/Makefile.am | 18 +++++++++--------- - ui/gtk3/{ibus-emoji.1.in => ibus-emoji.7.in} | 0 - 3 files changed, 10 insertions(+), 10 deletions(-) - rename ui/gtk3/{ibus-emoji.1.in => ibus-emoji.7.in} (100%) - -diff --git a/tools/ibus.1.in b/tools/ibus.1.in -index d34a77a..9275abb 100644 ---- a/tools/ibus.1.in -+++ b/tools/ibus.1.in -@@ -113,4 +113,4 @@ utility. - If you find a bug, please report it at https://github.com/ibus/ibus/issues - - .SH "SEE ALSO" --.BR ibus-emoji (1) -+.BR ibus-emoji (7) -diff --git a/ui/gtk3/Makefile.am b/ui/gtk3/Makefile.am -index 270ad1c..c79641a 100644 ---- a/ui/gtk3/Makefile.am -+++ b/ui/gtk3/Makefile.am -@@ -148,7 +148,7 @@ emoji_headers = \ - ibusemojidialog.h \ - $(NULL) - --man_one_in_files = ibus-emoji.1.in -+man_seven_in_files = ibus-emoji.7.in - - # References: - # libappindicator/src/notification-item.xml -@@ -157,7 +157,7 @@ man_one_in_files = ibus-emoji.1.in - # kdelibs/kdeui/knotifications/src/org.kde.StatusNotifierWatcher.xml - EXTRA_DIST = \ - $(emoji_headers) \ -- $(man_one_in_files) \ -+ $(man_seven_in_files) \ - IBusEmojiDialog-1.0.metadata \ - gtkpanel.xml.in \ - ibus-emoji-dialog-1.0.deps \ -@@ -261,19 +261,19 @@ endif - # end of HAVE_INTROSPECTION - endif - --man_one_files = $(man_one_in_files:.1.in=.1) --man_one_DATA =$(man_one_files:.1=.1.gz) --man_onedir = $(mandir)/man1 --%.1: %.1.in -+man_seven_files = $(man_seven_in_files:.7.in=.7) -+man_seven_DATA =$(man_seven_files:.7=.7.gz) -+man_sevendir = $(mandir)/man7 -+%.7: %.7.in - $(AM_V_GEN) sed \ - -e 's|@VERSION[@]|$(VERSION)|g' $< > $@.tmp && \ - mv $@.tmp $@ --%.1.gz: %.1 -+%.7.gz: %.7 - $(AM_V_GEN) gzip -c $< > $@.tmp && mv $@.tmp $@ - - CLEANFILES += \ -- $(man_one_DATA) \ -- $(man_one_files) \ -+ $(man_seven_DATA) \ -+ $(man_seven_files) \ - $(NULL) - - # end of ENABLE_EMOJI_DICT -diff --git a/ui/gtk3/ibus-emoji.1.in b/ui/gtk3/ibus-emoji.7.in -similarity index 100% -rename from ui/gtk3/ibus-emoji.1.in -rename to ui/gtk3/ibus-emoji.7.in --- -2.9.3 -