aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dino.doap308
-rw-r--r--dino.doap.in202
-rw-r--r--libdino/src/service/avatar_manager.vala10
-rw-r--r--libdino/src/service/avatar_storage.vala2
-rw-r--r--main/CMakeLists.txt5
-rw-r--r--main/data/emojichooser.ui410
-rw-r--r--main/data/im.dino.Dino.appdata.xml15
-rw-r--r--main/src/emojichooser.c820
-rw-r--r--main/src/emojichooser.h36
-rw-r--r--main/src/ui/avatar_image.vala4
-rw-r--r--main/vapi/emojichooser.vapi7
-rw-r--r--qlite/src/delete_builder.vala2
-rw-r--r--qlite/src/insert_builder.vala2
-rw-r--r--qlite/src/update_builder.vala2
-rw-r--r--qlite/src/upsert_builder.vala2
-rw-r--r--xmpp-vala/src/core/stanza_node.vala3
-rw-r--r--xmpp-vala/src/core/stanza_reader.vala56
17 files changed, 1840 insertions, 46 deletions
diff --git a/dino.doap b/dino.doap
index bfba3774..ce811cf4 100644
--- a/dino.doap
+++ b/dino.doap
@@ -1,21 +1,299 @@
<?xml version="1.0" encoding="UTF-8"?>
-<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
- xmlns:foaf="http://xmlns.com/foaf/0.1/"
- xmlns="http://usefulinc.com/ns/doap#">
-
+<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns="http://usefulinc.com/ns/doap#">
<name>Dino</name>
<short-name>dino</short-name>
- <shortdesc xml:lang="en">Modern Jabber/XMPP Client</shortdesc>
+ <shortdesc xml:lang="en">Modern XMPP Chat Client</shortdesc>
+ <shortdesc xml:lang="zh-Hans">现代 XMPP 聊天客户端</shortdesc>
+ <shortdesc xml:lang="ru">Современный чат-клиент на XMPP</shortdesc>
+ <shortdesc xml:lang="ro">Client XMPP de discuții modern</shortdesc>
+ <shortdesc xml:lang="pt">Moderno cliente de chat XMPP</shortdesc>
+ <shortdesc xml:lang="pl">Nowoczesny komunikator XMPP</shortdesc>
+ <shortdesc xml:lang="nl">Moderne XMPP-chatcliënt</shortdesc>
+ <shortdesc xml:lang="nb">Moderne XMPP-sludreklient</shortdesc>
+ <shortdesc xml:lang="lb">Modernen XMPP Chat Client</shortdesc>
+ <shortdesc xml:lang="ja">現代的な XMPP チャット クライアント</shortdesc>
+ <shortdesc xml:lang="it">Client di chat moderno per XMPP</shortdesc>
+ <shortdesc xml:lang="hu">Modern XMPP Üzenetküldő</shortdesc>
+ <shortdesc xml:lang="gl">Cliente moderno para parolas XMPP</shortdesc>
+ <shortdesc xml:lang="fr">Client XMPP moderne</shortdesc>
+ <shortdesc xml:lang="eu">XMPP txat bezero modernoa</shortdesc>
+ <shortdesc xml:lang="es">Cliente de XMPP moderno</shortdesc>
+ <shortdesc xml:lang="de">Moderner XMPP Chat Client</shortdesc>
+ <shortdesc xml:lang="ar">تطبيق حديث للدردشة عبر XMPP</shortdesc>
<description xml:lang="en">
- Dino is an instant messaging client for the
- Jabber/XMPP network, providing a unique and modern user experience
- based on the latest technology from the GNOME project.
- </description>
-
- <homepage rdf:resource="https://dino.im/" />
- <bug-database rdf:resource="https://github.com/dino/dino/issues" />
- <category rdf:resource="http://api.gnome.org/doap-extensions#apps" />
+ Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.
+ It supports end-to-end encryption with OMEMO and OpenPGP and allows configuring privacy-related features such as read receipts and typing notifications.
+ Dino fetches history from the server and synchronizes messages with other devices.
+ </description>
+ <description xml:lang="zh-Hans">
+ Dino 是一个现代的开源聊天桌面客户端。它致力于提供一个清爽又可靠的 Jabber/XMPP 体验,同时又保护您的隐私。
+ 它支持 OMEMO 和 OpenPGP 端对端加密并允许配置隐私相关的特性比如已读回执和输入提醒。
+ Dino 从服务器获取消息并和其他设备同步。
+ </description>
+ <description xml:lang="ru">
+ Dino - это современный клиент для чатов с открытым исходным кодом, направленный на надёжное и приватное использование Jabber/XMPP на персональных компьютерах.
+ Он поддерживает сквозное шифрование через OMEMO и OpenPGP и позволяет настраивать такие функции, как уведомления о прочтении и наборе сообщений.
+ Dino загружает историю с сервера и синхронизирует сообщения с другими устройствами.
+ </description>
+ <description xml:lang="ro">
+ Dino este un client de chat modern, cu sursă deschisă, pentru calculatoare. Se concentrează să furnizeze o experiență Jabber/XMPP clară și fiabilă, ținând cont de confidențialitatea dumneavoastră.
+ Suportă criptare de la un capăt la altul prin intermediul OMEMO și OpenPGP, și permite configurarea caracteristicilor legate de confidențialitate precum trimiterea notificărilor de primire și tastare.
+ Dino preia istoricul discuțiilor de pe server și sincronizează mesajele cu celelalte dispozitive.
+ </description>
+ <description xml:lang="pt">
+ Dino é um moderno chat de código aberto para desktop. Ele é focado em prover uma transparente e confiável experiência Jabber/XMPP, tendo em mente a sua privacidade.
+ Suporte criptografia ponta a ponta com OMEMO e OpenPGP e permite configurar privacidade—características relacionadas às notificações de leitura, recebimento e escrita.
+ Dino obtém o histórico do servidor e sincroniza mensagens com outros dispositivos.
+ </description>
+ <description xml:lang="pl">
+ Dino jest nowoczesnym, otwartym komunikatorem. Skupia się na prostej obsłudze sieci Jabber/XMPP dbając o twoją prywatność.
+ Dino obsługuje szyfrowanie end-to-end za pomocą OMEMO i OpenPGP, a także daje kontrolę nad funkcjami wpływającymi na prywatność, takimi jak powiadomienia o pisaniu czy odczytaniu wiadomości.
+ Dino pobiera historię rozmów z serwera i synchronizuje wiadomości z innymi urządzeniami.
+ </description>
+ <description xml:lang="nl">
+ Dino is een moderne, vrije chattoepassing voor uw bureaublad. Ze biedt een eenvoudige en betrouwbare Jabber/XMPP-ervaring, met uw privacy in het achterhoofd.
+ Ze ondersteunt eind-tot-eind-versleuteling met OMEMO en OpenPGP, en staat u toe privacy-gerelateerde functies, zoals leesbevestigingen en typmeldingen, in te stellen.
+ Dino haalt de geschiedenis op van de server en synchroniseert berichten met andere apparaten.
+ </description>
+ <description xml:lang="nb">
+ Dino er en moderne friporg-sludringsklient for skrivebordet. Det fokuserer på rask og pålitelig XMPP-opplevelse, samtidig som det hegner om personvernet.
+ Det støtter ende-til-ende -kryptering med OMEMO og OpenPGP, og tillater oppsett av personvernsrelaterte funksjoner som meldingskvitteringer og skrivevarsling.
+ Dino henter historikk fra tjeneren og synkroniserer meldinger med andre enheter.
+ </description>
+ <description xml:lang="lb">
+ Dino ass e modernen, quell-offene Chat Client fir den Desktop. Hien biet eng opgeraumt a robust Jabber/XMPP Erfarung a leet ee Schwéierpunkt op Privatsphär.
+ Hien ënnerstëtz Enn-zu-Enn Verschlësselung mat OMEMO an OpenPGP an enthält Privatsphäre-Astellungen zu Liesbestätegungen an Tipp-Benoriichtegungen.
+ Dino rifft  Gespréichverläf vum Server of a synchroniséiert Noriichte mat anere Geräter.
+ </description>
+ <description xml:lang="it">
+ Dino è un client di chat per il desktop, moderno e open-source. Si concentra nel fornire un'esperienza Jabber/XMPP pulita e affidabile tenendo presente la tua privacy.
+ Support la crittografia end-to-end tramite OMEMO e OpenPGP e permette di configurare le funzioni relative alla privacy come le ricevute di lettura e le notifiche di digitazione.
+ Dino recupera la cronologia dal server e sincronizza i messaggi con gli altri dispositivi.
+ </description>
+ <description xml:lang="hu">
+ A Dino egy modern, nyílt forráskódú üzenetküldő alkalmazás asztali rendszerekre, ami a hangsúlyt a letisztult és megbízható Jabber/XMPP élményre helyezi, miközben a magánszféra megőrzését is fontosnak tartja.
+ Támogatja a végponttól-végpontig titkosítást az OMEMO és az OpenPGP által, és magánszférához kötődő beállítási lehetőségeket is biztosít, mint például a kézbesítési, vagy gépelési értesítések küldése.
+ A Dino lekéri a chat előzményeket a szerverről, és szinkronizálja az üzeneteket a többi eszközzel.
+ </description>
+ <description xml:lang="gl">
+ Dino é un cliente moderno e de código aberto para o escritorio. Orientado a fornecer unha experiencia Jabber/XMPP limpa e fiábel tendo a privacidade e seguranza presentes.
+ Suporta o cifrado de punto-a-punto con OMEMO e OpenPGP e permite configurar trazos orientados á privacidade tales coma confirmación de lectura e notificacións de escritura.
+ Dino obtén o histórico dende o servidor e sincroniza as mensaxes con outros dispositivos.
+ </description>
+ <description xml:lang="fr">
+ Dino est un client de chat libre et moderne pour le bureau. Il tente de fournir une expérience Jabber/XMPP simple et fiable tout en ayant toujours à l’esprit votre vie privée.
+ Il prend en charge le chiffrement de bout en bout avec OMEMO et OpenPGP et permet de configurer les fonctions liées à la vie privée telles que l’accusé de réception et les notifications de frappe.
+ Dino récupère l’historique du serveur et synchronise les messages avec d'autres appareils.
+ </description>
+ <description xml:lang="fi">
+ Dino on nykyaikainen avoimen lähdekoodin jutteluohjelma työpöydälle. Se keskittyy tarjoamaan selkeän ja luotettavan Jabber/XMPP-kokemuksen unohtamatta yksityisyyttäsi.
+ Se tukee päästä päähän -salausta OMEMO:n ja OpenPGP:n avulla ja mahdollistaa yksityisyyteen liittyvien ominaisuuksien, kuten lukukuittausten ja kirjoitusilmoitusten asetusten määrittämisen.
+ Dino hakee historian palvelimelta ja synkronisoi viestit muiden laitteiden kanssa.
+ </description>
+ <description xml:lang="eu">
+ Dino mahaigainerako iturburu irekiko txat bezero moderno bat da. Jabber/XMPP esperientzia garbi eta fidagarri bat ematen du zure pribatutasuna kontuan hartzeaz gain.
+ Amaieratik amaierarako enkriptazioa onartzen du OMEMO eta OpenPGPrekin eta pribatutasun ezaugarriak konfiguratzea baimentzen du irakurtze markak eta idazketa jakinarazpenak bezala.
+ Dinok zerbitzaritik hartzen du historia eta beste gailuekin mezuak sinkronizatzen ditu.
+ </description>
+ <description xml:lang="es">
+ Dino es un cliente de mensajería moderno y libre para escritorio. Está enfocado en proveer una experiencia Jabber/XMPP limpia y confiable teniendo tu privacidad en mente.
+ Soporta cifrado de extremo a extremo a través de OMEMO y OpenPGP y permite configurar las características relacionadas con la privacidad, como confirmaciones de lectura y notificaciones de escritura.
+ Dino recupera los mensajes desde el servidor y sincroniza los mensajes con otros dispositivos.
+ </description>
+ <description xml:lang="de">
+ Dino ist ein moderner, quelloffener Chat Client. Er bietet eine aufgeräumte und robuste Jabber/XMPP Erfahrung und legt einen Schwerpunkt auf Privatsphäre.
+ Er unterstützt Ende-zu-Ende Verschlüsselung mit OMEMO und OpenPGP und enthält Privatsphäre-Einstellungen zu Lesebestätigungen und Tippbenachrichtigungen.
+ Dino ruft Gesprächsverläufe vom Server ab und synchronisiert Nachrichten mit anderen Geräten.
+ </description>
+ <description xml:lang="ca">
+ Dino és un client de xat lliure i modern per a l'escriptori. Està centrat en proveir una experiència neta i fiable de Jabber/XMPP, sempre tenint en compte la vostra privacitat.
+ Implementa xifratge punt a punt amb OMEMO i OpenPGP, i permet configurar funcionalitats relacionades amb la privacitat com per exemple rebuts de lectura i notificacions d'escriptura.
+ Dino recupera l'historial del servidor i sincronitza els missatges amb altres dispositius.
+ </description>
+ <description xml:lang="ar">
+ دينو برنامج حديث ومفتوح المصدر للدردشة صُمّم لسطح المكتب. ويُركّز علي تقديم تجربة نظيفة وموثوق منها لجابر/XMPP مع أخذ خصوصيتكم بعين الإعتبار.
+ وهو يدعم التشفير بواسطة OMEMO و OpenPGP يسمح بإعداد ميزات الخصوصية كالإيصالات المقروءة والإخطارات عند الكتابة.
+ يقوم دينو بجلب السِجلّ مِن السيرفر ثم يُزامِن الرسائل مع الأجهزة الأخرى.
+ </description>
+ <homepage rdf:resource="https://dino.im/"/>
+ <wiki rdf:resource="https://github.com/dino/dino/wiki"/>
+ <bug-database rdf:resource="https://github.com/dino/dino/issues"/>
+ <category rdf:resource="http://api.gnome.org/doap-extensions#apps"/>
+ <license rdf:resource="http://usefulinc.com/doap/licenses/gplv3"/>
<programming-language>Vala</programming-language>
-
+ <os>Linux</os>
+ <os>FreeBSD</os>
+ <repository>
+ <GitRepository>
+ <location rdf:resource="https://github.com/dino/dino.git"/>
+ <browse rdf:resource="https://github.com/dino/dino/"/>
+ </GitRepository>
+ </repository>
+ <implements rdf:resource="https://xmpp.org/rfcs/rfc6120.html"/>
+ <implements rdf:resource="https://xmpp.org/rfcs/rfc6121.html"/>
+ <implements rdf:resource="https://xmpp.org/rfcs/rfc6122.html"/>
+ <implements rdf:resource="https://xmpp.org/rfcs/rfc7590.html"/>
+ <software xmlns="https://linkmauve.fr/ns/xmpp-doap#">
+ <Client>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0004.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0027.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0030.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0045.html"/>
+ <status>partial</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0049.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0054.html"/>
+ <status>partial</status>
+ <note>Only for viewing avatars</note>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0060.html"/>
+ <status>partial</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0066.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0077.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0082.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0084.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0085.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0115.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0163.html"/>
+ <status>partial</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0184.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0191.html"/>
+ <status>partial</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0198.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0199.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0203.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0280.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0313.html"/>
+ <status>partial</status>
+ <note>Not for MUCs</note>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0333.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0363.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0368.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0380.html"/>
+ <status>partial</status>
+ <note>Only for outgoing messages</note>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0384.html"/>
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ </Client>
+ </software>
</Project>
diff --git a/dino.doap.in b/dino.doap.in
new file mode 100644
index 00000000..b391091e
--- /dev/null
+++ b/dino.doap.in
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
+ xmlns:foaf="http://xmlns.com/foaf/0.1/"
+ xmlns="http://usefulinc.com/ns/doap#">
+
+ <name>Dino</name>
+ <short-name>dino</short-name>
+
+ <shortdesc xml:lang="en">Modern XMPP Chat Client</shortdesc>
+ <description xml:lang="en">
+ Dino is a modern open-source chat client for the desktop. It focuses on providing a clean and reliable Jabber/XMPP experience while having your privacy in mind.
+ It supports end-to-end encryption with OMEMO and OpenPGP and allows configuring privacy-related features such as read receipts and typing notifications.
+ Dino fetches history from the server and synchronizes messages with other devices.
+ </description>
+
+ <homepage rdf:resource="https://dino.im/" />
+ <wiki rdf:resource="https://github.com/dino/dino/wiki" />
+ <bug-database rdf:resource="https://github.com/dino/dino/issues" />
+ <category rdf:resource="http://api.gnome.org/doap-extensions#apps" />
+ <license rdf:resource="http://usefulinc.com/doap/licenses/gplv3" />
+
+ <programming-language>Vala</programming-language>
+ <os>Linux</os>
+ <os>FreeBSD</os>
+
+ <repository>
+ <GitRepository>
+ <location rdf:resource="https://github.com/dino/dino.git" />
+ <browse rdf:resource="https://github.com/dino/dino/" />
+ </GitRepository>
+ </repository>
+
+ <implements rdf:resource="https://xmpp.org/rfcs/rfc6120.html" />
+ <implements rdf:resource="https://xmpp.org/rfcs/rfc6121.html" />
+ <implements rdf:resource="https://xmpp.org/rfcs/rfc6122.html" />
+ <implements rdf:resource="https://xmpp.org/rfcs/rfc7590.html" />
+
+ <software xmlns="https://linkmauve.fr/ns/xmpp-doap#">
+ <Client>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0004.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0027.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0030.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0045.html" />
+ <status>partial</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0049.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0054.html" />
+ <status>partial</status>
+ <note>Only for viewing avatars</note>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0060.html" />
+ <status>partial</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0066.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0077.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0082.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0084.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0085.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0115.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0163.html" />
+ <status>partial</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0184.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0191.html" />
+ <status>partial</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0198.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0199.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0203.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0280.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0313.html" />
+ <status>partial</status>
+ <note>Not for MUCs</note>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0333.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0363.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0368.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0380.html" />
+ <status>partial</status>
+ <note>Only for outgoing messages</note>
+ </SupportedXep>
+ </supports>
+ <supports>
+ <SupportedXep>
+ <xep rdf:resource="https://xmpp.org/extensions/xep-0384.html" />
+ <status>complete</status>
+ </SupportedXep>
+ </supports>
+ </Client>
+ </software>
+</Project>
diff --git a/libdino/src/service/avatar_manager.vala b/libdino/src/service/avatar_manager.vala
index c9c078ab..a47273ee 100644
--- a/libdino/src/service/avatar_manager.vala
+++ b/libdino/src/service/avatar_manager.vala
@@ -23,6 +23,7 @@ public class AvatarManager : StreamInteractionModule, Object {
private HashMap<Jid, string> vcard_avatars = new HashMap<Jid, string>(Jid.hash_func, Jid.equals_func);
private AvatarStorage avatar_storage = new AvatarStorage(get_storage_dir());
private HashMap<string, Pixbuf> cached_pixbuf = new HashMap<string, Pixbuf>();
+ private HashMap<string, Gee.List<SourceFuncWrapper>> pending_pixbuf = new HashMap<string, Gee.List<SourceFuncWrapper>>();
private const int MAX_PIXEL = 192;
public static void start(StreamInteractor stream_interactor, Database db) {
@@ -50,12 +51,21 @@ public class AvatarManager : StreamInteractionModule, Object {
if (cached_pixbuf.has_key(hash)) {
return cached_pixbuf[hash];
}
+ if (pending_pixbuf.has_key(hash)) {
+ pending_pixbuf[hash].add(new SourceFuncWrapper(get_avatar_by_hash.callback));
+ yield;
+ return cached_pixbuf[hash];
+ }
+ pending_pixbuf[hash] = new ArrayList<SourceFuncWrapper>();
Pixbuf? image = yield avatar_storage.get_image(hash);
if (image != null) {
cached_pixbuf[hash] = image;
} else {
db.avatar.delete().with(db.avatar.hash, "=", hash).perform();
}
+ foreach (SourceFuncWrapper sfw in pending_pixbuf[hash]) {
+ sfw.sfun();
+ }
return image;
}
diff --git a/libdino/src/service/avatar_storage.vala b/libdino/src/service/avatar_storage.vala
index d6bdd30e..26c98e12 100644
--- a/libdino/src/service/avatar_storage.vala
+++ b/libdino/src/service/avatar_storage.vala
@@ -33,7 +33,7 @@ public class AvatarStorage : Xep.PixbufStorage, Object {
File file = File.new_for_path(Path.build_filename(folder, id));
FileInputStream stream = yield file.read_async();
- uint8 fbuf[100];
+ uint8 fbuf[1024];
size_t size;
Checksum checksum = new Checksum (ChecksumType.SHA1);
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
index dead5348..278f3360 100644
--- a/main/CMakeLists.txt
+++ b/main/CMakeLists.txt
@@ -33,6 +33,7 @@ set(RESOURCE_LIST
contact_details_dialog.ui
conversation_list_titlebar.ui
conversation_list_titlebar_csd.ui
+ emojichooser.ui
global_search.ui
conversation_selector/chat_row_tooltip.ui
conversation_selector/conversation_row.ui
@@ -134,6 +135,7 @@ CUSTOM_VAPIS
${CMAKE_BINARY_DIR}/exports/xmpp-vala.vapi
${CMAKE_BINARY_DIR}/exports/qlite.vapi
${CMAKE_BINARY_DIR}/exports/dino_internal.vapi
+ vapi/emojichooser.vapi
PACKAGES
${MAIN_PACKAGES}
${MAIN_EXTRA_PACKAGES}
@@ -144,8 +146,9 @@ OPTIONS
)
add_definitions(${VALA_CFLAGS} -DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\" -DLOCALE_INSTALL_DIR=\"${LOCALE_INSTALL_DIR}\")
-add_executable(dino ${MAIN_VALA_C} ${MAIN_GRESOURCES_TARGET})
+add_executable(dino ${MAIN_VALA_C} ${MAIN_GRESOURCES_TARGET} src/emojichooser.c)
add_dependencies(dino ${GETTEXT_PACKAGE}-translations)
+target_include_directories(dino PRIVATE src)
target_link_libraries(dino libdino ${MAIN_PACKAGES})
if(WIN32)
diff --git a/main/data/emojichooser.ui b/main/data/emojichooser.ui
new file mode 100644
index 00000000..d47a2c22
--- /dev/null
+++ b/main/data/emojichooser.ui
@@ -0,0 +1,410 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk30">
+ <template class="DinoEmojiChooser" parent="GtkPopover">
+ <property name="modal">1</property>
+ <style>
+ <class name="emoji-picker"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="box">
+ <property name="orientation">vertical</property>
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkSearchEntry" id="search_entry">
+ <property name="visible">1</property>
+ <property name="input-hints">no-emoji</property>
+ <signal name="search-changed" handler="search_changed"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">1</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window">
+ <property name="visible">1</property>
+ <property name="vexpand">1</property>
+ <property name="hscrollbar-policy">never</property>
+ <property name="min-content-height">250</property>
+ <style>
+ <class name="view"/>
+ </style>
+ <child>
+ <object class="GtkBox" id="emoji_box">
+ <property name="visible">1</property>
+ <property name="orientation">vertical</property>
+ <property name="margin">6</property>
+ <property name="spacing">6</property>
+ <!-- Remember to keep headings here in sync with button tooltips below -->
+ <child>
+ <object class="GtkFlowBox" id="recent.box">
+ <property name="visible">1</property>
+ <property name="homogeneous">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="child-activated" handler="emoji_activated"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="people.heading">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Smileys &amp; People</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFlowBox" id="people.box">
+ <property name="visible">1</property>
+ <property name="homogeneous">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="child-activated" handler="emoji_activated"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="body.heading">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Body &amp; Clothing</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFlowBox" id="body.box">
+ <property name="visible">1</property>
+ <property name="homogeneous">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="child-activated" handler="emoji_activated"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="nature.heading">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Animals &amp; Nature</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFlowBox" id="nature.box">
+ <property name="visible">1</property>
+ <property name="homogeneous">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="child-activated" handler="emoji_activated"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="food.heading">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Food &amp; Drink</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFlowBox" id="food.box">
+ <property name="visible">1</property>
+ <property name="homogeneous">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="child-activated" handler="emoji_activated"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="travel.heading">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Travel &amp; Places</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFlowBox" id="travel.box">
+ <property name="visible">1</property>
+ <property name="homogeneous">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="child-activated" handler="emoji_activated"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="activities.heading">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Activities</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFlowBox" id="activities.box">
+ <property name="visible">1</property>
+ <property name="homogeneous">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="child-activated" handler="emoji_activated"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="objects.heading">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes" context="emoji category">Objects</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFlowBox" id="objects.box">
+ <property name="visible">1</property>
+ <property name="homogeneous">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="child-activated" handler="emoji_activated"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="symbols.heading">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Symbols</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFlowBox" id="symbols.box">
+ <property name="visible">1</property>
+ <property name="homogeneous">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="child-activated" handler="emoji_activated"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="flags.heading">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Flags</property>
+ <property name="xalign">0</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkFlowBox" id="flags.box">
+ <property name="visible">1</property>
+ <property name="homogeneous">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="child-activated" handler="emoji_activated"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">1</property>
+ <!-- Remember to keep tooltips here in sync with section headings above -->
+ <child>
+ <object class="GtkButton" id="recent.button">
+ <property name="visible">1</property>
+ <property name="relief">none</property>
+ <property name="tooltip-text" translatable="yes">Recent</property>
+ <style>
+ <class name="emoji-section"/>
+ </style>
+ <child>
+ <object class="GtkImage" id="recent.icon">
+ <property name="visible">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="people.button">
+ <property name="visible">1</property>
+ <property name="relief">none</property>
+ <property name="tooltip-text" translatable="yes">Smileys &amp; People</property>
+ <style>
+ <class name="emoji-section"/>
+ </style>
+ <child>
+ <object class="GtkImage" id="people.icon">
+ <property name="visible">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="body.button">
+ <property name="visible">1</property>
+ <property name="relief">none</property>
+ <property name="tooltip-text" translatable="yes">Body &amp; Clothing</property>
+ <style>
+ <class name="emoji-section"/>
+ </style>
+ <child>
+ <object class="GtkImage" id="body.icon">
+ <property name="visible">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="nature.button">
+ <property name="visible">1</property>
+ <property name="relief">none</property>
+ <property name="tooltip-text" translatable="yes">Animals &amp; Nature</property>
+ <style>
+ <class name="emoji-section"/>
+ </style>
+ <child>
+ <object class="GtkImage" id="nature.icon">
+ <property name="visible">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="food.button">
+ <property name="visible">1</property>
+ <property name="relief">none</property>
+ <property name="tooltip-text" translatable="yes">Food &amp; Drink</property>
+ <style>
+ <class name="emoji-section"/>
+ </style>
+ <child>
+ <object class="GtkImage" id="food.icon">
+ <property name="visible">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="travel.button">
+ <property name="visible">1</property>
+ <property name="relief">none</property>
+ <property name="tooltip-text" translatable="yes">Travel &amp; Places</property>
+ <style>
+ <class name="emoji-section"/>
+ </style>
+ <child>
+ <object class="GtkImage" id="travel.icon">
+ <property name="visible">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="activities.button">
+ <property name="visible">1</property>
+ <property name="relief">none</property>
+ <property name="tooltip-text" translatable="yes">Activities</property>
+ <style>
+ <class name="emoji-section"/>
+ </style>
+ <child>
+ <object class="GtkImage" id="activities.icon">
+ <property name="visible">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="objects.button">
+ <property name="visible">1</property>
+ <property name="relief">none</property>
+ <property name="tooltip-text" translatable="yes" context="emoji category">Objects</property>
+ <style>
+ <class name="emoji-section"/>
+ </style>
+ <child>
+ <object class="GtkImage" id="objects.icon">
+ <property name="visible">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="symbols.button">
+ <property name="visible">1</property>
+ <property name="relief">none</property>
+ <property name="tooltip-text" translatable="yes">Symbols</property>
+ <style>
+ <class name="emoji-section"/>
+ </style>
+ <child>
+ <object class="GtkImage" id="symbols.icon">
+ <property name="visible">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="flags.button">
+ <property name="visible">1</property>
+ <property name="relief">none</property>
+ <property name="tooltip-text" translatable="yes">Flags</property>
+ <style>
+ <class name="emoji-section"/>
+ </style>
+ <child>
+ <object class="GtkImage" id="flags.icon">
+ <property name="visible">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">list</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid">
+ <property name="visible">1</property>
+ <property name="row-spacing">12</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">1</property>
+ <property name="icon-name">edit-find-symbolic</property>
+ <property name="pixel-size">72</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">No Results Found</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.44"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">1</property>
+ <property name="label" translatable="yes">Try a different search</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">empty</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/main/data/im.dino.Dino.appdata.xml b/main/data/im.dino.Dino.appdata.xml
index ba138e6c..0828f4c9 100644
--- a/main/data/im.dino.Dino.appdata.xml
+++ b/main/data/im.dino.Dino.appdata.xml
@@ -7,13 +7,15 @@
<name>Dino</name>
<summary>Modern XMPP Chat Client</summary>
<summary xml:lang="zh_Hans">现代 XMPP 聊天客户端</summary>
+ <summary xml:lang="ru">Современный чат-клиент на XMPP</summary>
<summary xml:lang="ro">Client XMPP de discuții modern</summary>
<summary xml:lang="pt_BR">Moderno cliente de chat XMPP</summary>
- <summary xml:lang="pl">Nowoczesny klient Chatu XMPP</summary>
+ <summary xml:lang="pl">Nowoczesny komunikator XMPP</summary>
<summary xml:lang="nl_BE">Modernen XMPP-chatcliënt</summary>
<summary xml:lang="nl">Moderne XMPP-chatcliënt</summary>
<summary xml:lang="nb">Moderne XMPP-sludreklient</summary>
<summary xml:lang="lb">Modernen XMPP Chat Client</summary>
+ <summary xml:lang="ja">現代的な XMPP チャット クライアント</summary>
<summary xml:lang="it">Client di chat moderno per XMPP</summary>
<summary xml:lang="hu">Modern XMPP Üzenetküldő</summary>
<summary xml:lang="gl">Cliente moderno para parolas XMPP</summary>
@@ -28,7 +30,7 @@
<p xml:lang="ru">Dino - это современный клиент для чатов с открытым исходным кодом, направленный на надёжное и приватное использование Jabber/XMPP на персональных компьютерах.</p>
<p xml:lang="ro">Dino este un client de chat modern, cu sursă deschisă, pentru calculatoare. Se concentrează să furnizeze o experiență Jabber/XMPP clară și fiabilă, ținând cont de confidențialitatea dumneavoastră.</p>
<p xml:lang="pt_BR">Dino é um moderno chat de código aberto para desktop. Ele é focado em prover uma transparente e confiável experiência Jabber/XMPP, tendo em mente a sua privacidade.</p>
- <p xml:lang="pl">Dino jest nowoczesnym klientem chat z otwartym źródłem w wersji na komputery. Skupia się na prostej obsłudze Jabber/XMPP dbając o twoją prywatność.</p>
+ <p xml:lang="pl">Dino jest nowoczesnym, otwartym komunikatorem. Skupia się na prostej obsłudze sieci Jabber/XMPP dbając o twoją prywatność.</p>
<p xml:lang="nl_BE">Dino is een moderne, vrije chattoepassing voor uw bureaublad. Ze biedt een eenvoudige en betrouwbare Jabber/XMPP-ervaring, met uw privacy in het achterhoofd.</p>
<p xml:lang="nl">Dino is een moderne, vrije chattoepassing voor uw bureaublad. Ze biedt een eenvoudige en betrouwbare Jabber/XMPP-ervaring, met uw privacy in het achterhoofd.</p>
<p xml:lang="nb">Dino er en moderne friporg-sludringsklient for skrivebordet. Det fokuserer på rask og pålitelig XMPP-opplevelse, samtidig som det hegner om personvernet.</p>
@@ -48,6 +50,7 @@
<p xml:lang="ru">Он поддерживает сквозное шифрование через OMEMO и OpenPGP и позволяет настраивать такие функции, как уведомления о прочтении и наборе сообщений.</p>
<p xml:lang="ro">Suportă criptare de la un capăt la altul prin intermediul OMEMO și OpenPGP, și permite configurarea caracteristicilor legate de confidențialitate precum trimiterea notificărilor de primire și tastare.</p>
<p xml:lang="pt_BR">Suporte criptografia ponta a ponta com OMEMO e OpenPGP e permite configurar privacidade—características relacionadas às notificações de leitura, recebimento e escrita.</p>
+ <p xml:lang="pl">Dino obsługuje szyfrowanie end-to-end za pomocą OMEMO i OpenPGP, a także daje kontrolę nad funkcjami wpływającymi na prywatność, takimi jak powiadomienia o pisaniu czy odczytaniu wiadomości.</p>
<p xml:lang="nl_BE">Ze ondersteunt eind-tot-eind-versleuteling met OMEMO en OpenPGP, en laat u toe privacygerelateerde functies, gelijk leesbevestigingen en typmeldingen, in te stellen.</p>
<p xml:lang="nl">Ze ondersteunt eind-tot-eind-versleuteling met OMEMO en OpenPGP, en staat u toe privacy-gerelateerde functies, zoals leesbevestigingen en typmeldingen, in te stellen.</p>
<p xml:lang="nb">Det støtter ende-til-ende -kryptering med OMEMO og OpenPGP, og tillater oppsett av personvernsrelaterte funksjoner som meldingskvitteringer og skrivevarsling.</p>
@@ -67,10 +70,12 @@
<p xml:lang="ru">Dino загружает историю с сервера и синхронизирует сообщения с другими устройствами.</p>
<p xml:lang="ro">Dino preia istoricul discuțiilor de pe server și sincronizează mesajele cu celelalte dispozitive.</p>
<p xml:lang="pt_BR">Dino obtém o histórico do servidor e sincroniza mensagens com outros dispositivos.</p>
+ <p xml:lang="pl">Dino pobiera historię rozmów z serwera i synchronizuje wiadomości z innymi urządzeniami.</p>
<p xml:lang="nl_BE">Dino haalt de geschiedenis op van de server en synchroniseert berichten met andere apparaten.</p>
<p xml:lang="nl">Dino haalt de geschiedenis op van de server en synchroniseert berichten met andere apparaten.</p>
<p xml:lang="nb">Dino henter historikk fra tjeneren og synkroniserer meldinger med andre enheter.</p>
<p xml:lang="lb">Dino rifft  Gespréichverläf vum Server of a synchroniséiert Noriichte mat anere Geräter.</p>
+ <p xml:lang="ja">Dino はサーバーから履歴を取得し、ほかのデバイスとメッセージを同期します。</p>
<p xml:lang="it">Dino recupera la cronologia dal server e sincronizza i messaggi con gli altri dispositivi.</p>
<p xml:lang="hu">A Dino lekéri a chat előzményeket a szerverről, és szinkronizálja az üzeneteket a többi eszközzel.</p>
<p xml:lang="gl">Dino obtén o histórico dende o servidor e sincroniza as mensaxes con outros dispositivos.</p>
@@ -94,6 +99,7 @@
<caption xml:lang="nl">Hoofdvenster met gesprekken</caption>
<caption xml:lang="nb">Hovedvindu med samtaler</caption>
<caption xml:lang="lb">Haaptfënster mat den Conversatiounen</caption>
+ <caption xml:lang="ja">会話中のメインウィンドウ</caption>
<caption xml:lang="it">La finestra principale con le conversazioni</caption>
<caption xml:lang="hu">A fő ablak a beszélgetésekkel</caption>
<caption xml:lang="gl">Lapela principal con parolas</caption>
@@ -117,9 +123,10 @@
<caption xml:lang="nl">Gesprek beginnen</caption>
<caption xml:lang="nb">Start samtale</caption>
<caption xml:lang="lb">Konversatioun starten</caption>
+ <caption xml:lang="ja">会話を開始</caption>
<caption xml:lang="it">Inizia una Conversazione</caption>
<caption xml:lang="hu">Csevegés kezdeményezése</caption>
- <caption xml:lang="gl">Comezar parola</caption>
+ <caption xml:lang="gl">Comezar conversa</caption>
<caption xml:lang="fr">Commencer une discussion</caption>
<caption xml:lang="fi">Aloita keskustelu</caption>
<caption xml:lang="eu">Elkarrizketa hasi</caption>
@@ -139,7 +146,7 @@
<caption xml:lang="nl">Contactgegevens</caption>
<caption xml:lang="nb">Kontaktdetaljer</caption>
<caption xml:lang="lb">Kontaktdetailer</caption>
- <caption xml:lang="ja">相手先の詳細</caption>
+ <caption xml:lang="ja">連絡先の詳細</caption>
<caption xml:lang="it">Dettagli del contatto</caption>
<caption xml:lang="hu">Felhasználó információ</caption>
<caption xml:lang="gl">Detalles do contacto</caption>
diff --git a/main/src/emojichooser.c b/main/src/emojichooser.c
new file mode 100644
index 00000000..f8c0c51c
--- /dev/null
+++ b/main/src/emojichooser.c
@@ -0,0 +1,820 @@
+/* gtkemojichooser.c: An Emoji chooser widget
+ * Copyright 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
+ * License as published by the Free Software Foundation; either
+ * version 2 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "emojichooser.h"
+
+#define BOX_SPACE 6
+
+typedef struct {
+ GtkWidget *box;
+ GtkWidget *heading;
+ GtkWidget *button;
+ const char *first;
+ gunichar label;
+ gboolean empty;
+} EmojiSection;
+
+struct _DinoEmojiChooser
+{
+ GtkPopover parent_instance;
+
+ GtkWidget *search_entry;
+ GtkWidget *stack;
+ GtkWidget *scrolled_window;
+
+ int emoji_max_width;
+
+ EmojiSection recent;
+ EmojiSection people;
+ EmojiSection body;
+ EmojiSection nature;
+ EmojiSection food;
+ EmojiSection travel;
+ EmojiSection activities;
+ EmojiSection objects;
+ EmojiSection symbols;
+ EmojiSection flags;
+
+ GtkGesture *recent_long_press;
+ GtkGesture *recent_multi_press;
+ GtkGesture *people_long_press;
+ GtkGesture *people_multi_press;
+ GtkGesture *body_long_press;
+ GtkGesture *body_multi_press;
+
+ GVariant *data;
+ GtkWidget *box;
+ GVariantIter *iter;
+ guint populate_idle;
+
+ GSettings *settings;
+};
+
+struct _DinoEmojiChooserClass {
+ GtkPopoverClass parent_class;
+ gboolean (* popover_button_release_event) (GtkWidget *widget,
+ GdkEventButton *event);
+};
+
+enum {
+ EMOJI_PICKED,
+ LAST_SIGNAL
+};
+
+static int signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (DinoEmojiChooser, dino_emoji_chooser, GTK_TYPE_POPOVER)
+
+static void
+dino_emoji_chooser_finalize (GObject *object)
+{
+ DinoEmojiChooser *chooser = DINO_EMOJI_CHOOSER (object);
+
+ if (chooser->populate_idle)
+ g_source_remove (chooser->populate_idle);
+
+ g_variant_unref (chooser->data);
+ g_object_unref (chooser->settings);
+
+ g_clear_object (&chooser->recent_long_press);
+ g_clear_object (&chooser->recent_multi_press);
+ g_clear_object (&chooser->people_long_press);
+ g_clear_object (&chooser->people_multi_press);
+ g_clear_object (&chooser->body_long_press);
+ g_clear_object (&chooser->body_multi_press);
+
+ G_OBJECT_CLASS (dino_emoji_chooser_parent_class)->finalize (object);
+}
+
+static void
+scroll_to_section (GtkButton *button,
+ gpointer data)
+{
+ EmojiSection *section = data;
+ DinoEmojiChooser *chooser;
+ GtkAdjustment *adj;
+ GtkAllocation alloc = { 0, 0, 0, 0 };
+
+ chooser = DINO_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_EMOJI_CHOOSER));
+
+ adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
+
+ if (section->heading)
+ gtk_widget_get_allocation (section->heading, &alloc);
+
+ gtk_adjustment_set_value (adj, alloc.y - BOX_SPACE);
+}
+
+static void
+add_emoji (GtkWidget *box,
+ gboolean prepend,
+ GVariant *item,
+ gunichar modifier,
+ DinoEmojiChooser *chooser);
+
+#define MAX_RECENT (7*3)
+
+static void
+populate_recent_section (DinoEmojiChooser *chooser)
+{
+ GVariant *variant;
+ GVariant *item;
+ GVariantIter iter;
+ gboolean empty = FALSE;
+
+ variant = g_settings_get_value (chooser->settings, "recent-emoji");
+ g_variant_iter_init (&iter, variant);
+ while ((item = g_variant_iter_next_value (&iter)))
+ {
+ GVariant *emoji_data;
+ gunichar modifier;
+
+ emoji_data = g_variant_get_child_value (item, 0);
+ g_variant_get_child (item, 1, "u", &modifier);
+ add_emoji (chooser->recent.box, FALSE, emoji_data, modifier, chooser);
+ g_variant_unref (emoji_data);
+ g_variant_unref (item);
+ empty = FALSE;
+ }
+
+ if (!empty)
+ {
+ gtk_widget_show (chooser->recent.box);
+ gtk_widget_set_sensitive (chooser->recent.button, TRUE);
+ }
+ g_variant_unref (variant);
+}
+
+static void
+add_recent_item (DinoEmojiChooser *chooser,
+ GVariant *item,
+ gunichar modifier)
+{
+ GList *children, *l;
+ int i;
+ GVariantBuilder builder;
+
+ g_variant_ref (item);
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a((auss)u)"));
+ g_variant_builder_add (&builder, "(@(auss)u)", item, modifier);
+
+ children = gtk_container_get_children (GTK_CONTAINER (chooser->recent.box));
+ for (l = children, i = 1; l; l = l->next, i++)
+ {
+ GVariant *item2 = g_object_get_data (G_OBJECT (l->data), "emoji-data");
+ gunichar modifier2 = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (l->data), "modifier"));
+
+ if (modifier == modifier2 && g_variant_equal (item, item2))
+ {
+ gtk_widget_destroy (GTK_WIDGET (l->data));
+ i--;
+ continue;
+ }
+ if (i >= MAX_RECENT)
+ {
+ gtk_widget_destroy (GTK_WIDGET (l->data));
+ continue;
+ }
+
+ g_variant_builder_add (&builder, "(@(auss)u)", item2, modifier2);
+ }
+ g_list_free (children);
+
+ add_emoji (chooser->recent.box, TRUE, item, modifier, chooser);
+
+ /* Enable recent */
+ gtk_widget_show (chooser->recent.box);
+ gtk_widget_set_sensitive (chooser->recent.button, TRUE);
+
+ g_settings_set_value (chooser->settings, "recent-emoji", g_variant_builder_end (&builder));
+
+ g_variant_unref (item);
+}
+
+static void
+emoji_activated (GtkFlowBox *box,
+ GtkFlowBoxChild *child,
+ gpointer data)
+{
+ DinoEmojiChooser *chooser = data;
+ char *text;
+ GtkWidget *ebox;
+ GtkWidget *label;
+ GVariant *item;
+ gunichar modifier;
+
+ gtk_popover_popdown (GTK_POPOVER (chooser));
+
+ ebox = gtk_bin_get_child (GTK_BIN (child));
+ label = gtk_bin_get_child (GTK_BIN (ebox));
+ text = g_strdup (gtk_label_get_label (GTK_LABEL (label)));
+
+ item = (GVariant*) g_object_get_data (G_OBJECT (child), "emoji-data");
+ modifier = (gunichar) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (child), "modifier"));
+ add_recent_item (chooser, item, modifier);
+
+ g_signal_emit (data, signals[EMOJI_PICKED], 0, text);
+ g_free (text);
+}
+
+static gboolean
+has_variations (GVariant *emoji_data)
+{
+ GVariant *codes;
+ int i;
+ gboolean has_variations;
+
+ has_variations = FALSE;
+ codes = g_variant_get_child_value (emoji_data, 0);
+ for (i = 0; i < g_variant_n_children (codes); i++)
+ {
+ gunichar code;
+ g_variant_get_child (codes, i, "u", &code);
+ if (code == 0)
+ {
+ has_variations = TRUE;
+ break;
+ }
+ }
+ g_variant_unref (codes);
+
+ return has_variations;
+}
+
+static void
+show_variations (DinoEmojiChooser *chooser,
+ GtkWidget *child)
+{
+ GtkWidget *popover;
+ GtkWidget *view;
+ GtkWidget *box;
+ GVariant *emoji_data;
+ GtkWidget *parent_popover;
+ gunichar modifier;
+
+ if (!child)
+ return;
+
+ emoji_data = (GVariant*) g_object_get_data (G_OBJECT (child), "emoji-data");
+ if (!emoji_data)
+ return;
+
+ if (!has_variations (emoji_data))
+ return;
+
+ parent_popover = gtk_widget_get_ancestor (child, GTK_TYPE_POPOVER);
+ popover = gtk_popover_new (child);
+ view = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_style_context_add_class (gtk_widget_get_style_context (view), "view");
+ box = gtk_flow_box_new ();
+ gtk_flow_box_set_homogeneous (GTK_FLOW_BOX (box), TRUE);
+ gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (box), 6);
+ gtk_flow_box_set_max_children_per_line (GTK_FLOW_BOX (box), 6);
+ gtk_flow_box_set_activate_on_single_click (GTK_FLOW_BOX (box), TRUE);
+ gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (box), GTK_SELECTION_NONE);
+ gtk_container_add (GTK_CONTAINER (popover), view);
+ gtk_container_add (GTK_CONTAINER (view), box);
+
+ g_signal_connect (box, "child-activated", G_CALLBACK (emoji_activated), parent_popover);
+
+ add_emoji (box, FALSE, emoji_data, 0, chooser);
+ for (modifier = 0x1f3fb; modifier <= 0x1f3ff; modifier++)
+ add_emoji (box, FALSE, emoji_data, modifier, chooser);
+
+ gtk_widget_show_all (view);
+ gtk_popover_popup (GTK_POPOVER (popover));
+}
+
+static void
+update_hover (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer data)
+{
+ if (event->type == GDK_ENTER_NOTIFY)
+ gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_PRELIGHT, FALSE);
+ else
+ gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_PRELIGHT);
+}
+
+static void
+long_pressed_cb (GtkGesture *gesture,
+ double x,
+ double y,
+ gpointer data)
+{
+ DinoEmojiChooser *chooser = data;
+ GtkWidget *box;
+ GtkWidget *child;
+
+ box = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+ child = GTK_WIDGET (gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (box), x, y));
+ show_variations (chooser, child);
+}
+
+static void
+pressed_cb (GtkGesture *gesture,
+ int n_press,
+ double x,
+ double y,
+ gpointer data)
+{
+ DinoEmojiChooser *chooser = data;
+ GtkWidget *box;
+ GtkWidget *child;
+
+ box = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+ child = GTK_WIDGET (gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (box), x, y));
+ show_variations (chooser, child);
+}
+
+static gboolean
+popup_menu (GtkWidget *widget,
+ gpointer data)
+{
+ DinoEmojiChooser *chooser = data;
+
+ show_variations (chooser, widget);
+ return TRUE;
+}
+
+static void
+add_emoji (GtkWidget *box,
+ gboolean prepend,
+ GVariant *item,
+ gunichar modifier,
+ DinoEmojiChooser *chooser)
+{
+ GtkWidget *child;
+ GtkWidget *ebox;
+ GtkWidget *label;
+ PangoAttrList *attrs;
+ GVariant *codes;
+ char text[64];
+ char *p = text;
+ int i;
+ PangoLayout *layout;
+ PangoRectangle rect;
+
+ codes = g_variant_get_child_value (item, 0);
+ for (i = 0; i < g_variant_n_children (codes); i++)
+ {
+ gunichar code;
+
+ g_variant_get_child (codes, i, "u", &code);
+ if (code == 0)
+ code = modifier;
+ if (code != 0)
+ p += g_unichar_to_utf8 (code, p);
+ }
+ g_variant_unref (codes);
+ p += g_unichar_to_utf8 (0xFE0F, p); /* U+FE0F is the Emoji variation selector */
+ p[0] = 0;
+
+ label = gtk_label_new (text);
+ attrs = pango_attr_list_new ();
+ pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE));
+ gtk_label_set_attributes (GTK_LABEL (label), attrs);
+ pango_attr_list_unref (attrs);
+
+ layout = gtk_label_get_layout (GTK_LABEL (label));
+ pango_layout_get_extents (layout, &rect, NULL);
+
+ /* Check for fallback rendering that generates too wide items */
+ if (pango_layout_get_unknown_glyphs_count (layout) > 0 ||
+ rect.width >= 1.5 * chooser->emoji_max_width)
+ {
+ gtk_widget_destroy (label);
+ return;
+ }
+
+ child = gtk_flow_box_child_new ();
+ gtk_style_context_add_class (gtk_widget_get_style_context (child), "emoji");
+ g_object_set_data_full (G_OBJECT (child), "emoji-data",
+ g_variant_ref (item),
+ (GDestroyNotify)g_variant_unref);
+ if (modifier != 0)
+ g_object_set_data (G_OBJECT (child), "modifier", GUINT_TO_POINTER (modifier));
+
+ ebox = gtk_event_box_new ();
+ gtk_widget_add_events (ebox, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
+ g_signal_connect (ebox, "enter-notify-event", G_CALLBACK (update_hover), FALSE);
+ g_signal_connect (ebox, "leave-notify-event", G_CALLBACK (update_hover), FALSE);
+ gtk_container_add (GTK_CONTAINER (child), ebox);
+ gtk_container_add (GTK_CONTAINER (ebox), label);
+ gtk_widget_show_all (child);
+
+ if (chooser)
+ g_signal_connect (child, "popup-menu", G_CALLBACK (popup_menu), chooser);
+
+ gtk_flow_box_insert (GTK_FLOW_BOX (box), child, prepend ? 0 : -1);
+}
+
+static gboolean
+populate_emoji_chooser (gpointer data)
+{
+ DinoEmojiChooser *chooser = data;
+ GBytes *bytes = NULL;
+ GVariant *item;
+ guint64 start, now;
+
+ start = g_get_monotonic_time ();
+
+ if (!chooser->data)
+ {
+ bytes = g_resources_lookup_data ("/org/gtk/libgtk/emoji/emoji.data", 0, NULL);
+ chooser->data = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE ("a(auss)"), bytes, TRUE));
+ }
+
+ if (!chooser->iter)
+ {
+ chooser->iter = g_variant_iter_new (chooser->data);
+ chooser->box = chooser->people.box;
+ }
+ while ((item = g_variant_iter_next_value (chooser->iter)))
+ {
+ const char *name;
+
+ g_variant_get_child (item, 1, "&s", &name);
+
+ if (strcmp (name, chooser->body.first) == 0)
+ chooser->box = chooser->body.box;
+ else if (strcmp (name, chooser->nature.first) == 0)
+ chooser->box = chooser->nature.box;
+ else if (strcmp (name, chooser->food.first) == 0)
+ chooser->box = chooser->food.box;
+ else if (strcmp (name, chooser->travel.first) == 0)
+ chooser->box = chooser->travel.box;
+ else if (strcmp (name, chooser->activities.first) == 0)
+ chooser->box = chooser->activities.box;
+ else if (strcmp (name, chooser->objects.first) == 0)
+ chooser->box = chooser->objects.box;
+ else if (strcmp (name, chooser->symbols.first) == 0)
+ chooser->box = chooser->symbols.box;
+ else if (strcmp (name, chooser->flags.first) == 0)
+ chooser->box = chooser->flags.box;
+
+ add_emoji (chooser->box, FALSE, item, 0, chooser);
+ g_variant_unref (item);
+
+ now = g_get_monotonic_time ();
+ if (now > start + 8000)
+ return G_SOURCE_CONTINUE;
+ }
+
+ /* We scroll to the top on show, so check the right button for the 1st time */
+ gtk_widget_set_state_flags (chooser->recent.button, GTK_STATE_FLAG_CHECKED, FALSE);
+
+ g_variant_iter_free (chooser->iter);
+ chooser->iter = NULL;
+ chooser->box = NULL;
+ chooser->populate_idle = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+adj_value_changed (GtkAdjustment *adj,
+ gpointer data)
+{
+ DinoEmojiChooser *chooser = data;
+ double value = gtk_adjustment_get_value (adj);
+ EmojiSection const *sections[] = {
+ &chooser->recent,
+ &chooser->people,
+ &chooser->body,
+ &chooser->nature,
+ &chooser->food,
+ &chooser->travel,
+ &chooser->activities,
+ &chooser->objects,
+ &chooser->symbols,
+ &chooser->flags,
+ };
+ EmojiSection const *select_section = sections[0];
+ gsize i;
+
+ /* Figure out which section the current scroll position is within */
+ for (i = 0; i < G_N_ELEMENTS (sections); ++i)
+ {
+ EmojiSection const *section = sections[i];
+ GtkAllocation alloc;
+
+ if (section->heading)
+ gtk_widget_get_allocation (section->heading, &alloc);
+ else
+ gtk_widget_get_allocation (section->box, &alloc);
+
+ if (value < alloc.y - BOX_SPACE)
+ break;
+
+ select_section = section;
+ }
+
+ /* Un/Check the section buttons accordingly */
+ for (i = 0; i < G_N_ELEMENTS (sections); ++i)
+ {
+ EmojiSection const *section = sections[i];
+
+ if (section == select_section)
+ gtk_widget_set_state_flags (section->button, GTK_STATE_FLAG_CHECKED, FALSE);
+ else
+ gtk_widget_unset_state_flags (section->button, GTK_STATE_FLAG_CHECKED);
+ }
+}
+
+static gboolean
+filter_func (GtkFlowBoxChild *child,
+ gpointer data)
+{
+ EmojiSection *section = data;
+ DinoEmojiChooser *chooser;
+ GVariant *emoji_data;
+ const char *text;
+ const char *name;
+ gboolean res;
+
+ res = TRUE;
+
+ chooser = DINO_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (child), GTK_TYPE_EMOJI_CHOOSER));
+ text = gtk_entry_get_text (GTK_ENTRY (chooser->search_entry));
+ emoji_data = (GVariant *) g_object_get_data (G_OBJECT (child), "emoji-data");
+
+ if (text[0] == 0)
+ goto out;
+
+ if (!emoji_data)
+ goto out;
+
+ g_variant_get_child (emoji_data, 1, "&s", &name);
+ res = g_str_match_string (text, name, TRUE);
+
+out:
+ if (res)
+ section->empty = FALSE;
+
+ return res;
+}
+
+static void
+invalidate_section (EmojiSection *section)
+{
+ section->empty = TRUE;
+ gtk_flow_box_invalidate_filter (GTK_FLOW_BOX (section->box));
+}
+
+static void
+update_headings (DinoEmojiChooser *chooser)
+{
+ gtk_widget_set_visible (chooser->people.heading, !chooser->people.empty);
+ gtk_widget_set_visible (chooser->people.box, !chooser->people.empty);
+ gtk_widget_set_visible (chooser->body.heading, !chooser->body.empty);
+ gtk_widget_set_visible (chooser->body.box, !chooser->body.empty);
+ gtk_widget_set_visible (chooser->nature.heading, !chooser->nature.empty);
+ gtk_widget_set_visible (chooser->nature.box, !chooser->nature.empty);
+ gtk_widget_set_visible (chooser->food.heading, !chooser->food.empty);
+ gtk_widget_set_visible (chooser->food.box, !chooser->food.empty);
+ gtk_widget_set_visible (chooser->travel.heading, !chooser->travel.empty);
+ gtk_widget_set_visible (chooser->travel.box, !chooser->travel.empty);
+ gtk_widget_set_visible (chooser->activities.heading, !chooser->activities.empty);
+ gtk_widget_set_visible (chooser->activities.box, !chooser->activities.empty);
+ gtk_widget_set_visible (chooser->objects.heading, !chooser->objects.empty);
+ gtk_widget_set_visible (chooser->objects.box, !chooser->objects.empty);
+ gtk_widget_set_visible (chooser->symbols.heading, !chooser->symbols.empty);
+ gtk_widget_set_visible (chooser->symbols.box, !chooser->symbols.empty);
+ gtk_widget_set_visible (chooser->flags.heading, !chooser->flags.empty);
+ gtk_widget_set_visible (chooser->flags.box, !chooser->flags.empty);
+
+ if (chooser->recent.empty && chooser->people.empty &&
+ chooser->body.empty && chooser->nature.empty &&
+ chooser->food.empty && chooser->travel.empty &&
+ chooser->activities.empty && chooser->objects.empty &&
+ chooser->symbols.empty && chooser->flags.empty)
+ gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), "empty");
+ else
+ gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), "list");
+}
+
+static void
+search_changed (GtkEntry *entry,
+ gpointer data)
+{
+ DinoEmojiChooser *chooser = data;
+
+ invalidate_section (&chooser->recent);
+ invalidate_section (&chooser->people);
+ invalidate_section (&chooser->body);
+ invalidate_section (&chooser->nature);
+ invalidate_section (&chooser->food);
+ invalidate_section (&chooser->travel);
+ invalidate_section (&chooser->activities);
+ invalidate_section (&chooser->objects);
+ invalidate_section (&chooser->symbols);
+ invalidate_section (&chooser->flags);
+
+ update_headings (chooser);
+}
+
+static void
+setup_section (DinoEmojiChooser *chooser,
+ EmojiSection *section,
+ const char *first,
+ const char *icon)
+{
+ GtkAdjustment *adj;
+ GtkWidget *image;
+
+ section->first = first;
+
+ image = gtk_bin_get_child (GTK_BIN (section->button));
+ gtk_image_set_from_icon_name (GTK_IMAGE (image), icon, GTK_ICON_SIZE_BUTTON);
+
+ adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
+
+ gtk_container_set_focus_vadjustment (GTK_CONTAINER (section->box), adj);
+ gtk_flow_box_set_filter_func (GTK_FLOW_BOX (section->box), filter_func, section, NULL);
+ g_signal_connect (section->button, "clicked", G_CALLBACK (scroll_to_section), section);
+}
+
+static void
+dino_emoji_chooser_init (DinoEmojiChooser *chooser)
+{
+ GtkAdjustment *adj;
+
+ chooser->settings = g_settings_new ("org.gtk.Settings.EmojiChooser");
+
+ gtk_widget_init_template (GTK_WIDGET (chooser));
+
+ /* Get a reasonable maximum width for an emoji. We do this to
+ * skip overly wide fallback rendering for certain emojis the
+ * font does not contain and therefore end up being rendered
+ * as multiply glyphs.
+ */
+ {
+ PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (chooser), "🙂");
+ PangoAttrList *attrs;
+ PangoRectangle rect;
+
+ attrs = pango_attr_list_new ();
+ pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE));
+ pango_layout_set_attributes (layout, attrs);
+ pango_attr_list_unref (attrs);
+
+ pango_layout_get_extents (layout, &rect, NULL);
+ chooser->emoji_max_width = rect.width;
+
+ g_object_unref (layout);
+ }
+
+ chooser->recent_long_press = gtk_gesture_long_press_new (chooser->recent.box);
+ g_signal_connect (chooser->recent_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
+ chooser->recent_multi_press = gtk_gesture_multi_press_new (chooser->recent.box);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->recent_multi_press), GDK_BUTTON_SECONDARY);
+ g_signal_connect (chooser->recent_multi_press, "pressed", G_CALLBACK (pressed_cb), chooser);
+
+ chooser->people_long_press = gtk_gesture_long_press_new (chooser->people.box);
+ g_signal_connect (chooser->people_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
+ chooser->people_multi_press = gtk_gesture_multi_press_new (chooser->people.box);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->people_multi_press), GDK_BUTTON_SECONDARY);
+ g_signal_connect (chooser->people_multi_press, "pressed", G_CALLBACK (pressed_cb), chooser);
+
+ chooser->body_long_press = gtk_gesture_long_press_new (chooser->body.box);
+ g_signal_connect (chooser->body_long_press, "pressed", G_CALLBACK (long_pressed_cb), chooser);
+ chooser->body_multi_press = gtk_gesture_multi_press_new (chooser->body.box);
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (chooser->body_multi_press), GDK_BUTTON_SECONDARY);
+ g_signal_connect (chooser->body_multi_press, "pressed", G_CALLBACK (pressed_cb), chooser);
+
+ adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
+ g_signal_connect (adj, "value-changed", G_CALLBACK (adj_value_changed), chooser);
+
+ setup_section (chooser, &chooser->recent, NULL, "emoji-recent-symbolic");
+ setup_section (chooser, &chooser->people, "grinning face", "emoji-people-symbolic");
+ setup_section (chooser, &chooser->body, "selfie", "emoji-body-symbolic");
+ setup_section (chooser, &chooser->nature, "monkey face", "emoji-nature-symbolic");
+ setup_section (chooser, &chooser->food, "grapes", "emoji-food-symbolic");
+ setup_section (chooser, &chooser->travel, "globe showing Europe-Africa", "emoji-travel-symbolic");
+ setup_section (chooser, &chooser->activities, "jack-o-lantern", "emoji-activities-symbolic");
+ setup_section (chooser, &chooser->objects, "muted speaker", "emoji-objects-symbolic");
+ setup_section (chooser, &chooser->symbols, "ATM sign", "emoji-symbols-symbolic");
+ setup_section (chooser, &chooser->flags, "chequered flag", "emoji-flags-symbolic");
+
+ populate_recent_section (chooser);
+
+ chooser->populate_idle = g_idle_add (populate_emoji_chooser, chooser);
+ g_source_set_name_by_id (chooser->populate_idle, "[gtk] populate_emoji_chooser");
+}
+
+static void
+dino_emoji_chooser_show (GtkWidget *widget)
+{
+ DinoEmojiChooser *chooser = DINO_EMOJI_CHOOSER (widget);
+ GtkAdjustment *adj;
+
+ GTK_WIDGET_CLASS (dino_emoji_chooser_parent_class)->show (widget);
+
+ adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window));
+ gtk_adjustment_set_value (adj, 0);
+
+ gtk_entry_set_text (GTK_ENTRY (chooser->search_entry), "");
+}
+
+static gboolean
+dino_emoji_chooser_button_release (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ DinoEmojiChooserClass *klass = DINO_EMOJI_CHOOSER_GET_CLASS(widget);
+ GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent *) event);
+ if (!event_widget && event->window != gtk_widget_get_window (widget))
+ {
+ return GDK_EVENT_PROPAGATE;
+ }
+ return klass->popover_button_release_event (widget, event);
+}
+
+static void
+dino_emoji_chooser_class_init (DinoEmojiChooserClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = dino_emoji_chooser_finalize;
+ widget_class->show = dino_emoji_chooser_show;
+ klass->popover_button_release_event = widget_class->button_release_event;
+ widget_class->button_release_event = dino_emoji_chooser_button_release;
+
+ signals[EMOJI_PICKED] = g_signal_new ("emoji-picked",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 1, G_TYPE_STRING|G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/im/dino/Dino/emojichooser.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, search_entry);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, stack);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, scrolled_window);
+
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, recent.box);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, recent.button);
+
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, people.box);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, people.heading);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, people.button);
+
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, body.box);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, body.heading);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, body.button);
+
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, nature.box);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, nature.heading);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, nature.button);
+
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, food.box);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, food.heading);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, food.button);
+
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, travel.box);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, travel.heading);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, travel.button);
+
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, activities.box);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, activities.heading);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, activities.button);
+
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, objects.box);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, objects.heading);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, objects.button);
+
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, symbols.box);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, symbols.heading);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, symbols.button);
+
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, flags.box);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, flags.heading);
+ gtk_widget_class_bind_template_child (widget_class, DinoEmojiChooser, flags.button);
+
+ gtk_widget_class_bind_template_callback (widget_class, emoji_activated);
+ gtk_widget_class_bind_template_callback (widget_class, search_changed);
+}
+
+GtkWidget *
+dino_emoji_chooser_new (void)
+{
+ return GTK_WIDGET (g_object_new (GTK_TYPE_EMOJI_CHOOSER, NULL));
+}
diff --git a/main/src/emojichooser.h b/main/src/emojichooser.h
new file mode 100644
index 00000000..3e459431
--- /dev/null
+++ b/main/src/emojichooser.h
@@ -0,0 +1,36 @@
+/* gtkemojichooser.h: An Emoji chooser widget
+ * Copyright 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
+ * License as published by the Free Software Foundation; either
+ * version 2 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_EMOJI_CHOOSER (dino_emoji_chooser_get_type ())
+#define DINO_EMOJI_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_EMOJI_CHOOSER, DinoEmojiChooser))
+#define DINO_EMOJI_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_EMOJI_CHOOSER, DinoEmojiChooserClass))
+#define GTK_IS_EMOJI_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_EMOJI_CHOOSER))
+#define GTK_IS_EMOJI_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_EMOJI_CHOOSER))
+#define DINO_EMOJI_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_EMOJI_CHOOSER, DinoEmojiChooserClass))
+
+typedef struct _DinoEmojiChooser DinoEmojiChooser;
+typedef struct _DinoEmojiChooserClass DinoEmojiChooserClass;
+
+GType dino_emoji_chooser_get_type (void) G_GNUC_CONST;
+GtkWidget *dino_emoji_chooser_new (void);
+
+G_END_DECLS
diff --git a/main/src/ui/avatar_image.vala b/main/src/ui/avatar_image.vala
index 846bf0ff..d98e5baa 100644
--- a/main/src/ui/avatar_image.vala
+++ b/main/src/ui/avatar_image.vala
@@ -40,11 +40,11 @@ public class AvatarImage : Misc {
Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height);
Cairo.Context bufctx = new Cairo.Context(buffer);
if (idx == -1 || current_avatars[idx] == null) {
- set_source_hex_color(bufctx, gray || idx == -1 ? "555753" : Util.get_avatar_hex_color(stream_interactor, account, current_jids[idx]));
+ set_source_hex_color(bufctx, gray || idx == -1 || current_jids[idx] == null ? "555753" : Util.get_avatar_hex_color(stream_interactor, account, current_jids[idx]));
bufctx.rectangle(0, 0, width, height);
bufctx.fill();
- string text = text_only ?? (idx == -1 ? "…" : Util.get_display_name(stream_interactor, current_jids[idx], account).get_char(0).toupper().to_string());
+ string text = text_only ?? (idx == -1 || current_jids[idx] == null ? "…" : Util.get_display_name(stream_interactor, current_jids[idx], account).get_char(0).toupper().to_string());
bufctx.select_font_face(get_pango_context().get_font_description().get_family(), Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL);
bufctx.set_font_size(width / font_factor < 40 ? font_factor * 17 : font_factor * 25);
Cairo.TextExtents extents;
diff --git a/main/vapi/emojichooser.vapi b/main/vapi/emojichooser.vapi
new file mode 100644
index 00000000..f29e52d3
--- /dev/null
+++ b/main/vapi/emojichooser.vapi
@@ -0,0 +1,7 @@
+namespace Dino {
+ [CCode (cheader_filename = "emojichooser.h")]
+ class EmojiChooser : Gtk.Popover {
+ public signal void emoji_picked(string text);
+ public EmojiChooser();
+ }
+}
diff --git a/qlite/src/delete_builder.vala b/qlite/src/delete_builder.vala
index e7e3b784..45e09dc7 100644
--- a/qlite/src/delete_builder.vala
+++ b/qlite/src/delete_builder.vala
@@ -53,7 +53,7 @@ public class DeleteBuilder : StatementBuilder {
public void perform() {
if (prepare().step() != DONE) {
- error(@"SQLite error: %d - %s", db.errcode(), db.errmsg());
+ critical(@"SQLite error: %d - %s", db.errcode(), db.errmsg());
}
}
diff --git a/qlite/src/insert_builder.vala b/qlite/src/insert_builder.vala
index b66464a6..8b14b33f 100644
--- a/qlite/src/insert_builder.vala
+++ b/qlite/src/insert_builder.vala
@@ -74,7 +74,7 @@ public class InsertBuilder : StatementBuilder {
public int64 perform() {
if (prepare().step() != DONE) {
- error(@"SQLite error: %d - %s", db.errcode(), db.errmsg());
+ critical(@"SQLite error: %d - %s", db.errcode(), db.errmsg());
}
return db.last_insert_rowid();
}
diff --git a/qlite/src/update_builder.vala b/qlite/src/update_builder.vala
index f675a85c..0bf83aee 100644
--- a/qlite/src/update_builder.vala
+++ b/qlite/src/update_builder.vala
@@ -94,7 +94,7 @@ public class UpdateBuilder : StatementBuilder {
public void perform() {
if (fields.length == 0) return;
if (prepare().step() != DONE) {
- error("SQLite error: %d - %s", db.errcode(), db.errmsg());
+ critical("SQLite error: %d - %s", db.errcode(), db.errmsg());
}
}
diff --git a/qlite/src/upsert_builder.vala b/qlite/src/upsert_builder.vala
index 54ba9924..6d29b288 100644
--- a/qlite/src/upsert_builder.vala
+++ b/qlite/src/upsert_builder.vala
@@ -100,7 +100,7 @@ public class UpsertBuilder : StatementBuilder {
public int64 perform() {
if (prepare_update().step() != DONE || prepare_insert().step() != DONE) {
- error(@"SQLite error: %d - %s", db.errcode(), db.errmsg());
+ critical(@"SQLite error: %d - %s", db.errcode(), db.errmsg());
}
return db.last_insert_rowid();
}
diff --git a/xmpp-vala/src/core/stanza_node.vala b/xmpp-vala/src/core/stanza_node.vala
index 0544cc1f..6ef3f0aa 100644
--- a/xmpp-vala/src/core/stanza_node.vala
+++ b/xmpp-vala/src/core/stanza_node.vala
@@ -333,6 +333,9 @@ public class StanzaNode : StanzaEntry {
internal string printf(int i, string fmt_start_begin, string start_empty_end, string start_content_end, string fmt_end, string fmt_attr, bool no_ns = false) {
string indent = string.nfill (i * 2, ' ');
if (name == "#text") {
+ if (((!)val).length > 1000) {
+ return indent + "[... retracted for brevity ...]\n";
+ }
return indent + ((!)val).replace("\n", indent + "\n") + "\n";
}
var sb = new StringBuilder();
diff --git a/xmpp-vala/src/core/stanza_reader.vala b/xmpp-vala/src/core/stanza_reader.vala
index 72aa0b5f..c90390b5 100644
--- a/xmpp-vala/src/core/stanza_reader.vala
+++ b/xmpp-vala/src/core/stanza_reader.vala
@@ -63,14 +63,14 @@ public class StanzaReader {
if (buffer_pos >= buffer_fill) {
yield update_buffer();
}
- char c = (char) buffer[buffer_pos++];
- return c;
+ return (char) buffer[buffer_pos++];
}
private async char peek_single() throws XmlError {
- var res = yield read_single();
- buffer_pos--;
- return res;
+ if (buffer_pos >= buffer_fill) {
+ yield update_buffer();
+ }
+ return (char) buffer[buffer_pos];
}
private bool is_ws(uint8 what) {
@@ -82,37 +82,55 @@ public class StanzaReader {
}
private async void skip_until_non_ws() throws XmlError {
- while (is_ws(yield peek_single())) {
- skip_single();
+ if (buffer_pos >= buffer_fill) {
+ yield update_buffer();
+ }
+ while (is_ws(buffer[buffer_pos])) {
+ buffer_pos++;
+ if (buffer_pos >= buffer_fill) {
+ yield update_buffer();
+ }
}
}
private async string read_until_ws() throws XmlError {
var res = new StringBuilder();
- var what = yield peek_single();
- while (!is_ws(what)) {
- res.append_c(yield read_single());
- what = yield peek_single();
+ if (buffer_pos >= buffer_fill) {
+ yield update_buffer();
+ }
+ while (!is_ws(buffer[buffer_pos])) {
+ res.append_c((char) buffer[buffer_pos++]);
+ if (buffer_pos >= buffer_fill) {
+ yield update_buffer();
+ }
}
return res.str;
}
private async string read_until_char_or_ws(char x, char y = 0) throws XmlError {
var res = new StringBuilder();
- var what = yield peek_single();
- while (what != x && what != y && !is_ws(what)) {
- res.append_c(yield read_single());
- what = yield peek_single();
+ if (buffer_pos >= buffer_fill) {
+ yield update_buffer();
+ }
+ while (buffer[buffer_pos] != x && buffer[buffer_pos] != y && !is_ws(buffer[buffer_pos])) {
+ res.append_c((char) buffer[buffer_pos++]);
+ if (buffer_pos >= buffer_fill) {
+ yield update_buffer();
+ }
}
return res.str;
}
private async string read_until_char(char x) throws XmlError {
var res = new StringBuilder();
- var what = yield peek_single();
- while (what != x) {
- res.append_c(yield read_single());
- what = yield peek_single();
+ if (buffer_pos >= buffer_fill) {
+ yield update_buffer();
+ }
+ while (buffer[buffer_pos] != x) {
+ res.append_c((char) buffer[buffer_pos++]);
+ if (buffer_pos >= buffer_fill) {
+ yield update_buffer();
+ }
}
return res.str;
}