diff --git a/.ci/install.sh b/.ci/install.sh index d1bff54b..8d27d301 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -3,6 +3,7 @@ set -ex if [ "$FLATPAK" ]; then + sudo apt-get -y install flatpak flatpak-builder elfutils flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo flatpak --noninteractive install --user flathub org.kde.Platform//5.14 flatpak --noninteractive install --user flathub org.kde.Sdk//5.14 diff --git a/.ci/script.sh b/.ci/script.sh index 15e7f8cf..63e7f135 100755 --- a/.ci/script.sh +++ b/.ci/script.sh @@ -6,7 +6,26 @@ if [ "$FLATPAK" ]; then mkdir -p build-flatpak cd build-flatpak - flatpak-builder --ccache --repo=repo --subject="Build of Nheko ${VERSION} `date`" app ../io.github.NhekoReborn.Nheko.json + jobsarg="" + if [ "$ARCH" = "arm64" ]; then + jobsarg="--jobs=2" + fi + + flatpak-builder --ccache --repo=repo --subject="Build of Nheko ${VERSION} $jobsarg `date`" app ../io.github.NhekoReborn.Nheko.json & + + # to prevent flatpak builder from timing out on arm, run it in the background and print something every minute for up to 30 minutes. + minutes=0 + limit=40 + while kill -0 $! >/dev/null 2>&1; do + if [ $minutes == $limit ]; then + break; + fi + + minutes=$((minutes+1)) + + sleep 60 + done + flatpak build-bundle repo nheko-${VERSION}-${ARCH}.flatpak io.github.NhekoReborn.Nheko master mkdir ../artifacts diff --git a/.travis.yml b/.travis.yml index 443836cf..eec32290 100644 --- a/.travis.yml +++ b/.travis.yml @@ -113,10 +113,6 @@ matrix: apt: sources: - sourceline: 'ppa:alexlarsson/flatpak' - packages: - - flatpak - - flatpak-builder - - elfutils - os: linux arch: arm64 env: @@ -128,20 +124,17 @@ matrix: sources: - sourceline: 'ppa:alexlarsson/flatpak' packages: - - flatpak - - flatpak-builder - - elfutils - librsvg2-bin before_install: # Use TRAVIS_TAG if defined, or the short commit SHA otherwise - export VERSION=${TRAVIS_TAG:-$(git rev-parse --short HEAD)} install: - - travis_wait ./.ci/install.sh + - ./.ci/install.sh - export PATH=/usr/local/bin:${PATH} script: - - travis_wait ./.ci/script.sh + - ./.ci/script.sh - sed -i -e "s/VERSION_NAME_VALUE/${VERSION}/g" ./.ci/bintray-release.json || true - cp ./.ci/bintray-release.json . deploy: diff --git a/CMakeLists.txt b/CMakeLists.txt index 36403890..c1def924 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,9 @@ set( CACHE FILEPATH "Default toolchain" ) - +set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard") +set(CMAKE_CXX_STANDARD_REQUIRED ON CACHE BOOL "Require C++ standard to be supported") +set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "compile as PIC by default") option(HUNTER_ENABLED "Enable Hunter package manager" OFF) include("cmake/HunterGate.cmake") @@ -333,7 +335,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG f5c78f4331b62a1e25a2d839cb38b07bb9bd7829 + GIT_TAG 795b6a82d4f10c629ce0eb99803cc677c413f940 ) FetchContent_MakeAvailable(MatrixClient) else() @@ -421,7 +423,7 @@ endif() # single instance functionality set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication") -add_subdirectory(third_party/SingleApplication-3.0.19/) +add_subdirectory(third_party/SingleApplication-3.1.3.1/) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) @@ -515,6 +517,9 @@ set(TRANSLATION_DEPS ${LANG_QRC} ${QRC} ${QM_SRC}) if (APPLE) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa") set(SRC_FILES ${SRC_FILES} src/notifications/ManagerMac.mm src/emoji/MacHelper.mm) + if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0") + set_source_files_properties( src/notifications/ManagerMac.mm src/emoji/MacHelper.mm PROPERTIES SKIP_PRECOMPILE_HEADERS ON) + endif() elseif (WIN32) file(DOWNLOAD "https://raw.githubusercontent.com/mohabouje/WinToast/41ed1c58d5dce0ee9c01dbdeac05be45358d4f57/src/wintoastlib.cpp" @@ -578,6 +583,13 @@ target_link_libraries(nheko PRIVATE tweeny SingleApplication::SingleApplication) +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.16.0") +target_precompile_headers(nheko + PRIVATE + + ) +endif() + if(MSVC) target_link_libraries(nheko PRIVATE ntdll) endif() diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index 50fa3b33..7ffb1995 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -131,7 +131,7 @@ { "sha256": "59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722", "type": "archive", - "url": "https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.bz2" + "url": "https://sourceforge.net/projects/boost/files/boost/1.72.0/boost_1_72_0.tar.bz2" } ] }, @@ -146,9 +146,9 @@ "name": "mtxclient", "sources": [ { - "sha256": "16203a92b03c488178b31bedca9d9015b1d406443f7e5363a2e09171e50f8dfc", + "sha256": "7ba85bb477c9e17e2389faf2333034aa9ceae4b1c65d6bf5f7067f2799e9b770", "type": "archive", - "url": "https://github.com/Nheko-Reborn/mtxclient/archive/f5c78f4331b62a1e25a2d839cb38b07bb9bd7829.tar.gz" + "url": "https://github.com/Nheko-Reborn/mtxclient/archive/795b6a82d4f10c629ce0eb99803cc677c413f940.tar.gz" } ] }, diff --git a/resources/langs/nheko_it.ts b/resources/langs/nheko_it.ts new file mode 100644 index 00000000..38566eca --- /dev/null +++ b/resources/langs/nheko_it.ts @@ -0,0 +1,1586 @@ + + + + + Cache + + + You joined this room. + Sei entrato in questa stanza. + + + + ChatPage + + + Failed to invite user: %1 + Impossibile invitare l'utente: %1 + + + + + Invited user: %1 + Invitato utente: %1 + + + + Migrating the cache to the current version failed. This can have different reasons. Please open an issue and try to use an older version in the mean time. Alternatively you can try deleting the cache manually. + Migrazione della cache alla versione corrente fallita. Questo può avere diverse cause. Per favore apri una issue e nel frattempo prova ad usare una versione più vecchia. In alternativa puoi provare a cancellare la cache manualmente. + + + + Room %1 created. + Stanza %1 creata. + + + + Failed to invite %1 to %2: %3 + Impossibile invitare %1 a %2: %3 + + + + Failed to kick %1 to %2: %3 + Impossibile scacciare %1 a %2: %3 + + + + Kicked user: %1 + Scacciato utente: %1 + + + + Failed to ban %1 in %2: %3 + Impossibile bannare %1 in %2: %3 + + + + Banned user: %1 + Utente bannato: %1 + + + + Failed to unban %1 in %2: %3 + Impossibile rimuovere il ban di %1 in %2: %3 + + + + Unbanned user: %1 + Rimosso il ban dall'utente: %1 + + + + Failed to upload media. Please try again. + Impossibile inviare il file multimediale. Per favore riprova. + + + + Cache migration failed! + Migrazione della cache fallita! + + + + Incompatible cache version + Versione della cache incompatibile + + + + The cache on your disk is newer than this version of Nheko supports. Please update or clear your cache. + La cache sul tuo disco è più nuova di quella supportata da questa versione di Nheko. Per favore aggiorna o pulisci la tua cache. + + + + Failed to restore OLM account. Please login again. + Impossibile ripristinare l'account OLM. Per favore accedi nuovamente. + + + + Failed to restore save data. Please login again. + Impossibile ripristinare i dati salvati. Per favore accedi nuovamente. + + + + Failed to setup encryption keys. Server response: %1 %2. Please try again later. + Impossibile configurare le chiavi crittografiche. Risposta del server: %1 %2. Per favore riprova in seguito. + + + + + Please try to login again: %1 + Per favore prova ad accedere nuovamente: %1 + + + + Failed to join room: %1 + Impossibile accedere alla stanza: %1 + + + + You joined the room + Sei entrato nella stanza + + + + Failed to remove invite: %1 + Impossibile rimuovere l'invito: %1 + + + + Room creation failed: %1 + Creazione della stanza fallita: %1 + + + + Failed to leave room: %1 + Impossibile lasciare la stanza: %1 + + + + CommunitiesListItem + + + All rooms + Tutte le stanze + + + + Favourite rooms + Stanze preferite + + + + Low priority rooms + Stanze a bassa priorità + + + + + (tag) + (tag) + + + + (community) + (comunità) + + + + EditModal + + + Apply + Applica + + + + Cancel + Annulla + + + + Name + Nome + + + + Topic + Argomento + + + + EncryptionIndicator + + + Encrypted + Criptato + + + + This message is not encrypted! + Questo messaggio è in chiaro! + + + + InviteeItem + + + Remove + Rimuovi + + + + LoginPage + + + Matrix ID + ID Matrix + + + + e.g @joe:matrix.org + es. @joe:matrix.org + + + + Your login name. A mxid should start with @ followed by the user id. After the user id you need to include your server name after a :. +You can also put your homeserver address there, if your server doesn't support .well-known lookup. +Example: @user:server.my +If Nheko fails to discover your homeserver, it will show you a field to enter the server manually. + Il tuo nome utente. Un mxid dovrebbe iniziare con @ seguita dall'user id. Dopo l'user id hai bisogno di includere il nome del tuo server dopo un :. +Puoi anche inserire qui l'indirizzo del tuo homeserver, se il tuo server non supporta la ricerca con .well-known. +Esempio: @utente:server.mio +Se Nheko non conclude la ricerca del tuo homeserver, ti mostrerà un campo in cui inserire il server manualmente. + + + + Password + Password + + + + Device name + Nome del dispositivo + + + + A name for this device, which will be shown to others, when verifying your devices. If none is provided, a random string is used for privacy purposes. + Un nome per questo dispositivo, che sarà mostrato agli altri mentre si verificano i tuoi dispositivi. Se non ne fornisci uno, verrà usata una stringa casuale per ragioni di privacy. + + + + The address that can be used to contact you homeservers client API. +Example: https://server.my:8787 + L'indirizzo che può essere usato per contattare le API client del tuo homeserver. +Esempio: https://server.mio:8787 + + + + + LOGIN + ACCEDI + + + + Autodiscovery failed. Received malformed response. + Ricerca automatica fallita. Ricevuta risposta malformata. + + + + Autodiscovery failed. Unknown error when requesting .well-known. + Ricerca automatica fallita. Errore ignoto durante la richiesta di .well-known. + + + + The required endpoints were not found. Possibly not a Matrix server. + Gli endpoint richiesti non sono stati trovati. Forse non è un server Matrix. + + + + Received malformed response. Make sure the homeserver domain is valid. + Ricevuta risposta malformata. Assicurati che il dominio dell'homeserver sia valido. + + + + An unknown error occured. Make sure the homeserver domain is valid. + Avvenuto un errore sconosciuto. Assicurati che il dominio dell'homeserver sia valido. + + + + SSO LOGIN + ACCESSO SSO + + + + Empty password + Password vuota + + + + SSO login failed + Accesso SSO fallito + + + + MemberList + + + Room members + Membri della stanza + + + + OK + OK + + + + MessageDelegate + + + redacted + oscurato + + + + Encryption enabled + Crittografia abilitata + + + + room name changed to: %1 + nome della stanza cambiato in: %1 + + + + removed room name + nome della stanza rimosso + + + + topic changed to: %1 + argomento cambiato in: %1 + + + + removed topic + argomento rimosso + + + + %1 created and configured room: %2 + %1 creato e configurata stanza: %2 + + + + Placeholder + + + unimplemented event: + event non implementato: + + + + QuickSwitcher + + + Search for a room... + Cerca una stanza… + + + + RegisterPage + + + Username + Nome utente + + + + The username must not be empty, and must contain only the characters a-z, 0-9, ., _, =, -, and /. + Il nome utente non deve essere vuoto e deve contenere solo i caratteri a-z, 0-9, ., _, =, -, e /. + + + + Password + Password + + + + Please choose a secure password. The exact requirements for password strength may depend on your server. + Per favore scegli una password sicura. I requisiti di robustezza della password potrebbero dipendere dal server. + + + + Password confirmation + Conferma della password + + + + Homeserver + Homeserver + + + + A server that allows registration. Since matrix is decentralized, you need to first find a server you can register on or host your own. + Un server che consente la registrazione. Siccome matrix è decentralizzata, devi prima trovare un server su cui registrarti o ospitarne uno tuo. + + + + REGISTER + REGISTRATI + + + + No supported registration flows! + Non ci sono processi di registrazione supportati! + + + + Invalid username + Nome utente non valido + + + + Password is not long enough (min 8 chars) + La password non è abbastanza lunga (minimo 8 caratteri) + + + + Passwords don't match + Le password non corrispondono + + + + Invalid server name + Nome del server non valido + + + + RoomInfo + + + no version stored + nessuna versione memorizzata + + + + RoomInfoListItem + + + Leave room + Lascia la stanza + + + + Accept + Accetta + + + + Decline + Rifiuta + + + + SideBarActions + + + User settings + Impostazioni utente + + + + Create new room + Crea una nuova stanza + + + + Join a room + Entra in una stanza + + + + Start a new chat + Inizia una nuova discussione + + + + Room directory + Elenco delle stanze + + + + StatusIndicator + + + Failed + Fallito + + + + Sent + Inviato + + + + Received + Ricevuto + + + + Read + Letto + + + + TextInputWidget + + + Send a file + Invia un file + + + + + Write a message... + Scrivi un messaggio… + + + + Send a message + Invia un messaggio + + + + Emoji + Emoji + + + + Select a file + Seleziona un file + + + + All Files (*) + Tutti i file (*) + + + + Connection lost. Nheko is trying to re-connect... + Connessione interrotta. Nheko sta provando a riconnettersi… + + + + TimelineModel + + + -- Decryption Error (failed to communicate with DB) -- + Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session. + -- Errore di Decriptazione (impossibile comunicare con il DB) -- + + + + -- Decryption Error (failed to retrieve megolm keys from db) -- + Placeholder, when the message can't be decrypted, because the DB access failed. + -- Errore di Decrittazione (impossibile recuperare le chiavi megolm dal DB) -- + + + + -- Decryption Error (%1) -- + Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1. + -- Errore di Decrittazione (%1) -- + + + + Message redaction failed: %1 + Oscuramento del messaggio fallito: %1 + + + + Save image + Salva immagine + + + + Save video + Salva video + + + + Save audio + Salva audio + + + + Save file + Salva file + + + + -- Encrypted Event (No keys found for decryption) -- + Placeholder, when the message was not decrypted yet or can't be decrypted. + -- Evento Criptato (Chiavi per la decriptazione non trovate) -- + + + + -- Encrypted Event (Unknown event type) -- + Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet. + -- Evento Criptato (Tipo di evento ignoto) -- + + + + %1 and %2 are typing. + Multiple users are typing. First argument is a comma separated list of potentially multiple users. Second argument is the last user of that list. (If only one user is typing, %1 is empty. You should still use it in your string though to silence Qt warnings.) + + %1%2 sta scrivendo. + %1 e %2 stanno scrivendo. + + + + + %1 opened the room to the public. + %1 ha aperto la stanza al pubblico. + + + + %1 made this room require and invitation to join. + %1 ha configurato questa stanza per richiedere un invito per entrare. + + + + %1 made the room open to guests. + %1 ha configurato questa stanza affinché sia aperta ai visitatori. + + + + %1 has closed the room to guest access. + %1 ha chiuso la stanza ai visitatori. + + + + %1 made the room history world readable. Events may be now read by non-joined people. + %1 ha reso la cronologia della stanza leggibile da tutti. Gli eventi adesso possono essere letti da persone esterne. + + + + %1 set the room history visible to members from this point on. + %1 ha reso la cronologia della stanza visibile ai membri da questo momento in poi. + + + + %1 set the room history visible to members since they were invited. + %1 ha reso la cronologia della stanza visibile ai membri dal momento in cui sono stati invitati. + + + + %1 set the room history visible to members since they joined the room. + %1 ha reso la cronologia della stanza visibile ai membri dal momento in cui sono entrati nella stanza. + + + + %1 has changed the room's permissions. + %1 ha cambiato i permessi della stanza. + + + + %1 was invited. + %1 è stato invitato. + + + + %1 changed their display name and avatar. + %1 ha cambiato il suo nome visualizzato e l'avatar. + + + + %1 changed their display name. + %1 ha cambiato il suo nome visualizzato. + + + + %1 changed their avatar. + %1 ha cambiato il suo avatar. + + + + %1 joined. + %1 è entrato. + + + + %1 rejected their invite. + %1 ha rifiutato il suo invito. + + + + Revoked the invite to %1. + Revocato l'invito a %1. + + + + %1 left the room. + %1 ha lasciato la stanza. + + + + Kicked %1. + Scacciato %1. + + + + Unbanned %1. + Rimosso ban da %1. + + + + %1 was banned. + %1 è stato bannato. + + + + %1 redacted their knock. + %1 ha oscurato la sua bussata. + + + + You joined this room. + Sei entrato in questa stanza. + + + + Rejected the knock from %1. + Rifiutata la bussata di %1. + + + + %1 left after having already left! + This is a leave event after the user already left and shouldn't happen apart from state resets + %1 è uscito dopo essere già uscito! + + + + Reason: %1 + Motivazione: %1 + + + + %1 knocked. + %1 ha bussato. + + + + TimelineRow + + + Reply + Rispondi + + + + Options + Opzioni + + + + TimelineView + + + Reply + Risposta + + + + Read receipts + Leggi le ricevute + + + + Mark as read + Segna come letto + + + + View raw message + Mostra il messaggio grezzo + + + + View decrypted raw message + Mostra il messaggio grezzo decriptato + + + + Redact message + Oscura messaggio + + + + Save as + Salva come + + + + No room open + Nessuna stanza aperta + + + + Close + Chiudi + + + + TopRoomBar + + + Room options + Opzioni della stanza + + + + Mentions + Menzioni + + + + Invite users + Invita utenti + + + + Members + Membri + + + + Leave room + Lascia la stanza + + + + Settings + Impostazioni + + + + TrayIcon + + + Show + Rivela + + + + Quit + Esci + + + + UserInfoWidget + + + Logout + Disconnettiti + + + + UserSettingsPage + + + Minimize to tray + Minimizza nella tray + + + + Start in tray + Avvia nella tray + + + + Group's sidebar + Barra laterale dei gruppi + + + + Circular Avatars + Avatar Circolari + + + + Decrypt messages in sidebar + Decripta messaggi nella barra laterale + + + + Show buttons in timeline + Mostra pulsanti nella timeline + + + + Typing notifications + Notifiche di scrittura + + + + Sort rooms by unreads + Ordina stanze per non letti + + + + Read receipts + Ricevute di lettura + + + + Send messages as Markdown + Invia messaggi come Markdown + + + + Desktop notifications + Notifiche desktop + + + + Scale factor + Fattore di scala + + + + Font size + Dimensione dei caratteri + + + + Font Family + Famiglia dei caratteri + + + + Theme + Tema + + + + Device ID + ID Dispositivo + + + + Device Fingerprint + Impronta digitale del dispositivo + + + + Session Keys + Chiavi di Sessione + + + + IMPORT + IMPORTA + + + + EXPORT + ESPORTA + + + + ENCRYPTION + CRITTOGRAFIA + + + + GENERAL + GENERALE + + + + INTERFACE + INTERFACCIA + + + + Emoji Font Family + Famiglia dei caratteri delle Emoji + + + + Open Sessions File + Apri File delle Sessioni + + + + + + + + + + + + + Error + Errore + + + + + File Password + Password del File + + + + Enter the passphrase to decrypt the file: + Inserisci la passphrase per decriptare il file: + + + + + The password cannot be empty + La password non può essere vuota + + + + Enter passphrase to encrypt your session keys: + Inserisci la passphrase per criptare le tue chiavi di sessione: + + + + File to save the exported session keys + File ove salvare le chiavi di sessione esportate + + + + WelcomePage + + + Welcome to nheko! The desktop client for the Matrix protocol. + Benvenuto su nheko! Il client desktop per il protocollo Matrix. + + + + Enjoy your stay! + Goditi la permanenza! + + + + REGISTER + REGISTRATI + + + + LOGIN + ACCEDI + + + + descriptiveTime + + + Yesterday + Ieri + + + + dialogs::CreateRoom + + + Create room + Crea stanza + + + + Cancel + Annulla + + + + Name + Nome + + + + Topic + Argomento + + + + Alias + Alias + + + + Room Visibility + Visibilità Stanza + + + + Room Preset + Preset Stanza + + + + Direct Chat + Chat Diretta + + + + dialogs::FallbackAuth + + + Open Fallback in Browser + Apertura di Ripiego nel Browser + + + + Cancel + Annulla + + + + Confirm + Conferma + + + + Open the fallback, follow the steps and confirm after completing them. + Apri il ripiego, segui i passaggi e conferma dopo averli completati. + + + + dialogs::InviteUsers + + + Cancel + Annulla + + + + User ID to invite + ID utente da invitare + + + + dialogs::JoinRoom + + + Join + Entra + + + + Cancel + Annulla + + + + Room ID or alias + ID della stanza o alias + + + + dialogs::LeaveRoom + + + Cancel + Annulla + + + + Are you sure you want to leave? + Sei sicuro di voler uscire? + + + + dialogs::Logout + + + Cancel + Annulla + + + + Logout. Are you sure? + Uscita. Ne sei certo? + + + + dialogs::PreviewUploadOverlay + + + Upload + Upload + + + + Cancel + Annulla + + + + Media type: %1 +Media size: %2 + + Tipo media: %1 +Peso media: %2 + + + + + dialogs::ReCaptcha + + + Cancel + Annulla + + + + Confirm + Conferma + + + + Solve the reCAPTCHA and press the confirm button + Risolvi il reCAPTCHA e premi il pulsante di conferma + + + + dialogs::ReadReceipts + + + Read receipts + Ricevute di lettura + + + + Close + Chiudi + + + + dialogs::ReceiptItem + + + Today %1 + Oggi %1 + + + + Yesterday %1 + Ieri %1 + + + + dialogs::RoomSettings + + + Settings + Impostazioni + + + + Info + Informazioni + + + + Internal ID + ID interno + + + + Room Version + Versione Stanza + + + + Notifications + Notifiche + + + + Muted + Silenziata + + + + Mentions only + Solo menzioni + + + + All messages + Tutti i messaggi + + + + Room access + Accesso stanza + + + + Anyone and guests + Chiunque ed ospiti + + + + Anyone + Chiunque conosca il link della stanza (no ospiti) + + + + Invited users + Utenti invitati + + + + Encryption + Crittografia + + + + End-to-End Encryption + Crittografia End-to-End + + + + Encryption is currently experimental and things might break unexpectedly. <br>Please take note that it can't be disabled afterwards. + La crittografia è ancora sperimentale e le cose potrebbero rompersi inaspettatamente. <br>Per favore prendi nota che in seguito non potrà essere disabilitata. + + + + Respond to key requests + Rispondi alle richieste di chiavi + + + + Whether or not the client should respond automatically with the session keys + upon request. Use with caution, this is a temporary measure to test the + E2E implementation until device verification is completed. + Indica se il client deve rispondere automaticamente con le chiavi di sessione + su richiesta. Usa con cautela, questa è una misura temporanea per testare + l'implementazione di E2E fino al completamento della verifica dei dispositivi. + + + + %n member(s) + + %n membro + %n membri + + + + + Failed to enable encryption: %1 + Impossibile abilitare la crittografia: %1 + + + + Select an avatar + Scegli un avatar + + + + All Files (*) + Tutti i File (*) + + + + The selected file is not an image + Il file selezionato non è un'immagine + + + + Error while reading file: %1 + Errore durante la lettura del file: %1 + + + + + Failed to upload image: %s + Impossibile fare l'upload dell'immagine: %s + + + + dialogs::UserProfile + + + Ban the user from the room + Banna l'utente dalla stanza + + + + Ignore messages from this user + Ignora i messaggi da questo utente + + + + Kick the user from the room + Scaccia l'utente dalla stanza + + + + Start a conversation + Inizia una conversazione + + + + Devices + Dispositivi + + + + emoji::Panel + + + Smileys & People + Faccine & Persone + + + + Animals & Nature + Animali & Natura + + + + Food & Drink + Cibi & Bevande + + + + Activity + Attività + + + + Travel & Places + Viaggi & Luoghi + + + + Objects + Oggetti + + + + Symbols + Simboli + + + + Flags + Bandiere + + + + message-description sent: + + + You sent an audio clip + Hai inviato una clip audio + + + + %1 sent an audio clip + %1 ha inviato una clip audio + + + + You sent an image + Hai inviato un'immagine + + + + %1 sent an image + %1 ha inviato un'immagine + + + + You sent a file + Hai inviato un file + + + + %1 sent a file + %1 ha inviato un file + + + + You sent a video + Hai inviato un video + + + + %1 sent a video + %1 ha inviato un video + + + + You sent a sticker + Hai inviato uno sticker + + + + %1 sent a sticker + %1 ha inviato uno sticker + + + + You sent a notification + Hai inviato una notifica + + + + %1 sent a notification + %1 ha inviato una notifica + + + + You: %1 + Tu: %1 + + + + %1: %2 + %1: %2 + + + + You sent an encrypted message + Hai inviato un messaggio criptato + + + + %1 sent an encrypted message + %1 ha inviato un messaggio criptato + + + + popups::UserMentions + + + This Room + Questa Stanza + + + + All Rooms + Tutte le Stanze + + + + utils + + + Unknown Message Type + Tipo di Messaggio sconosciuto + + + + + + + Tag room as: + stanza come: + + + + Favourite + Standard matrix tag for favourites + Tag matrix standard per i preferiti + + + + Adds or removes the specified tag. + WhatsThis hint for tag menu actions + Aggiungi o rimuovi il tag specificato. + + + + Highlight message on hover + Evidenzia il messaggio al passaggio del mouse + + + diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index ed065270..465a8e1c 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -6,7 +6,7 @@ Rectangle { id: avatar width: 48 height: 48 - radius: settings.avatar_circles ? height/2 : 3 + radius: settings.avatarCircles ? height/2 : 3 property alias url: img.source property string displayName @@ -39,7 +39,7 @@ Rectangle { anchors.fill: parent width: avatar.width height: avatar.height - radius: settings.avatar_circles ? height/2 : 3 + radius: settings.avatarCircles ? height/2 : 3 } } } diff --git a/resources/qml/Reactions.qml b/resources/qml/Reactions.qml index b272e2e6..848e4974 100644 --- a/resources/qml/Reactions.qml +++ b/resources/qml/Reactions.qml @@ -57,7 +57,7 @@ Flow { Text { anchors.baseline: reactionCounter.baseline id: reactionText - text: textMetrics.elidedText + (textMetrics.elidedText == model.key ? "" : "…") + text: textMetrics.elidedText + (textMetrics.elidedText == textMetrics.text ? "" : "…") font.family: settings.emoji_font_family color: reaction.hovered ? colors.highlight : colors.text maximumLineCount: 1 @@ -65,7 +65,7 @@ Flow { Rectangle { id: divider - height: reactionCounter.implicitHeight * 1.4 + height: Math.floor(reactionCounter.implicitHeight * 1.4) width: 1 color: (reaction.hovered || model.selfReactedEvent !== '') ? colors.highlight : colors.text } diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index 9961d15f..cbec6d94 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -26,13 +26,13 @@ MouseArea { messageContextMenu.show(model.id, model.type, model.isEncrypted, row) } Rectangle { - color: (timelineSettings.message_hover_highlight && parent.containsMouse) ? colors.base : "transparent" + color: (settings.messageHoverHighlight && parent.containsMouse) ? colors.base : "transparent" anchors.fill: row } RowLayout { id: row - anchors.leftMargin: avatarSize + 4 + anchors.leftMargin: avatarSize + 16 anchors.left: parent.left anchors.right: parent.right @@ -97,7 +97,7 @@ MouseArea { event_id: model.id } ImageButton { - visible: timelineSettings.buttons + visible: settings.buttonsInTimeline Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.preferredHeight: 16 width: 16 @@ -113,7 +113,7 @@ MouseArea { onClicked: chat.model.replyAction(model.id) } ImageButton { - visible: timelineSettings.buttons + visible: settings.buttonsInTimeline Layout.alignment: Qt.AlignRight | Qt.AlignTop Layout.preferredHeight: 16 width: 16 diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index e5359910..12c8108a 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -3,7 +3,6 @@ import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import QtQuick.Window 2.2 -import Qt.labs.settings 1.0 import im.nheko 1.0 import im.nheko.EmojiModel 1.0 @@ -122,7 +121,7 @@ Page { BusyIndicator { visible: running anchors.centerIn: parent - running: timelineManager.isInitialSync + running: timelineManager.isInitialSync height: 200 width: 200 z: 3 @@ -133,12 +132,12 @@ Page { visible: timelineManager.timeline != null - cacheBuffer: 500 + cacheBuffer: 400 - anchors.left: parent.left - anchors.right: parent.right + anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.bottom: chatFooter.top + width: parent.width anchors.leftMargin: 4 anchors.rightMargin: scrollbar.width @@ -180,7 +179,7 @@ Page { id: scrollbar parent: chat.parent anchors.top: chat.top - anchors.left: chat.right + anchors.right: chat.right anchors.bottom: chat.bottom } @@ -195,7 +194,8 @@ Page { id: wrapper property Item section - width: chat.width + anchors.horizontalCenter: parent.horizontalCenter + width: (settings.timelineMaxWidth > 100 && (parent.width - settings.timelineMaxWidth) > 32) ? settings.timelineMaxWidth : (parent.width - 32) height: section ? section.height + timelinerow.height : timelinerow.height color: "transparent" @@ -265,7 +265,8 @@ Page { } Row { height: userName.height - spacing: 4 + spacing: 8 + Avatar { width: avatarSize height: avatarSize diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml index bef4f76d..b3c45c36 100644 --- a/resources/qml/delegates/TextMessage.qml +++ b/resources/qml/delegates/TextMessage.qml @@ -6,4 +6,5 @@ MatrixText { width: parent ? parent.width : undefined height: isReply ? Math.min(chat.height / 8, implicitHeight) : undefined clip: true + font.pointSize: (settings.enlargeEmojiOnlyMessages && model.data.isOnlyEmoji > 0 && model.data.isOnlyEmoji < 4) ? settings.fontSize * 3 : settings.fontSize } diff --git a/resources/qml/emoji/EmojiCategory.qml b/resources/qml/emoji/EmojiCategory.qml new file mode 100644 index 00000000..1cd42d0b --- /dev/null +++ b/resources/qml/emoji/EmojiCategory.qml @@ -0,0 +1,67 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.9 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.9 + +import im.nheko 1.0 +import im.nheko.EmojiModel 1.0 + +GridView { + id: root + property var category + property var emojiPopup + property EmojiProxyModel model: EmojiProxyModel { + sourceModel: EmojiModel { + viewCategory: category + } + } + + interactive: false + + cellWidth: 52 + cellHeight: 52 + height: 52 * ( model.count / 7 + 1 ) + + clip: true + + // Individual emoji + delegate: AbstractButton { + width: 48 + height: 48 + contentItem: Text { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: settings.emoji_font_family + + font.pixelSize: 36 + text: model.unicode + } + + background: Rectangle { + anchors.fill: parent + color: hovered ? colors.highlight : 'transparent' + radius: 5 + } + + hoverEnabled: true + ToolTip.text: model.shortName + ToolTip.visible: hovered + + // give the emoji a little oomf + DropShadow { + width: parent.width; + height: parent.height; + horizontalOffset: 3 + verticalOffset: 3 + radius: 8.0 + samples: 17 + color: "#80000000" + source: parent.contentItem + } + // TODO: maybe add favorites at some point? + onClicked: { + console.debug("Picked " + model.unicode + "in response to " + emojiPopup.event_id + " in room " + emojiPopup.room_id) + emojiPopup.picked(emojiPopup.room_id, emojiPopup.event_id, model.unicode) + } + } +} \ No newline at end of file diff --git a/src/Cache.cpp b/src/Cache.cpp index 1061e60e..009cbabc 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -31,6 +31,7 @@ #include "Cache.h" #include "Cache_p.h" +#include "EventAccessors.h" #include "Logging.h" #include "Utils.h" @@ -1947,13 +1948,14 @@ Cache::saveTimelineMessages(lmdb::txn &txn, json obj = json::object(); - obj["event"] = utils::serialize_event(e); + obj["event"] = mtx::accessors::serialize_event(e); obj["token"] = res.prev_batch; - lmdb::dbi_put(txn, - db, - lmdb::val(std::to_string(utils::event_timestamp(e))), - lmdb::val(obj.dump())); + lmdb::dbi_put( + txn, + db, + lmdb::val(std::to_string(obj["event"]["origin_server_ts"].get())), + lmdb::val(obj.dump())); } } @@ -2026,7 +2028,7 @@ Cache::saveTimelineMentions(lmdb::txn &txn, using namespace mtx::events::state; for (const auto ¬if : res) { - const auto event_id = utils::event_id(notif.event); + const auto event_id = mtx::accessors::event_id(notif.event); // double check that we have the correct room_id... if (room_id.compare(notif.room_id) != 0) { diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 1e06da5d..2340b146 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -26,6 +26,7 @@ #include "Cache.h" #include "Cache_p.h" #include "ChatPage.h" +#include "EventAccessors.h" #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" @@ -255,7 +256,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) text_input_, &TextInputWidget::startedTyping, this, &ChatPage::sendTypingNotifications); connect(typingRefresher_, &QTimer::timeout, this, &ChatPage::sendTypingNotifications); connect(text_input_, &TextInputWidget::stoppedTyping, this, [this]() { - if (!userSettings_->isTypingNotificationsEnabled()) + if (!userSettings_->typingNotifications()) return; typingRefresher_->stop(); @@ -482,7 +483,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) activateWindow(); }); - setGroupViewState(userSettings_->isGroupViewEnabled()); + setGroupViewState(userSettings_->groupView()); connect(userSettings_.data(), &UserSettings::groupViewStateChanged, @@ -891,7 +892,7 @@ void ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res) { for (const auto &item : res.notifications) { - const auto event_id = utils::event_id(item.event); + const auto event_id = mtx::accessors::event_id(item.event); try { if (item.read) { @@ -901,7 +902,8 @@ ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res) if (!cache::isNotificationSent(event_id)) { const auto room_id = QString::fromStdString(item.room_id); - const auto user_id = utils::event_sender(item.event); + const auto user_id = + QString::fromStdString(mtx::accessors::sender(item.event)); // We should only sent one notification per event. cache::markSentNotification(event_id); @@ -1213,7 +1215,7 @@ ChatPage::unbanUser(QString userid, QString reason) void ChatPage::sendTypingNotifications() { - if (!userSettings_->isTypingNotificationsEnabled()) + if (!userSettings_->typingNotifications()) return; http::client()->start_typing( @@ -1349,7 +1351,7 @@ ChatPage::hideSideBars() void ChatPage::showSideBars() { - if (userSettings_->isGroupViewEnabled()) + if (userSettings_->groupView()) communitiesList_->show(); sideBar_->show(); diff --git a/src/CommunitiesListItem.h b/src/CommunitiesListItem.h index 0cc5d60c..535a6ec0 100644 --- a/src/CommunitiesListItem.h +++ b/src/CommunitiesListItem.h @@ -7,7 +7,6 @@ #include "ui/Theme.h" class RippleOverlay; -class QPainter; class QMouseEvent; class CommunitiesListItem : public QWidget diff --git a/src/EventAccessors.cpp b/src/EventAccessors.cpp index 7f28eb46..a2d8adbb 100644 --- a/src/EventAccessors.cpp +++ b/src/EventAccessors.cpp @@ -400,3 +400,9 @@ mtx::accessors::media_width(const mtx::events::collections::TimelineEvents &even { return std::visit(EventMediaWidth{}, event); } + +nlohmann::json +mtx::accessors::serialize_event(const mtx::events::collections::TimelineEvents &event) +{ + return std::visit([](const auto &e) { return nlohmann::json(e); }, event); +} diff --git a/src/EventAccessors.h b/src/EventAccessors.h index c9ac4d00..a7577d86 100644 --- a/src/EventAccessors.h +++ b/src/EventAccessors.h @@ -63,4 +63,7 @@ media_height(const mtx::events::collections::TimelineEvents &event); uint64_t media_width(const mtx::events::collections::TimelineEvents &event); + +nlohmann::json +serialize_event(const mtx::events::collections::TimelineEvents &event); } diff --git a/src/InviteeItem.cpp b/src/InviteeItem.cpp index 906a3bfe..a6b471dc 100644 --- a/src/InviteeItem.cpp +++ b/src/InviteeItem.cpp @@ -1,4 +1,5 @@ #include +#include #include #include "InviteeItem.h" diff --git a/src/InviteeItem.h b/src/InviteeItem.h index 582904b4..54c61938 100644 --- a/src/InviteeItem.h +++ b/src/InviteeItem.h @@ -1,11 +1,11 @@ #pragma once -#include #include #include class QPushButton; +class QLabel; class InviteeItem : public QWidget { diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp index bb329699..9a920d1d 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp @@ -16,6 +16,7 @@ */ #include +#include #include #include @@ -118,7 +119,7 @@ LoginPage::LoginPage(QWidget *parent) deviceName_->setLabel(tr("Device name")); deviceName_->setToolTip( tr("A name for this device, which will be shown to others, when verifying your devices. " - "If none is provided, a random string is used for privacy purposes.")); + "If none is provided a default is used.")); serverInput_ = new TextField(this); serverInput_->setLabel("Homeserver address"); @@ -132,7 +133,7 @@ LoginPage::LoginPage(QWidget *parent) form_layout_->addLayout(matrixidLayout_); form_layout_->addWidget(password_input_); - form_layout_->addWidget(deviceName_, Qt::AlignHCenter, nullptr); + form_layout_->addWidget(deviceName_, Qt::AlignHCenter); form_layout_->addLayout(serverLayout_); button_layout_ = new QHBoxLayout(); @@ -179,6 +180,12 @@ LoginPage::LoginPage(QWidget *parent) connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered())); } +void +LoginPage::loginError(const QString &msg) +{ + error_label_->setText(msg); +} + void LoginPage::onMatrixIdEntered() { diff --git a/src/LoginPage.h b/src/LoginPage.h index 8a402aea..c9220297 100644 --- a/src/LoginPage.h +++ b/src/LoginPage.h @@ -17,8 +17,6 @@ #pragma once -#include -#include #include class FlatButton; @@ -26,6 +24,9 @@ class LoadingIndicator; class OverlayModal; class RaisedButton; class TextField; +class QLabel; +class QVBoxLayout; +class QHBoxLayout; namespace mtx { namespace responses { @@ -65,7 +66,7 @@ protected: public slots: // Displays errors produced during the login. - void loginError(const QString &msg) { error_label_->setText(msg); } + void loginError(const QString &msg); private slots: // Callback for the back button. diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index c6abdca2..cc1d868b 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -148,7 +148,7 @@ MainWindow::MainWindow(QWidget *parent) QSettings settings; - trayIcon_->setVisible(userSettings_->isTrayEnabled()); + trayIcon_->setVisible(userSettings_->tray()); if (hasActiveUser()) { QString token = settings.value("auth/access_token").toString(); @@ -286,7 +286,7 @@ void MainWindow::closeEvent(QCloseEvent *event) { if (!qApp->isSavingSession() && isVisible() && pageSupportsTray() && - userSettings_->isTrayEnabled()) { + userSettings_->tray()) { event->ignore(); hide(); } diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index 8e67375d..a197e4aa 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -17,7 +17,8 @@ MxcImageResponse::run() auto data = cache::image(fileName); if (!data.isNull()) { m_image = utils::readImage(&data); - m_image = m_image.scaled(m_requestedSize, Qt::KeepAspectRatio); + m_image = m_image.scaled( + m_requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); m_image.setText("mxc url", "mxc://" + m_id); if (!m_image.isNull()) { diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp index 03e9ab34..b8fe93b5 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp @@ -15,6 +15,7 @@ * along with this program. If not, see . */ +#include #include #include #include @@ -106,10 +107,10 @@ RegisterPage::RegisterPage(QWidget *parent) tr("A server that allows registration. Since matrix is decentralized, you need to first " "find a server you can register on or host your own.")); - form_layout_->addWidget(username_input_, Qt::AlignHCenter, nullptr); - form_layout_->addWidget(password_input_, Qt::AlignHCenter, nullptr); - form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter, nullptr); - form_layout_->addWidget(server_input_, Qt::AlignHCenter, nullptr); + form_layout_->addWidget(username_input_, Qt::AlignHCenter); + form_layout_->addWidget(password_input_, Qt::AlignHCenter); + form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter); + form_layout_->addWidget(server_input_, Qt::AlignHCenter); button_layout_ = new QHBoxLayout(); button_layout_->setSpacing(0); diff --git a/src/RegisterPage.h b/src/RegisterPage.h index ebc24bb1..59ba3d1d 100644 --- a/src/RegisterPage.h +++ b/src/RegisterPage.h @@ -17,8 +17,8 @@ #pragma once -#include -#include +#include + #include #include @@ -26,6 +26,9 @@ class FlatButton; class RaisedButton; class TextField; +class QLabel; +class QVBoxLayout; +class QHBoxLayout; class RegisterPage : public QWidget { diff --git a/src/RoomInfoListItem.cpp b/src/RoomInfoListItem.cpp index ad774360..f234b59b 100644 --- a/src/RoomInfoListItem.cpp +++ b/src/RoomInfoListItem.cpp @@ -451,7 +451,7 @@ RoomInfoListItem::calculateImportance() const // returns ImportanceDisabled or Invite if (isInvite()) { return Invite; - } else if (!settings->isSortByImportanceEnabled()) { + } else if (!settings->sortByImportance()) { return ImportanceDisabled; } else if (unreadHighlightedMsgCount_) { return NewMentions; diff --git a/src/RoomList.cpp b/src/RoomList.cpp index 85a22026..b4c507b5 100644 --- a/src/RoomList.cpp +++ b/src/RoomList.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include "Logging.h" diff --git a/src/SideBarActions.cpp b/src/SideBarActions.cpp index 4934ec05..5af01cc2 100644 --- a/src/SideBarActions.cpp +++ b/src/SideBarActions.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include diff --git a/src/TopRoomBar.h b/src/TopRoomBar.h index 1aa5934b..0c33c1e0 100644 --- a/src/TopRoomBar.h +++ b/src/TopRoomBar.h @@ -27,7 +27,6 @@ class Menu; class TextLabel; class OverlayModal; -class QPainter; class QLabel; class QHBoxLayout; class QVBoxLayout; diff --git a/src/UserInfoWidget.cpp b/src/UserInfoWidget.cpp index 2e21d41f..e11aa6aa 100644 --- a/src/UserInfoWidget.cpp +++ b/src/UserInfoWidget.cpp @@ -16,7 +16,10 @@ * along with this program. If not, see . */ +#include #include +#include +#include #include #include diff --git a/src/UserInfoWidget.h b/src/UserInfoWidget.h index e1a925a4..575ade52 100644 --- a/src/UserInfoWidget.h +++ b/src/UserInfoWidget.h @@ -17,13 +17,16 @@ #pragma once -#include -#include +#include class Avatar; class FlatButton; class OverlayModal; +class QLabel; +class QHBoxLayout; +class QVBoxLayout; + class UserInfoWidget : public QWidget { Q_OBJECT diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 6af08e12..dfd99069 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -51,54 +52,206 @@ void UserSettings::load() { QSettings settings; - isTrayEnabled_ = settings.value("user/window/tray", false).toBool(); - hasDesktopNotifications_ = settings.value("user/desktop_notifications", true).toBool(); - isStartInTrayEnabled_ = settings.value("user/window/start_in_tray", false).toBool(); - isGroupViewEnabled_ = settings.value("user/group_view", true).toBool(); - isButtonsInTimelineEnabled_ = settings.value("user/timeline/buttons", true).toBool(); - isMessageHoverHighlightEnabled_ = + tray_ = settings.value("user/window/tray", false).toBool(); + hasDesktopNotifications_ = settings.value("user/desktop_notifications", true).toBool(); + startInTray_ = settings.value("user/window/start_in_tray", false).toBool(); + groupView_ = settings.value("user/group_view", true).toBool(); + buttonsInTimeline_ = settings.value("user/timeline/buttons", true).toBool(); + timelineMaxWidth_ = settings.value("user/timeline/max_width", 0).toInt(); + messageHoverHighlight_ = settings.value("user/timeline/message_hover_highlight", false).toBool(); - isMarkdownEnabled_ = settings.value("user/markdown_enabled", true).toBool(); - isTypingNotificationsEnabled_ = settings.value("user/typing_notifications", true).toBool(); - sortByImportance_ = settings.value("user/sort_by_unread", true).toBool(); - isReadReceiptsEnabled_ = settings.value("user/read_receipts", true).toBool(); - theme_ = settings.value("user/theme", defaultTheme_).toString(); - font_ = settings.value("user/font_family", "default").toString(); - avatarCircles_ = settings.value("user/avatar_circles", true).toBool(); - decryptSidebar_ = settings.value("user/decrypt_sidebar", true).toBool(); - emojiFont_ = settings.value("user/emoji_font_family", "default").toString(); - baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble(); + enlargeEmojiOnlyMessages_ = + settings.value("user/timeline/enlarge_emoji_only_msg", false).toBool(); + markdown_ = settings.value("user/markdown_enabled", true).toBool(); + typingNotifications_ = settings.value("user/typing_notifications", true).toBool(); + sortByImportance_ = settings.value("user/sort_by_unread", true).toBool(); + readReceipts_ = settings.value("user/read_receipts", true).toBool(); + theme_ = settings.value("user/theme", defaultTheme_).toString(); + font_ = settings.value("user/font_family", "default").toString(); + avatarCircles_ = settings.value("user/avatar_circles", true).toBool(); + decryptSidebar_ = settings.value("user/decrypt_sidebar", true).toBool(); + emojiFont_ = settings.value("user/emoji_font_family", "default").toString(); + baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble(); applyTheme(); } +void +UserSettings::setMessageHoverHighlight(bool state) +{ + if (state == messageHoverHighlight_) + return; + messageHoverHighlight_ = state; + emit messageHoverHighlightChanged(state); + save(); +} +void +UserSettings::setEnlargeEmojiOnlyMessages(bool state) +{ + if (state == enlargeEmojiOnlyMessages_) + return; + enlargeEmojiOnlyMessages_ = state; + emit enlargeEmojiOnlyMessagesChanged(state); + save(); +} +void +UserSettings::setTray(bool state) +{ + if (state == tray_) + return; + tray_ = state; + emit trayChanged(state); + save(); +} + +void +UserSettings::setStartInTray(bool state) +{ + if (state == startInTray_) + return; + startInTray_ = state; + emit startInTrayChanged(state); + save(); +} + +void +UserSettings::setGroupView(bool state) +{ + if (groupView_ != state) + emit groupViewStateChanged(state); + + groupView_ = state; + save(); +} + +void +UserSettings::setMarkdown(bool state) +{ + if (state == markdown_) + return; + markdown_ = state; + emit markdownChanged(state); + save(); +} + +void +UserSettings::setReadReceipts(bool state) +{ + if (state == readReceipts_) + return; + readReceipts_ = state; + emit readReceiptsChanged(state); + save(); +} + +void +UserSettings::setTypingNotifications(bool state) +{ + if (state == typingNotifications_) + return; + typingNotifications_ = state; + emit typingNotificationsChanged(state); + save(); +} + +void +UserSettings::setSortByImportance(bool state) +{ + if (state == sortByImportance_) + return; + sortByImportance_ = state; + emit roomSortingChanged(state); + save(); +} + +void +UserSettings::setButtonsInTimeline(bool state) +{ + if (state == buttonsInTimeline_) + return; + buttonsInTimeline_ = state; + emit buttonInTimelineChanged(state); + save(); +} + +void +UserSettings::setTimelineMaxWidth(int state) +{ + if (state == timelineMaxWidth_) + return; + timelineMaxWidth_ = state; + emit timelineMaxWidthChanged(state); + save(); +} + +void +UserSettings::setDesktopNotifications(bool state) +{ + if (state == hasDesktopNotifications_) + return; + hasDesktopNotifications_ = state; + emit desktopNotificationsChanged(state); + save(); +} + +void +UserSettings::setAvatarCircles(bool state) +{ + if (state == avatarCircles_) + return; + avatarCircles_ = state; + emit avatarCirclesChanged(state); + save(); +} + +void +UserSettings::setDecryptSidebar(bool state) +{ + if (state == decryptSidebar_) + return; + decryptSidebar_ = state; + emit decryptSidebarChanged(state); + save(); +} void UserSettings::setFontSize(double size) { + if (size == baseFontSize_) + return; baseFontSize_ = size; + emit fontSizeChanged(size); save(); } void UserSettings::setFontFamily(QString family) { + if (family == font_) + return; font_ = family; + emit fontChanged(family); save(); } void UserSettings::setEmojiFontFamily(QString family) { + if (family == emojiFont_) + return; emojiFont_ = family; + emit emojiFontChanged(family); save(); } void UserSettings::setTheme(QString theme) { + if (theme == theme) + return; theme_ = theme; save(); applyTheme(); + emit themeChanged(theme); } void @@ -161,29 +314,33 @@ UserSettings::save() settings.beginGroup("user"); settings.beginGroup("window"); - settings.setValue("tray", isTrayEnabled_); - settings.setValue("start_in_tray", isStartInTrayEnabled_); + settings.setValue("tray", tray_); + settings.setValue("start_in_tray", startInTray_); settings.endGroup(); settings.beginGroup("timeline"); - settings.setValue("buttons", isButtonsInTimelineEnabled_); - settings.setValue("message_hover_highlight", isMessageHoverHighlightEnabled_); + settings.setValue("buttons", buttonsInTimeline_); + settings.setValue("message_hover_highlight", messageHoverHighlight_); + settings.setValue("enlarge_emoji_only_msg", enlargeEmojiOnlyMessages_); + settings.setValue("max_width", timelineMaxWidth_); settings.endGroup(); settings.setValue("avatar_circles", avatarCircles_); settings.setValue("decrypt_sidebar", decryptSidebar_); settings.setValue("font_size", baseFontSize_); - settings.setValue("typing_notifications", isTypingNotificationsEnabled_); + settings.setValue("typing_notifications", typingNotifications_); settings.setValue("minor_events", sortByImportance_); - settings.setValue("read_receipts", isReadReceiptsEnabled_); - settings.setValue("group_view", isGroupViewEnabled_); - settings.setValue("markdown_enabled", isMarkdownEnabled_); + settings.setValue("read_receipts", readReceipts_); + settings.setValue("group_view", groupView_); + settings.setValue("markdown_enabled", markdown_); settings.setValue("desktop_notifications", hasDesktopNotifications_); settings.setValue("theme", theme()); settings.setValue("font_family", font_); settings.setValue("emoji_font_family", emojiFont_); settings.endGroup(); + + settings.sync(); } HorizontalLine::HorizontalLine(QWidget *parent) @@ -231,24 +388,26 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge general_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); general_->setFont(font); - trayToggle_ = new Toggle{this}; - startInTrayToggle_ = new Toggle{this}; - avatarCircles_ = new Toggle{this}; - decryptSidebar_ = new Toggle(this); - groupViewToggle_ = new Toggle{this}; - timelineButtonsToggle_ = new Toggle{this}; - typingNotifications_ = new Toggle{this}; - messageHoverHighlight_ = new Toggle{this}; - sortByImportance_ = new Toggle{this}; - readReceipts_ = new Toggle{this}; - markdownEnabled_ = new Toggle{this}; - desktopNotifications_ = new Toggle{this}; - scaleFactorCombo_ = new QComboBox{this}; - fontSizeCombo_ = new QComboBox{this}; - fontSelectionCombo_ = new QComboBox{this}; - emojiFontSelectionCombo_ = new QComboBox{this}; + trayToggle_ = new Toggle{this}; + startInTrayToggle_ = new Toggle{this}; + avatarCircles_ = new Toggle{this}; + decryptSidebar_ = new Toggle(this); + groupViewToggle_ = new Toggle{this}; + timelineButtonsToggle_ = new Toggle{this}; + typingNotifications_ = new Toggle{this}; + messageHoverHighlight_ = new Toggle{this}; + enlargeEmojiOnlyMessages_ = new Toggle{this}; + sortByImportance_ = new Toggle{this}; + readReceipts_ = new Toggle{this}; + markdown_ = new Toggle{this}; + desktopNotifications_ = new Toggle{this}; + scaleFactorCombo_ = new QComboBox{this}; + fontSizeCombo_ = new QComboBox{this}; + fontSelectionCombo_ = new QComboBox{this}; + emojiFontSelectionCombo_ = new QComboBox{this}; + timelineMaxWidthSpin_ = new QSpinBox{this}; - if (!settings_->isTrayEnabled()) + if (!settings_->tray()) startInTrayToggle_->setDisabled(true); avatarCircles_->setFixedSize(64, 48); @@ -291,6 +450,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge int themeIndex = themeCombo_->findText(themeStr); themeCombo_->setCurrentIndex(themeIndex); + timelineMaxWidthSpin_->setMinimum(0); + timelineMaxWidthSpin_->setMaximum(100'000'000); + timelineMaxWidthSpin_->setSingleStep(10); + auto encryptionLabel_ = new QLabel{tr("ENCRYPTION"), this}; encryptionLabel_->setFixedHeight(encryptionLabel_->minimumHeight() + LayoutTopMargin); encryptionLabel_->setAlignment(Qt::AlignBottom); @@ -323,11 +486,15 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge sessionKeysLayout->addWidget(sessionKeysExportBtn, 0, Qt::AlignRight); sessionKeysLayout->addWidget(sessionKeysImportBtn, 0, Qt::AlignRight); - auto boxWrap = [this, &font](QString labelText, QWidget *field) { + auto boxWrap = [this, &font](QString labelText, QWidget *field, QString tooltipText = "") { auto label = new QLabel{labelText, this}; label->setFont(font); label->setMargin(OptionMargin); + if (!tooltipText.isEmpty()) { + label->setToolTip(tooltipText); + } + auto layout = new QHBoxLayout; layout->addWidget(field, 0, Qt::AlignRight); @@ -336,25 +503,70 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge formLayout_->addRow(general_); formLayout_->addRow(new HorizontalLine{this}); - boxWrap(tr("Minimize to tray"), trayToggle_); - boxWrap(tr("Start in tray"), startInTrayToggle_); + boxWrap( + tr("Minimize to tray"), + trayToggle_, + tr("Keep the application running in the background after closing the client window.")); + boxWrap(tr("Start in tray"), + startInTrayToggle_, + tr("Start the application in the background without showing the client window.")); formLayout_->addRow(new HorizontalLine{this}); - boxWrap(tr("Circular Avatars"), avatarCircles_); - boxWrap(tr("Group's sidebar"), groupViewToggle_); - boxWrap(tr("Decrypt messages in sidebar"), decryptSidebar_); - boxWrap(tr("Show buttons in timeline"), timelineButtonsToggle_); - boxWrap(tr("Typing notifications"), typingNotifications_); - boxWrap(tr("Sort rooms by unreads"), sortByImportance_); + boxWrap(tr("Circular Avatars"), + avatarCircles_, + tr("Change the appearance of user avatars in chats.\nOFF - square, ON - Circle.")); + boxWrap(tr("Group's sidebar"), + groupViewToggle_, + tr("Show a column containing groups and tags next to the room list.")); + boxWrap(tr("Decrypt messages in sidebar"), + decryptSidebar_, + tr("Decrypt the messages shown in the sidebar.\nOnly affects messages in " + "encrypted chats.")); + boxWrap(tr("Show buttons in timeline"), + timelineButtonsToggle_, + tr("Show buttons to quickly reply, react or access additional options next to each " + "message.")); + boxWrap(tr("Limit width of timeline"), + timelineMaxWidthSpin_, + tr("Set the max width of messages in the timeline (in pixels). This can help " + "readability on wide screen, when Nheko is maximised")); + boxWrap(tr("Typing notifications"), + typingNotifications_, + tr("Show who is typing in a room.\nThis will also enable or disable sending typing " + "notifications to others.")); + boxWrap( + tr("Sort rooms by unreads"), + sortByImportance_, + tr( + "Display rooms with new messages first.\nIf this is off, the list of rooms will only " + "be sorted by the timestamp of the last message in a room.\nIf this is on, rooms which " + "have active notifications (the small circle with a number in it) will be sorted on " + "top. Rooms, that you have muted, will still be sorted by timestamp, since you don't " + "seem to consider them as important as the other rooms.")); formLayout_->addRow(new HorizontalLine{this}); - boxWrap(tr("Read receipts"), readReceipts_); - boxWrap(tr("Send messages as Markdown"), markdownEnabled_); - boxWrap(tr("Desktop notifications"), desktopNotifications_); - boxWrap(tr("Highlight message on hover"), messageHoverHighlight_); + boxWrap(tr("Read receipts"), + readReceipts_, + tr("Show if your message was read.\nStatus is displayed next to timestamps.")); + boxWrap( + tr("Send messages as Markdown"), + markdown_, + tr("Allow using markdown in messages.\nWhen disabled, all messages are sent as a plain " + "text.")); + boxWrap(tr("Desktop notifications"), + desktopNotifications_, + tr("Notify about received message when the client is not currently focused.")); + boxWrap(tr("Highlight message on hover"), + messageHoverHighlight_, + tr("Change the background color of messages when you hover over them.")); + boxWrap(tr("Large Emoji in timeline"), + enlargeEmojiOnlyMessages_, + tr("Make font size larger if messages with only a few emojis are displayed.")); formLayout_->addRow(uiLabel_); formLayout_->addRow(new HorizontalLine{this}); #if !defined(Q_OS_MAC) - boxWrap(tr("Scale factor"), scaleFactorCombo_); + boxWrap(tr("Scale factor"), + scaleFactorCombo_, + tr("Change the scale factor of the whole user interface.")); #else scaleFactorCombo_->hide(); #endif @@ -400,78 +612,87 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge topLayout_->addWidget(versionInfo); connect(themeCombo_, - static_cast(&QComboBox::activated), + static_cast(&QComboBox::currentTextChanged), [this](const QString &text) { settings_->setTheme(text.toLower()); emit themeChanged(); }); connect(scaleFactorCombo_, - static_cast(&QComboBox::activated), + static_cast(&QComboBox::currentTextChanged), [](const QString &factor) { utils::setScaleFactor(factor.toFloat()); }); connect(fontSizeCombo_, - static_cast(&QComboBox::activated), + static_cast(&QComboBox::currentTextChanged), [this](const QString &size) { settings_->setFontSize(size.trimmed().toDouble()); }); connect(fontSelectionCombo_, - static_cast(&QComboBox::activated), + static_cast(&QComboBox::currentTextChanged), [this](const QString &family) { settings_->setFontFamily(family.trimmed()); }); connect(emojiFontSelectionCombo_, - static_cast(&QComboBox::activated), + static_cast(&QComboBox::currentTextChanged), [this](const QString &family) { settings_->setEmojiFontFamily(family.trimmed()); }); - connect(trayToggle_, &Toggle::toggled, this, [this](bool isDisabled) { - settings_->setTray(!isDisabled); - if (isDisabled) { + connect(trayToggle_, &Toggle::toggled, this, [this](bool disabled) { + settings_->setTray(!disabled); + if (disabled) { startInTrayToggle_->setDisabled(true); } else { startInTrayToggle_->setEnabled(true); } - emit trayOptionChanged(!isDisabled); + emit trayOptionChanged(!disabled); }); - connect(startInTrayToggle_, &Toggle::toggled, this, [this](bool isDisabled) { - settings_->setStartInTray(!isDisabled); + connect(startInTrayToggle_, &Toggle::toggled, this, [this](bool disabled) { + settings_->setStartInTray(!disabled); }); - connect(groupViewToggle_, &Toggle::toggled, this, [this](bool isDisabled) { - settings_->setGroupView(!isDisabled); + connect(groupViewToggle_, &Toggle::toggled, this, [this](bool disabled) { + settings_->setGroupView(!disabled); }); - connect(decryptSidebar_, &Toggle::toggled, this, [this](bool isDisabled) { - settings_->setDecryptSidebar(!isDisabled); + connect(decryptSidebar_, &Toggle::toggled, this, [this](bool disabled) { + settings_->setDecryptSidebar(!disabled); emit decryptSidebarChanged(); }); - connect(avatarCircles_, &Toggle::toggled, this, [this](bool isDisabled) { - settings_->setAvatarCircles(!isDisabled); + connect(avatarCircles_, &Toggle::toggled, this, [this](bool disabled) { + settings_->setAvatarCircles(!disabled); }); - connect(markdownEnabled_, &Toggle::toggled, this, [this](bool isDisabled) { - settings_->setMarkdownEnabled(!isDisabled); + connect(markdown_, &Toggle::toggled, this, [this](bool disabled) { + settings_->setMarkdown(!disabled); }); - connect(typingNotifications_, &Toggle::toggled, this, [this](bool isDisabled) { - settings_->setTypingNotifications(!isDisabled); + connect(typingNotifications_, &Toggle::toggled, this, [this](bool disabled) { + settings_->setTypingNotifications(!disabled); }); - connect(sortByImportance_, &Toggle::toggled, this, [this](bool isDisabled) { - settings_->setSortByImportance(!isDisabled); + connect(sortByImportance_, &Toggle::toggled, this, [this](bool disabled) { + settings_->setSortByImportance(!disabled); }); - connect(timelineButtonsToggle_, &Toggle::toggled, this, [this](bool isDisabled) { - settings_->setButtonsInTimeline(!isDisabled); + connect(timelineButtonsToggle_, &Toggle::toggled, this, [this](bool disabled) { + settings_->setButtonsInTimeline(!disabled); }); - connect(readReceipts_, &Toggle::toggled, this, [this](bool isDisabled) { - settings_->setReadReceipts(!isDisabled); + connect(readReceipts_, &Toggle::toggled, this, [this](bool disabled) { + settings_->setReadReceipts(!disabled); }); - connect(desktopNotifications_, &Toggle::toggled, this, [this](bool isDisabled) { - settings_->setDesktopNotifications(!isDisabled); + connect(desktopNotifications_, &Toggle::toggled, this, [this](bool disabled) { + settings_->setDesktopNotifications(!disabled); }); - connect(messageHoverHighlight_, &Toggle::toggled, this, [this](bool isDisabled) { - settings_->setMessageHoverHighlight(!isDisabled); + connect(messageHoverHighlight_, &Toggle::toggled, this, [this](bool disabled) { + settings_->setMessageHoverHighlight(!disabled); }); + connect(enlargeEmojiOnlyMessages_, &Toggle::toggled, this, [this](bool disabled) { + settings_->setEnlargeEmojiOnlyMessages(!disabled); + }); + + connect(timelineMaxWidthSpin_, + qOverload(&QSpinBox::valueChanged), + this, + [this](int newValue) { settings_->setTimelineMaxWidth(newValue); }); + connect( sessionKeysImportBtn, &QPushButton::clicked, this, &UserSettingsPage::importSessionKeys); @@ -493,19 +714,21 @@ UserSettingsPage::showEvent(QShowEvent *) utils::restoreCombobox(themeCombo_, settings_->theme()); // FIXME: Toggle treats true as "off" - trayToggle_->setState(!settings_->isTrayEnabled()); - startInTrayToggle_->setState(!settings_->isStartInTrayEnabled()); - groupViewToggle_->setState(!settings_->isGroupViewEnabled()); - decryptSidebar_->setState(!settings_->isDecryptSidebarEnabled()); - avatarCircles_->setState(!settings_->isAvatarCirclesEnabled()); - typingNotifications_->setState(!settings_->isTypingNotificationsEnabled()); - sortByImportance_->setState(!settings_->isSortByImportanceEnabled()); - timelineButtonsToggle_->setState(!settings_->isButtonsInTimelineEnabled()); - readReceipts_->setState(!settings_->isReadReceiptsEnabled()); - markdownEnabled_->setState(!settings_->isMarkdownEnabled()); + trayToggle_->setState(!settings_->tray()); + startInTrayToggle_->setState(!settings_->startInTray()); + groupViewToggle_->setState(!settings_->groupView()); + decryptSidebar_->setState(!settings_->decryptSidebar()); + avatarCircles_->setState(!settings_->avatarCircles()); + typingNotifications_->setState(!settings_->typingNotifications()); + sortByImportance_->setState(!settings_->sortByImportance()); + timelineButtonsToggle_->setState(!settings_->buttonsInTimeline()); + readReceipts_->setState(!settings_->readReceipts()); + markdown_->setState(!settings_->markdown()); desktopNotifications_->setState(!settings_->hasDesktopNotifications()); - messageHoverHighlight_->setState(!settings_->isMessageHoverHighlightEnabled()); + messageHoverHighlight_->setState(!settings_->messageHoverHighlight()); + enlargeEmojiOnlyMessages_->setState(!settings_->enlargeEmojiOnlyMessages()); deviceIdValue_->setText(QString::fromStdString(http::client()->device_id())); + timelineMaxWidthSpin_->setValue(settings_->timelineMaxWidth()); deviceFingerprintValue_->setText( utils::humanReadableFingerprint(olm::client()->identity_keys().ed25519)); diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index 088bbfb5..fb807067 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -17,17 +17,19 @@ #pragma once -#include #include -#include #include -#include -#include #include #include #include class Toggle; +class QLabel; +class QFormLayout; +class QComboBox; +class QSpinBox; +class QHBoxLayout; +class QVBoxLayout; constexpr int OptionMargin = 6; constexpr int LayoutTopMargin = 50; @@ -37,6 +39,36 @@ class UserSettings : public QObject { Q_OBJECT + Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged) + Q_PROPERTY(bool messageHoverHighlight READ messageHoverHighlight WRITE + setMessageHoverHighlight NOTIFY messageHoverHighlightChanged) + Q_PROPERTY(bool enlargeEmojiOnlyMessages READ enlargeEmojiOnlyMessages WRITE + setEnlargeEmojiOnlyMessages NOTIFY enlargeEmojiOnlyMessagesChanged) + Q_PROPERTY(bool tray READ tray WRITE setTray NOTIFY trayChanged) + Q_PROPERTY(bool startInTray READ startInTray WRITE setStartInTray NOTIFY startInTrayChanged) + Q_PROPERTY(bool groupView READ groupView WRITE setGroupView NOTIFY groupViewStateChanged) + Q_PROPERTY(bool markdown READ markdown WRITE setMarkdown NOTIFY markdownChanged) + Q_PROPERTY(bool typingNotifications READ typingNotifications WRITE setTypingNotifications + NOTIFY typingNotificationsChanged) + Q_PROPERTY(bool sortByImportance READ sortByImportance WRITE setSortByImportance NOTIFY + roomSortingChanged) + Q_PROPERTY(bool buttonsInTimeline READ buttonsInTimeline WRITE setButtonsInTimeline NOTIFY + buttonInTimelineChanged) + Q_PROPERTY( + bool readReceipts READ readReceipts WRITE setReadReceipts NOTIFY readReceiptsChanged) + Q_PROPERTY(bool desktopNotifications READ hasDesktopNotifications WRITE + setDesktopNotifications NOTIFY desktopNotificationsChanged) + Q_PROPERTY( + bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged) + Q_PROPERTY(bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY + decryptSidebarChanged) + Q_PROPERTY(int timelineMaxWidth READ timelineMaxWidth WRITE setTimelineMaxWidth NOTIFY + timelineMaxWidthChanged) + Q_PROPERTY(double fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged) + Q_PROPERTY(QString font READ font WRITE setFontFamily NOTIFY fontChanged) + Q_PROPERTY( + QString emojiFont READ emojiFont WRITE setEmojiFontFamily NOTIFY emojiFontChanged) + public: UserSettings(); @@ -44,104 +76,62 @@ public: void load(); void applyTheme(); void setTheme(QString theme); - void setMessageHoverHighlight(bool state) - { - isMessageHoverHighlightEnabled_ = state; - save(); - } - void setTray(bool state) - { - isTrayEnabled_ = state; - save(); - } - - void setStartInTray(bool state) - { - isStartInTrayEnabled_ = state; - save(); - } - + void setMessageHoverHighlight(bool state); + void setEnlargeEmojiOnlyMessages(bool state); + void setTray(bool state); + void setStartInTray(bool state); void setFontSize(double size); void setFontFamily(QString family); void setEmojiFontFamily(QString family); - - void setGroupView(bool state) - { - if (isGroupViewEnabled_ != state) - emit groupViewStateChanged(state); - - isGroupViewEnabled_ = state; - save(); - } - - void setMarkdownEnabled(bool state) - { - isMarkdownEnabled_ = state; - save(); - } - - void setReadReceipts(bool state) - { - isReadReceiptsEnabled_ = state; - save(); - } - - void setTypingNotifications(bool state) - { - isTypingNotificationsEnabled_ = state; - save(); - } - - void setSortByImportance(bool state) - { - sortByImportance_ = state; - emit roomSortingChanged(); - } - - void setButtonsInTimeline(bool state) - { - isButtonsInTimelineEnabled_ = state; - save(); - } - - void setDesktopNotifications(bool state) - { - hasDesktopNotifications_ = state; - save(); - } - - void setAvatarCircles(bool state) - { - avatarCircles_ = state; - save(); - } - - void setDecryptSidebar(bool state) - { - decryptSidebar_ = state; - save(); - } + void setGroupView(bool state); + void setMarkdown(bool state); + void setReadReceipts(bool state); + void setTypingNotifications(bool state); + void setSortByImportance(bool state); + void setButtonsInTimeline(bool state); + void setTimelineMaxWidth(int state); + void setDesktopNotifications(bool state); + void setAvatarCircles(bool state); + void setDecryptSidebar(bool state); QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; } - bool isMessageHoverHighlightEnabled() const { return isMessageHoverHighlightEnabled_; } - bool isTrayEnabled() const { return isTrayEnabled_; } - bool isStartInTrayEnabled() const { return isStartInTrayEnabled_; } - bool isGroupViewEnabled() const { return isGroupViewEnabled_; } - bool isAvatarCirclesEnabled() const { return avatarCircles_; } - bool isDecryptSidebarEnabled() const { return decryptSidebar_; } - bool isMarkdownEnabled() const { return isMarkdownEnabled_; } - bool isTypingNotificationsEnabled() const { return isTypingNotificationsEnabled_; } - bool isSortByImportanceEnabled() const { return sortByImportance_; } - bool isButtonsInTimelineEnabled() const { return isButtonsInTimelineEnabled_; } - bool isReadReceiptsEnabled() const { return isReadReceiptsEnabled_; } + bool messageHoverHighlight() const { return messageHoverHighlight_; } + bool enlargeEmojiOnlyMessages() const { return enlargeEmojiOnlyMessages_; } + bool tray() const { return tray_; } + bool startInTray() const { return startInTray_; } + bool groupView() const { return groupView_; } + bool avatarCircles() const { return avatarCircles_; } + bool decryptSidebar() const { return decryptSidebar_; } + bool markdown() const { return markdown_; } + bool typingNotifications() const { return typingNotifications_; } + bool sortByImportance() const { return sortByImportance_; } + bool buttonsInTimeline() const { return buttonsInTimeline_; } + bool readReceipts() const { return readReceipts_; } bool hasDesktopNotifications() const { return hasDesktopNotifications_; } + int timelineMaxWidth() const { return timelineMaxWidth_; } double fontSize() const { return baseFontSize_; } QString font() const { return font_; } QString emojiFont() const { return emojiFont_; } signals: void groupViewStateChanged(bool state); - void roomSortingChanged(); + void roomSortingChanged(bool state); + void themeChanged(QString state); + void messageHoverHighlightChanged(bool state); + void enlargeEmojiOnlyMessagesChanged(bool state); + void trayChanged(bool state); + void startInTrayChanged(bool state); + void markdownChanged(bool state); + void typingNotificationsChanged(bool state); + void buttonInTimelineChanged(bool state); + void readReceiptsChanged(bool state); + void desktopNotificationsChanged(bool state); + void avatarCirclesChanged(bool state); + void decryptSidebarChanged(bool state); + void timelineMaxWidthChanged(int state); + void fontSizeChanged(double state); + void fontChanged(QString state); + void emojiFontChanged(QString state); private: // Default to system theme if QT_QPA_PLATFORMTHEME var is set. @@ -150,18 +140,20 @@ private: ? "light" : "system"; QString theme_; - bool isMessageHoverHighlightEnabled_; - bool isTrayEnabled_; - bool isStartInTrayEnabled_; - bool isGroupViewEnabled_; - bool isMarkdownEnabled_; - bool isTypingNotificationsEnabled_; + bool messageHoverHighlight_; + bool enlargeEmojiOnlyMessages_; + bool tray_; + bool startInTray_; + bool groupView_; + bool markdown_; + bool typingNotifications_; bool sortByImportance_; - bool isButtonsInTimelineEnabled_; - bool isReadReceiptsEnabled_; + bool buttonsInTimeline_; + bool readReceipts_; bool hasDesktopNotifications_; bool avatarCircles_; bool decryptSidebar_; + int timelineMaxWidth_; double baseFontSize_; QString font_; QString emojiFont_; @@ -211,9 +203,10 @@ private: Toggle *timelineButtonsToggle_; Toggle *typingNotifications_; Toggle *messageHoverHighlight_; + Toggle *enlargeEmojiOnlyMessages_; Toggle *sortByImportance_; Toggle *readReceipts_; - Toggle *markdownEnabled_; + Toggle *markdown_; Toggle *desktopNotifications_; Toggle *avatarCircles_; Toggle *decryptSidebar_; @@ -226,5 +219,7 @@ private: QComboBox *fontSelectionCombo_; QComboBox *emojiFontSelectionCombo_; + QSpinBox *timelineMaxWidthSpin_; + int sideMargin_ = 0; }; diff --git a/src/Utils.cpp b/src/Utils.cpp index 7f11a8cd..26ea124c 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -51,6 +51,14 @@ utils::localUser() return QString::fromStdString(http::client()->user_id().to_string()); } +bool +utils::codepointIsEmoji(uint code) +{ + // TODO: Be more precise here. + return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x1f300 && code <= 0x1f3ff) || + (code >= 0x1f000 && code <= 0x1faff); +} + QString utils::replaceEmoji(const QString &body) { @@ -63,9 +71,7 @@ utils::replaceEmoji(const QString &body) bool insideFontBlock = false; for (auto &code : utf32_string) { - // TODO: Be more precise here. - if ((code >= 0x2600 && code <= 0x27bf) || (code >= 0x1f300 && code <= 0x1f3ff) || - (code >= 0x1f000 && code <= 0x1faff)) { + if (utils::codepointIsEmoji(code)) { if (!insideFontBlock) { fmtBody += QString(""); insideFontBlock = true; @@ -136,13 +142,13 @@ utils::descriptiveTime(const QDateTime &then) const auto days = then.daysTo(now); if (days == 0) - return then.time().toString(Qt::DefaultLocaleShortDate); + return QLocale::system().toString(then.time(), QLocale::ShortFormat); else if (days < 2) return QString(QCoreApplication::translate("descriptiveTime", "Yesterday")); else if (days < 7) return then.toString("dddd"); - return then.date().toString(Qt::DefaultLocaleShortDate); + return QLocale::system().toString(then.date(), QLocale::ShortFormat); } DescInfo diff --git a/src/Utils.h b/src/Utils.h index d3f66246..07a4a648 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -36,6 +36,9 @@ namespace utils { using TimelineEvent = mtx::events::collections::TimelineEvents; +bool +codepointIsEmoji(uint code); + QString replaceEmoji(const QString &body); @@ -183,42 +186,6 @@ erase_if(ContainerT &items, const PredicateT &predicate) } } -inline uint64_t -event_timestamp(const mtx::events::collections::TimelineEvents &event) -{ - return std::visit([](auto msg) { return msg.origin_server_ts; }, event); -} - -inline nlohmann::json -serialize_event(const mtx::events::collections::TimelineEvents &event) -{ - return std::visit([](auto msg) { return json(msg); }, event); -} - -inline mtx::events::EventType -event_type(const mtx::events::collections::TimelineEvents &event) -{ - return std::visit([](auto msg) { return msg.type; }, event); -} - -inline std::string -event_id(const mtx::events::collections::TimelineEvents &event) -{ - return std::visit([](auto msg) { return msg.event_id; }, event); -} - -inline QString -eventId(const mtx::events::collections::TimelineEvents &event) -{ - return QString::fromStdString(event_id(event)); -} - -inline QString -event_sender(const mtx::events::collections::TimelineEvents &event) -{ - return std::visit([](auto msg) { return QString::fromStdString(msg.sender); }, event); -} - template QString message_body(const mtx::events::collections::TimelineEvents &event) diff --git a/src/dialogs/CreateRoom.cpp b/src/dialogs/CreateRoom.cpp index 06676d3d..be5b4638 100644 --- a/src/dialogs/CreateRoom.cpp +++ b/src/dialogs/CreateRoom.cpp @@ -112,7 +112,7 @@ CreateRoom::CreateRoom(QWidget *parent) }); connect(visibilityCombo_, - static_cast(&QComboBox::activated), + static_cast(&QComboBox::currentTextChanged), [this](const QString &text) { if (text == "Private") { request_.visibility = mtx::requests::Visibility::Private; @@ -122,7 +122,7 @@ CreateRoom::CreateRoom(QWidget *parent) }); connect(presetCombo_, - static_cast(&QComboBox::activated), + static_cast(&QComboBox::currentTextChanged), [this](const QString &text) { if (text == "Private Chat") { request_.preset = mtx::requests::Preset::PrivateChat; diff --git a/src/dialogs/InviteUsers.cpp b/src/dialogs/InviteUsers.cpp index 691035ce..f85adb8f 100644 --- a/src/dialogs/InviteUsers.cpp +++ b/src/dialogs/InviteUsers.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include diff --git a/src/dialogs/InviteUsers.h b/src/dialogs/InviteUsers.h index 952c97a5..684f60b4 100644 --- a/src/dialogs/InviteUsers.h +++ b/src/dialogs/InviteUsers.h @@ -1,13 +1,13 @@ #pragma once #include -#include -#include #include class QPushButton; +class QLabel; class TextField; class QListWidget; +class QListWidgetItem; namespace dialogs { diff --git a/src/dialogs/ReadReceipts.cpp b/src/dialogs/ReadReceipts.cpp index 0edd1ebf..7dcffc28 100644 --- a/src/dialogs/ReadReceipts.cpp +++ b/src/dialogs/ReadReceipts.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -74,15 +75,17 @@ ReceiptItem::dateFormat(const QDateTime &then) const auto days = then.daysTo(now); if (days == 0) - return tr("Today %1").arg(then.time().toString(Qt::DefaultLocaleShortDate)); + return tr("Today %1") + .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); else if (days < 2) - return tr("Yesterday %1").arg(then.time().toString(Qt::DefaultLocaleShortDate)); + return tr("Yesterday %1") + .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); else if (days < 7) return QString("%1 %2") .arg(then.toString("dddd")) - .arg(then.time().toString(Qt::DefaultLocaleShortDate)); + .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); - return then.toString(Qt::DefaultLocaleShortDate); + return QLocale::system().toString(then.time(), QLocale::ShortFormat); } ReadReceipts::ReadReceipts(QWidget *parent) @@ -163,3 +166,10 @@ ReadReceipts::paintEvent(QPaintEvent *) QPainter p(this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } + +void +ReadReceipts::hideEvent(QHideEvent *event) +{ + userList_->clear(); + QFrame::hideEvent(event); +} diff --git a/src/dialogs/ReadReceipts.h b/src/dialogs/ReadReceipts.h index e298af0a..2e7a0217 100644 --- a/src/dialogs/ReadReceipts.h +++ b/src/dialogs/ReadReceipts.h @@ -2,12 +2,12 @@ #include #include -#include -#include -#include -#include class Avatar; +class QLabel; +class QListWidget; +class QHBoxLayout; +class QVBoxLayout; namespace dialogs { @@ -47,11 +47,7 @@ public slots: protected: void paintEvent(QPaintEvent *event) override; - void hideEvent(QHideEvent *event) override - { - userList_->clear(); - QFrame::hideEvent(event); - } + void hideEvent(QHideEvent *event) override; private: QLabel *topLabel_; diff --git a/src/dialogs/RoomSettings.cpp b/src/dialogs/RoomSettings.cpp index cc10ac91..26aece32 100644 --- a/src/dialogs/RoomSettings.cpp +++ b/src/dialogs/RoomSettings.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -41,6 +42,17 @@ constexpr int WIDGET_SPACING = 15; constexpr int TEXT_SPACING = 4; constexpr int BUTTON_SPACING = 2 * TEXT_SPACING; +bool +ClickableFilter::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonRelease) { + emit clicked(); + return true; + } + + return QObject::eventFilter(obj, event); +} + EditModal::EditModal(const QString &roomId, QWidget *parent) : QWidget(parent) , roomId_{roomId} @@ -93,6 +105,28 @@ EditModal::EditModal(const QString &roomId, QWidget *parent) move(center.x() - (width() * 0.5), center.y() - (height() * 0.5)); } +void +EditModal::topicEventSent() +{ + errorField_->hide(); + close(); +} + +void +EditModal::nameEventSent(const QString &name) +{ + errorField_->hide(); + emit nameChanged(name); + close(); +} + +void +EditModal::error(const QString &msg) +{ + errorField_->setText(msg); + errorField_->show(); +} + void EditModal::applyClicked() { diff --git a/src/dialogs/RoomSettings.h b/src/dialogs/RoomSettings.h index e41c866c..e0918afd 100644 --- a/src/dialogs/RoomSettings.h +++ b/src/dialogs/RoomSettings.h @@ -1,9 +1,7 @@ #pragma once -#include #include #include -#include #include @@ -21,6 +19,8 @@ class QPixmap; class TextField; class TextField; class Toggle; +class QLabel; +class QEvent; class ClickableFilter : public QObject { @@ -35,15 +35,7 @@ signals: void clicked(); protected: - bool eventFilter(QObject *obj, QEvent *event) override - { - if (event->type() == QEvent::MouseButtonRelease) { - emit clicked(); - return true; - } - - return QObject::eventFilter(obj, event); - } + bool eventFilter(QObject *obj, QEvent *event) override; }; /// Convenience class which connects events emmited from threads @@ -72,24 +64,9 @@ signals: void nameChanged(const QString &roomName); private slots: - void topicEventSent() - { - errorField_->hide(); - close(); - } - - void nameEventSent(const QString &name) - { - errorField_->hide(); - emit nameChanged(name); - close(); - } - - void error(const QString &msg) - { - errorField_->setText(msg); - errorField_->show(); - } + void topicEventSent(); + void nameEventSent(const QString &name); + void error(const QString &msg); void applyClicked(); diff --git a/src/emoji/Category.cpp b/src/emoji/Category.cpp index 7393b7bc..1e8df082 100644 --- a/src/emoji/Category.cpp +++ b/src/emoji/Category.cpp @@ -15,9 +15,12 @@ * along with this program. If not, see . */ +#include +#include #include #include #include +#include #include "Config.h" diff --git a/src/emoji/Category.h b/src/emoji/Category.h index 2f39d621..79e616ee 100644 --- a/src/emoji/Category.h +++ b/src/emoji/Category.h @@ -18,13 +18,14 @@ #pragma once #include -#include -#include -#include -#include #include "ItemDelegate.h" +class QLabel; +class QListView; +class QStandardItemModel; +class QVBoxLayout; + namespace emoji { class Category : public QWidget diff --git a/src/popups/PopupItem.cpp b/src/popups/PopupItem.cpp index 5513f942..b3784843 100644 --- a/src/popups/PopupItem.cpp +++ b/src/popups/PopupItem.cpp @@ -1,3 +1,4 @@ +#include #include #include #include diff --git a/src/popups/PopupItem.h b/src/popups/PopupItem.h index 7a710fdb..17a4f6bc 100644 --- a/src/popups/PopupItem.h +++ b/src/popups/PopupItem.h @@ -1,8 +1,5 @@ #pragma once -#include -#include -#include #include #include "../AvatarProvider.h" @@ -10,6 +7,8 @@ class Avatar; struct SearchResult; +class QLabel; +class QHBoxLayout; class PopupItem : public QWidget { diff --git a/src/popups/SuggestionsPopup.h b/src/popups/SuggestionsPopup.h index 63c44538..73bfe6f7 100644 --- a/src/popups/SuggestionsPopup.h +++ b/src/popups/SuggestionsPopup.h @@ -1,8 +1,5 @@ #pragma once -#include -#include -#include #include #include "CacheStructs.h" diff --git a/src/popups/UserMentions.cpp b/src/popups/UserMentions.cpp index 2e70dbd3..23a679f1 100644 --- a/src/popups/UserMentions.cpp +++ b/src/popups/UserMentions.cpp @@ -8,9 +8,9 @@ #include "Cache.h" #include "ChatPage.h" +#include "EventAccessors.h" #include "Logging.h" #include "UserMentions.h" -//#include "timeline/TimelineItem.h" using namespace popups; @@ -75,12 +75,15 @@ UserMentions::initializeMentions(const QMap utf32_string = qBody.toUcs4(); + int emojiCount = 0; + + for (auto &code : utf32_string) { + if (utils::codepointIsEmoji(code)) { + emojiCount++; + } else { + return QVariant(0); + } + } + + return QVariant(emojiCount); + } case Body: return QVariant(utils::replaceEmoji(QString::fromStdString(body(event)))); case FormattedBody: { @@ -386,6 +403,7 @@ TimelineModel::data(const QString &id, int role) const // m.insert(names[Section], data(id, static_cast(Section))); m.insert(names[Type], data(id, static_cast(Type))); m.insert(names[TypeString], data(id, static_cast(TypeString))); + m.insert(names[IsOnlyEmoji], data(id, static_cast(IsOnlyEmoji))); m.insert(names[Body], data(id, static_cast(Body))); m.insert(names[FormattedBody], data(id, static_cast(FormattedBody))); m.insert(names[UserId], data(id, static_cast(UserId))); @@ -810,7 +828,7 @@ TimelineModel::escapeEmoji(QString str) const void TimelineModel::viewRawMessage(QString id) const { - std::string ev = utils::serialize_event(events.value(id)).dump(4); + std::string ev = mtx::accessors::serialize_event(events.value(id)).dump(4); auto dialog = new dialogs::RawMessage(QString::fromStdString(ev)); Q_UNUSED(dialog); } @@ -824,7 +842,7 @@ TimelineModel::viewDecryptedRawMessage(QString id) const event = decryptEvent(*e).event; } - std::string ev = utils::serialize_event(event).dump(4); + std::string ev = mtx::accessors::serialize_event(event).dump(4); auto dialog = new dialogs::RawMessage(QString::fromStdString(ev)); Q_UNUSED(dialog); } @@ -1849,6 +1867,8 @@ TimelineModel::formatMemberEvent(QString id) rendered = tr("%1 changed their display name.").arg(name); else if (avatarChanged) rendered = tr("%1 changed their avatar.").arg(name); + else + rendered = tr("%1 changed some profile info.").arg(name); // the case of nothing changed but join follows join shouldn't happen, so // just show it as join } else { diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index ea7eaffd..3b01781f 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -142,6 +142,7 @@ public: Section, Type, TypeString, + IsOnlyEmoji, Body, FormattedBody, UserId, diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 0375ccc8..bf671cb0 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -21,7 +21,7 @@ Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) void TimelineViewManager::updateEncryptedDescriptions() { - auto decrypt = settings->isDecryptSidebarEnabled(); + auto decrypt = settings->decryptSidebar(); QHash>::iterator i; for (i = models.begin(); i != models.end(); ++i) { auto ptr = i.value(); @@ -96,6 +96,7 @@ TimelineViewManager::TimelineViewManager(QSharedPointer userSettin #endif container->setMinimumSize(200, 200); view->rootContext()->setContextProperty("timelineManager", this); + view->rootContext()->setContextProperty("settings", settings.data()); updateColorPalette(); view->engine()->addImageProvider("MxcImage", imgProvider); view->engine()->addImageProvider("colorimage", colorImgProvider); @@ -121,7 +122,7 @@ TimelineViewManager::sync(const mtx::responses::Rooms &rooms) const auto &room_model = models.value(QString::fromStdString(room_id)); room_model->addEvents(room.timeline); - if (ChatPage::instance()->userSettings()->isTypingNotificationsEnabled()) { + if (ChatPage::instance()->userSettings()->typingNotifications()) { std::vector typing; typing.reserve(room.ephemeral.typing.size()); for (const auto &user : room.ephemeral.typing) { @@ -141,7 +142,7 @@ TimelineViewManager::addRoom(const QString &room_id) { if (!models.contains(room_id)) { QSharedPointer newRoom(new TimelineModel(this, room_id)); - newRoom->setDecryptDescription(settings->isDecryptSidebarEnabled()); + newRoom->setDecryptDescription(settings->decryptSidebar()); connect(newRoom.data(), &TimelineModel::newEncryptedImage, @@ -225,7 +226,7 @@ TimelineViewManager::queueTextMessage(const QString &msg) mtx::events::msg::Text text = {}; text.body = msg.trimmed().toStdString(); - if (settings->isMarkdownEnabled()) { + if (settings->markdown()) { text.formatted_body = utils::markdownToHtml(msg).toStdString(); // Don't send formatted_body, when we don't need to @@ -253,7 +254,7 @@ TimelineViewManager::queueTextMessage(const QString &msg) // NOTE(Nico): rich replies always need a formatted_body! text.format = "org.matrix.custom.html"; - if (settings->isMarkdownEnabled()) + if (settings->markdown()) text.formatted_body = utils::getFormattedQuoteBody(related, utils::markdownToHtml(msg)) .toStdString(); @@ -276,7 +277,7 @@ TimelineViewManager::queueEmoteMessage(const QString &msg) mtx::events::msg::Emote emote; emote.body = msg.trimmed().toStdString(); - if (html != msg.trimmed().toHtmlEscaped() && settings->isMarkdownEnabled()) { + if (html != msg.trimmed().toHtmlEscaped() && settings->markdown()) { emote.formatted_body = html.toStdString(); emote.format = "org.matrix.custom.html"; } diff --git a/src/ui/Avatar.cpp b/src/ui/Avatar.cpp index cb77d1a8..70ebfcf2 100644 --- a/src/ui/Avatar.cpp +++ b/src/ui/Avatar.cpp @@ -1,4 +1,5 @@ #include +#include #include #include "AvatarProvider.h" diff --git a/src/ui/InfoMessage.cpp b/src/ui/InfoMessage.cpp index 27bc0a5f..0b69564d 100644 --- a/src/ui/InfoMessage.cpp +++ b/src/ui/InfoMessage.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include diff --git a/src/ui/Painter.h b/src/ui/Painter.h index 4d227a5a..2bb0981b 100644 --- a/src/ui/Painter.h +++ b/src/ui/Painter.h @@ -3,6 +3,7 @@ #include #include #include +#include #include class Painter : public QPainter @@ -163,5 +164,5 @@ public: private: Painter &_painter; - QPainter::RenderHints hints_ = 0; + QPainter::RenderHints hints_ = {}; }; diff --git a/third_party/SingleApplication-3.0.19/.gitignore b/third_party/SingleApplication-3.1.3.1/.gitignore similarity index 58% rename from third_party/SingleApplication-3.0.19/.gitignore rename to third_party/SingleApplication-3.1.3.1/.gitignore index ad390758..35533fe8 100644 --- a/third_party/SingleApplication-3.0.19/.gitignore +++ b/third_party/SingleApplication-3.1.3.1/.gitignore @@ -6,4 +6,11 @@ /examples/basic/basic /examples/calculator/calculator /examples/sending_arguments/sending_arguments -CMakeLists.txt.user +/**/CMakeLists.txt.user +/**/CMakeCache.txt +/**/CMakeCache/* +/**/CMakeFiles/* +/**/Makefile +/**/cmake_install.cmake +/**/*_autogen/ +libSingleApplication.a diff --git a/third_party/SingleApplication-3.0.19/CHANGELOG.md b/third_party/SingleApplication-3.1.3.1/CHANGELOG.md similarity index 91% rename from third_party/SingleApplication-3.0.19/CHANGELOG.md rename to third_party/SingleApplication-3.1.3.1/CHANGELOG.md index 36f1e261..3662b0b2 100644 --- a/third_party/SingleApplication-3.0.19/CHANGELOG.md +++ b/third_party/SingleApplication-3.1.3.1/CHANGELOG.md @@ -1,6 +1,40 @@ Changelog ========= +If by accident I have forgotten to credit someone in the CHANGELOG, email me and I will fix it. + +__3.1.3.1__ +--------- +* CMake build system improvements +* Fixed Clang Tidy warnings + + _Hennadii Chernyshchyk_ + +__3.1.3__ +--------- +* Improved `CMakeLists.txt` + + _Hennadii Chernyshchyk_ + +__3.1.2__ +--------- + +* Fix a crash when exiting an application on Android and iOS + + _Emeric Grange_ + +__3.1.1a__ +---------- + +* Added currentUser() method that returns the user the current instance is running as. + + _Leander Schulten_ + +__3.1.0a__ +---------- + +* Added primaryUser() method that returns the user the primary instance is running as. + __3.0.19__ ---------- diff --git a/third_party/SingleApplication-3.0.19/CMakeLists.txt b/third_party/SingleApplication-3.1.3.1/CMakeLists.txt similarity index 52% rename from third_party/SingleApplication-3.0.19/CMakeLists.txt rename to third_party/SingleApplication-3.1.3.1/CMakeLists.txt index 076d514d..85dba84c 100644 --- a/third_party/SingleApplication-3.0.19/CMakeLists.txt +++ b/third_party/SingleApplication-3.1.3.1/CMakeLists.txt @@ -1,45 +1,34 @@ -cmake_minimum_required(VERSION 3.1.0) +cmake_minimum_required(VERSION 3.7.0) -project(SingleApplication) +project(SingleApplication LANGUAGES CXX) -set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) -# SingleApplication base class -set(QAPPLICATION_CLASS QCoreApplication CACHE STRING "Inheritance class for SingleApplication") -set_property(CACHE QAPPLICATION_CLASS PROPERTY STRINGS QApplication QGuiApplication QCoreApplication) - -# Libary target add_library(${PROJECT_NAME} STATIC singleapplication.cpp singleapplication_p.cpp - ) +) +add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) # Find dependencies -find_package(Qt5Network) +find_package(Qt5 COMPONENTS Network REQUIRED) +target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Network) + if(QAPPLICATION_CLASS STREQUAL QApplication) find_package(Qt5 COMPONENTS Widgets REQUIRED) + target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Widgets) elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication) find_package(Qt5 COMPONENTS Gui REQUIRED) + target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Gui) else() + set(QAPPLICATION_CLASS QCoreApplication) find_package(Qt5 COMPONENTS Core REQUIRED) -endif() -target_compile_definitions(${PROJECT_NAME} PUBLIC QAPPLICATION_CLASS=${QAPPLICATION_CLASS}) - -# Link dependencies -target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Network) -if(QAPPLICATION_CLASS STREQUAL QApplication) - target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets) -elseif(QAPPLICATION_CLASS STREQUAL QGuiApplication) - target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Gui) -else() - target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core) + target_link_libraries(${PROJECT_NAME} PUBLIC Qt5::Core) endif() if(WIN32) target_link_libraries(${PROJECT_NAME} PRIVATE advapi32) endif() +target_compile_definitions(${PROJECT_NAME} PUBLIC QAPPLICATION_CLASS=${QAPPLICATION_CLASS}) target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - -add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) diff --git a/third_party/SingleApplication-3.0.19/LICENSE b/third_party/SingleApplication-3.1.3.1/LICENSE similarity index 96% rename from third_party/SingleApplication-3.0.19/LICENSE rename to third_party/SingleApplication-3.1.3.1/LICENSE index 85b2a149..a82e5a68 100644 --- a/third_party/SingleApplication-3.0.19/LICENSE +++ b/third_party/SingleApplication-3.1.3.1/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) Itay Grudev 2015 - 2016 +Copyright (c) Itay Grudev 2015 - 2020 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/third_party/SingleApplication-3.0.19/README.md b/third_party/SingleApplication-3.1.3.1/README.md similarity index 93% rename from third_party/SingleApplication-3.0.19/README.md rename to third_party/SingleApplication-3.1.3.1/README.md index 5d609865..3c36b557 100644 --- a/third_party/SingleApplication-3.0.19/README.md +++ b/third_party/SingleApplication-3.1.3.1/README.md @@ -1,5 +1,6 @@ SingleApplication ================= +[![CI](https://github.com/itay-grudev/SingleApplication/workflows/CI:%20Build%20Test/badge.svg)](https://github.com/itay-grudev/SingleApplication/actions) This is a replacement of the QtSingleApplication for `Qt5`. @@ -15,18 +16,6 @@ class you specify via the `QAPPLICATION_CLASS` macro (`QCoreApplication` is the default). Further usage is similar to the use of the `Q[Core|Gui]Application` classes. -The library sets up a `QLocalServer` and a `QSharedMemory` block. The first -instance of your Application is your Primary Instance. It would check if the -shared memory block exists and if not it will start a `QLocalServer` and listen -for connections. Each subsequent instance of your application would check if the -shared memory block exists and if it does, it will connect to the QLocalServer -to notify the primary instance that a new instance had been started, after which -it would terminate with status code `0`. In the Primary Instance -`SingleApplication` would emit the `instanceStarted()` signal upon detecting -that a new instance had been started. - -The library uses `stdlib` to terminate the program with the `exit()` function. - You can use the library as if you use any other `QCoreApplication` derived class: @@ -43,8 +32,7 @@ int main( int argc, char* argv[] ) ``` To include the library files I would recommend that you add it as a git -submodule to your project and include it's contents with a `.pri` file. Here is -how: +submodule to your project. Here is how: ```bash git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication @@ -66,13 +54,27 @@ Then include the subdirectory in your `CMakeLists.txt` project file. ```cmake set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication") add_subdirectory(src/third-party/singleapplication) +target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication) ``` + +The library sets up a `QLocalServer` and a `QSharedMemory` block. The first +instance of your Application is your Primary Instance. It would check if the +shared memory block exists and if not it will start a `QLocalServer` and listen +for connections. Each subsequent instance of your application would check if the +shared memory block exists and if it does, it will connect to the QLocalServer +to notify the primary instance that a new instance had been started, after which +it would terminate with status code `0`. In the Primary Instance +`SingleApplication` would emit the `instanceStarted()` signal upon detecting +that a new instance had been started. + +The library uses `stdlib` to terminate the program with the `exit()` function. + Also don't forget to specify which `QCoreApplication` class your app is using if it is not `QCoreApplication` as in examples above. The `Instance Started` signal ------------------------- +----------------------------- The SingleApplication class implements a `instanceStarted()` signal. You can bind to that signal to raise your application's window when a new instance had @@ -204,6 +206,22 @@ qint64 SingleApplication::primaryPid() Returns the process ID (PID) of the primary instance. +--- + +```cpp +QString SingleApplication::primaryUser() +``` + +Returns the username the primary instance is running as. + +--- + +```cpp +QString SingleApplication::currentUser() +``` + +Returns the username the current instance is running as. + ### Signals ```cpp diff --git a/third_party/SingleApplication-3.1.3.1/SingleApplication b/third_party/SingleApplication-3.1.3.1/SingleApplication new file mode 100644 index 00000000..8ead1a42 --- /dev/null +++ b/third_party/SingleApplication-3.1.3.1/SingleApplication @@ -0,0 +1 @@ +#include "singleapplication.h" diff --git a/third_party/SingleApplication-3.0.19/Windows.md b/third_party/SingleApplication-3.1.3.1/Windows.md similarity index 100% rename from third_party/SingleApplication-3.0.19/Windows.md rename to third_party/SingleApplication-3.1.3.1/Windows.md diff --git a/third_party/SingleApplication-3.0.19/singleapplication.cpp b/third_party/SingleApplication-3.1.3.1/singleapplication.cpp similarity index 93% rename from third_party/SingleApplication-3.0.19/singleapplication.cpp rename to third_party/SingleApplication-3.1.3.1/singleapplication.cpp index 8ff8747a..9af38804 100644 --- a/third_party/SingleApplication-3.0.19/singleapplication.cpp +++ b/third_party/SingleApplication-3.1.3.1/singleapplication.cpp @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) Itay Grudev 2015 - 2018 +// Copyright (c) Itay Grudev 2015 - 2020 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -85,7 +85,7 @@ SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSeconda } } - InstancesInfo* inst = static_cast( d->memory->data() ); + auto *inst = static_cast( d->memory->data() ); QElapsedTimer time; time.start(); @@ -172,7 +172,19 @@ qint64 SingleApplication::primaryPid() return d->primaryPid(); } -bool SingleApplication::sendMessage( QByteArray message, int timeout ) +QString SingleApplication::primaryUser() +{ + Q_D(SingleApplication); + return d->primaryUser(); +} + +QString SingleApplication::currentUser() +{ + Q_D(SingleApplication); + return d->getUsername(); +} + +bool SingleApplication::sendMessage( const QByteArray &message, int timeout ) { Q_D(SingleApplication); diff --git a/third_party/SingleApplication-3.0.19/singleapplication.h b/third_party/SingleApplication-3.1.3.1/singleapplication.h similarity index 91% rename from third_party/SingleApplication-3.0.19/singleapplication.h rename to third_party/SingleApplication-3.1.3.1/singleapplication.h index cb505971..fd806a3d 100644 --- a/third_party/SingleApplication-3.0.19/singleapplication.h +++ b/third_party/SingleApplication-3.1.3.1/singleapplication.h @@ -43,7 +43,7 @@ class SingleApplication : public QAPPLICATION_CLASS { Q_OBJECT - typedef QAPPLICATION_CLASS app_t; + using app_t = QAPPLICATION_CLASS; public: /** @@ -86,7 +86,7 @@ public: * @see See the corresponding QAPPLICATION_CLASS constructor for reference */ explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000 ); - ~SingleApplication(); + ~SingleApplication() override; /** * @brief Returns if the instance is the primary instance @@ -112,6 +112,18 @@ public: */ qint64 primaryPid(); + /** + * @brief Returns the username of the user running the primary instance + * @returns {QString} + */ + QString primaryUser(); + + /** + * @brief Returns the username of the current user + * @returns {QString} + */ + QString currentUser(); + /** * @brief Sends a message to the primary instance. Returns true on success. * @param {int} timeout - Timeout for connecting @@ -119,7 +131,7 @@ public: * @note sendMessage() will return false if invoked from the primary * instance. */ - bool sendMessage( QByteArray message, int timeout = 100 ); + bool sendMessage( const QByteArray &message, int timeout = 100 ); Q_SIGNALS: void instanceStarted(); diff --git a/third_party/SingleApplication-3.1.3.1/singleapplication.pri b/third_party/SingleApplication-3.1.3.1/singleapplication.pri new file mode 100644 index 00000000..ae81f599 --- /dev/null +++ b/third_party/SingleApplication-3.1.3.1/singleapplication.pri @@ -0,0 +1,20 @@ +QT += core network +CONFIG += c++11 + +HEADERS += $$PWD/SingleApplication \ + $$PWD/singleapplication.h \ + $$PWD/singleapplication_p.h +SOURCES += $$PWD/singleapplication.cpp \ + $$PWD/singleapplication_p.cpp + +INCLUDEPATH += $$PWD + +win32 { + msvc:LIBS += Advapi32.lib + gcc:LIBS += -ladvapi32 +} + +DISTFILES += \ + $$PWD/README.md \ + $$PWD/CHANGELOG.md \ + $$PWD/Windows.md diff --git a/third_party/SingleApplication-3.0.19/singleapplication_p.cpp b/third_party/SingleApplication-3.1.3.1/singleapplication_p.cpp similarity index 85% rename from third_party/SingleApplication-3.0.19/singleapplication_p.cpp rename to third_party/SingleApplication-3.1.3.1/singleapplication_p.cpp index 884fe631..705609f2 100644 --- a/third_party/SingleApplication-3.0.19/singleapplication_p.cpp +++ b/third_party/SingleApplication-3.1.3.1/singleapplication_p.cpp @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) Itay Grudev 2015 - 2018 +// Copyright (c) Itay Grudev 2015 - 2020 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -69,18 +69,52 @@ SingleApplicationPrivate::~SingleApplicationPrivate() delete socket; } - memory->lock(); - InstancesInfo* inst = static_cast(memory->data()); - if( server != nullptr ) { - server->close(); - delete server; - inst->primary = false; - inst->primaryPid = -1; - inst->checksum = blockChecksum(); - } - memory->unlock(); + if( memory != nullptr ) { + memory->lock(); + auto *inst = static_cast(memory->data()); + if( server != nullptr ) { + server->close(); + delete server; + inst->primary = false; + inst->primaryPid = -1; + inst->primaryUser[0] = '\0'; + inst->checksum = blockChecksum(); + } + memory->unlock(); - delete memory; + delete memory; + } +} + +QString SingleApplicationPrivate::getUsername() +{ +#ifdef Q_OS_WIN + wchar_t username[UNLEN + 1]; + // Specifies size of the buffer on input + DWORD usernameLength = UNLEN + 1; + if( GetUserNameW( username, &usernameLength ) ) + return QString::fromWCharArray( username ); +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + return QString::fromLocal8Bit( qgetenv( "USERNAME" ) ); +#else + return qEnvironmentVariable( "USERNAME" ); +#endif +#endif +#ifdef Q_OS_UNIX + QString username; + uid_t uid = geteuid(); + struct passwd *pw = getpwuid( uid ); + if( pw ) + username = QString::fromLocal8Bit( pw->pw_name ); + if ( username.isEmpty() ) { +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + username = QString::fromLocal8Bit( qgetenv( "USER" ) ); +#else + username = qEnvironmentVariable( "USER" ); +#endif + } + return username; +#endif } void SingleApplicationPrivate::genBlockServerName() @@ -105,28 +139,7 @@ void SingleApplicationPrivate::genBlockServerName() // User level block requires a user specific data in the hash if( options & SingleApplication::Mode::User ) { -#ifdef Q_OS_WIN - wchar_t username [ UNLEN + 1 ]; - // Specifies size of the buffer on input - DWORD usernameLength = UNLEN + 1; - if( GetUserNameW( username, &usernameLength ) ) { - appData.addData( QString::fromWCharArray(username).toUtf8() ); - } else { - appData.addData( qgetenv("USERNAME") ); - } -#endif -#ifdef Q_OS_UNIX - QByteArray username; - uid_t uid = geteuid(); - struct passwd *pw = getpwuid(uid); - if( pw ) { - username = pw->pw_name; - } - if( username.isEmpty() ) { - username = qgetenv("USER"); - } - appData.addData(username); -#endif + appData.addData( getUsername().toUtf8() ); } // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with @@ -136,10 +149,11 @@ void SingleApplicationPrivate::genBlockServerName() void SingleApplicationPrivate::initializeMemoryBlock() { - InstancesInfo* inst = static_cast( memory->data() ); + auto *inst = static_cast( memory->data() ); inst->primary = false; inst->secondary = 0; inst->primaryPid = -1; + inst->primaryUser[0] = '\0'; inst->checksum = blockChecksum(); } @@ -169,10 +183,12 @@ void SingleApplicationPrivate::startPrimary() ); // Reset the number of connections - InstancesInfo* inst = static_cast ( memory->data() ); + auto *inst = static_cast ( memory->data() ); inst->primary = true; inst->primaryPid = q->applicationPid(); + strncpy( inst->primaryUser, getUsername().toUtf8().data(), 127 ); + inst->primaryUser[127] = '\0'; inst->checksum = blockChecksum(); instanceNumber = 0; @@ -250,13 +266,25 @@ qint64 SingleApplicationPrivate::primaryPid() qint64 pid; memory->lock(); - InstancesInfo* inst = static_cast( memory->data() ); + auto *inst = static_cast( memory->data() ); pid = inst->primaryPid; memory->unlock(); return pid; } +QString SingleApplicationPrivate::primaryUser() +{ + QByteArray username; + + memory->lock(); + auto *inst = static_cast( memory->data() ); + username = inst->primaryUser; + memory->unlock(); + + return QString::fromUtf8( username ); +} + /** * @brief Executed when a connection has been made to the LocalServer */ diff --git a/third_party/SingleApplication-3.0.19/singleapplication_p.h b/third_party/SingleApplication-3.1.3.1/singleapplication_p.h similarity index 92% rename from third_party/SingleApplication-3.0.19/singleapplication_p.h rename to third_party/SingleApplication-3.1.3.1/singleapplication_p.h index e2c361fb..29ba346b 100644 --- a/third_party/SingleApplication-3.0.19/singleapplication_p.h +++ b/third_party/SingleApplication-3.1.3.1/singleapplication_p.h @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) Itay Grudev 2015 - 2016 +// Copyright (c) Itay Grudev 2015 - 2020 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -42,14 +42,13 @@ struct InstancesInfo { quint32 secondary; qint64 primaryPid; quint16 checksum; + char primaryUser[128]; }; struct ConnectionInfo { - explicit ConnectionInfo() : - msgLen(0), instanceId(0), stage(0) {} - qint64 msgLen; - quint32 instanceId; - quint8 stage; + qint64 msgLen = 0; + quint32 instanceId = 0; + quint8 stage = 0; }; class SingleApplicationPrivate : public QObject { @@ -69,8 +68,9 @@ public: Q_DECLARE_PUBLIC(SingleApplication) SingleApplicationPrivate( SingleApplication *q_ptr ); - ~SingleApplicationPrivate(); + ~SingleApplicationPrivate() override; + QString getUsername(); void genBlockServerName(); void initializeMemoryBlock(); void startPrimary(); @@ -78,6 +78,7 @@ public: void connectToPrimary(int msecs, ConnectionType connectionType ); quint16 blockChecksum(); qint64 primaryPid(); + QString primaryUser(); void readInitMessageHeader(QLocalSocket *socket); void readInitMessageBody(QLocalSocket *socket);