mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-25 12:38:48 +03:00
commit
4047a90302
75 changed files with 5159 additions and 6118 deletions
|
@ -11,5 +11,7 @@ FILES=$(find src -type f -type f \( -iname "*.cpp" -o -iname "*.h" \))
|
|||
|
||||
for f in $FILES
|
||||
do
|
||||
clang-format -i "$f" && git diff --exit-code
|
||||
clang-format -i "$f"
|
||||
done;
|
||||
|
||||
git diff --exit-code
|
||||
|
|
|
@ -54,5 +54,7 @@ if [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
|||
qt${QT_PKG}tools \
|
||||
qt${QT_PKG}svg \
|
||||
qt${QT_PKG}multimedia \
|
||||
qt${QT_PKG}quickcontrols2 \
|
||||
qt${QT_PKG}graphicaleffects \
|
||||
liblmdb-dev
|
||||
fi
|
||||
|
|
|
@ -44,8 +44,7 @@ do
|
|||
linuxdeployqt=$res
|
||||
done
|
||||
|
||||
./"$linuxdeployqt" ${DIR}/usr/share/applications/*.desktop -unsupported-allow-new-glibc -bundle-non-qt-libs
|
||||
./"$linuxdeployqt" ${DIR}/usr/share/applications/*.desktop -unsupported-allow-new-glibc -appimage
|
||||
./"$linuxdeployqt" ${DIR}/usr/share/applications/*.desktop -unsupported-allow-new-glibc -bundle-non-qt-libs -qmldir=./resources/qml -appimage
|
||||
|
||||
chmod +x nheko-*x86_64.AppImage
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ PATH=/usr/local/opt/qt/bin/:${PATH}
|
|||
mkdir -p nheko.app/Contents/Frameworks
|
||||
find "${ICU_LIB}" -type l -name "*.dylib" -exec cp -a -n {} nheko.app/Contents/Frameworks/ \; || true
|
||||
|
||||
sudo macdeployqt nheko.app -dmg -always-overwrite
|
||||
sudo macdeployqt nheko.app -dmg -always-overwrite -qmldir=../resources/qml/
|
||||
|
||||
user=$(id -nu)
|
||||
sudo chown "${user}" nheko.dmg
|
||||
|
|
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -1,8 +1,11 @@
|
|||
build
|
||||
/build*
|
||||
tags
|
||||
cscope*
|
||||
.clang_complete
|
||||
*wintoastlib*
|
||||
/.ccls-cache
|
||||
/.exrc
|
||||
.gdb_history
|
||||
|
||||
# GTAGS
|
||||
GTAGS
|
||||
|
@ -49,6 +52,10 @@ ui_*.h
|
|||
*.qmlproject.user
|
||||
*.qmlproject.user.*
|
||||
|
||||
# Vim
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
#####=== CMake ===#####
|
||||
|
||||
CMakeCache.txt
|
||||
|
|
|
@ -15,7 +15,8 @@ matrix:
|
|||
include:
|
||||
- os: osx
|
||||
compiler: clang
|
||||
osx_image: xcode9.2
|
||||
# Use the default osx image, because that one is actually tested to work with homebrew and probably the oldest supported version
|
||||
# osx_image: xcode9
|
||||
env:
|
||||
- DEPLOYMENT=1
|
||||
- USE_BUNDLED_BOOST=0
|
||||
|
@ -42,8 +43,8 @@ matrix:
|
|||
env:
|
||||
- CXX_COMPILER=g++-8
|
||||
- C_COMPILER=gcc-8
|
||||
- QT_VERSION=571
|
||||
- QT_PKG=57
|
||||
- QT_VERSION=58
|
||||
- QT_PKG=58
|
||||
- USE_BUNDLED_BOOST=1
|
||||
- USE_BUNDLED_CMARK=1
|
||||
- USE_BUNDLED_JSON=1
|
||||
|
|
|
@ -69,7 +69,8 @@ include(LMDB)
|
|||
#
|
||||
# Discover Qt dependencies.
|
||||
#
|
||||
find_package(Qt5 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia REQUIRED)
|
||||
find_package(Qt5 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia Qml QuickControls2 QuickWidgets REQUIRED)
|
||||
find_package(Qt5QuickCompiler)
|
||||
find_package(Qt5DBus)
|
||||
|
||||
if (APPLE)
|
||||
|
@ -192,12 +193,8 @@ set(SRC_FILES
|
|||
|
||||
# Timeline
|
||||
src/timeline/TimelineViewManager.cpp
|
||||
src/timeline/TimelineItem.cpp
|
||||
src/timeline/TimelineView.cpp
|
||||
src/timeline/widgets/AudioItem.cpp
|
||||
src/timeline/widgets/FileItem.cpp
|
||||
src/timeline/widgets/ImageItem.cpp
|
||||
src/timeline/widgets/VideoItem.cpp
|
||||
src/timeline/TimelineModel.cpp
|
||||
src/timeline/DelegateChooser.cpp
|
||||
|
||||
# UI components
|
||||
src/ui/Avatar.cpp
|
||||
|
@ -229,6 +226,8 @@ set(SRC_FILES
|
|||
src/Logging.cpp
|
||||
src/MainWindow.cpp
|
||||
src/MatrixClient.cpp
|
||||
src/MxcImageProvider.cpp
|
||||
src/ColorImageProvider.cpp
|
||||
src/QuickSwitcher.cpp
|
||||
src/Olm.cpp
|
||||
src/RegisterPage.cpp
|
||||
|
@ -333,13 +332,9 @@ qt5_wrap_cpp(MOC_HEADERS
|
|||
src/emoji/PickButton.h
|
||||
|
||||
# Timeline
|
||||
src/timeline/TimelineItem.h
|
||||
src/timeline/TimelineView.h
|
||||
src/timeline/TimelineViewManager.h
|
||||
src/timeline/widgets/AudioItem.h
|
||||
src/timeline/widgets/FileItem.h
|
||||
src/timeline/widgets/ImageItem.h
|
||||
src/timeline/widgets/VideoItem.h
|
||||
src/timeline/TimelineModel.h
|
||||
src/timeline/DelegateChooser.h
|
||||
|
||||
# UI components
|
||||
src/ui/Avatar.h
|
||||
|
@ -370,7 +365,6 @@ qt5_wrap_cpp(MOC_HEADERS
|
|||
src/CommunitiesList.h
|
||||
src/LoginPage.h
|
||||
src/MainWindow.h
|
||||
src/MatrixClient.h
|
||||
src/InviteeItem.h
|
||||
src/QuickSwitcher.h
|
||||
src/RegisterPage.h
|
||||
|
@ -405,6 +399,9 @@ set(COMMON_LIBS
|
|||
Qt5::Svg
|
||||
Qt5::Concurrent
|
||||
Qt5::Multimedia
|
||||
Qt5::Qml
|
||||
Qt5::QuickControls2
|
||||
Qt5::QuickWidgets
|
||||
nlohmann_json::nlohmann_json)
|
||||
|
||||
if(APPVEYOR_BUILD)
|
||||
|
@ -448,6 +445,7 @@ if(APPLE)
|
|||
target_link_libraries (nheko ${NHEKO_LIBS} Qt5::MacExtras)
|
||||
elseif(WIN32)
|
||||
add_executable (nheko ${OS_BUNDLE} ${ICON_FILE} ${NHEKO_DEPS})
|
||||
target_compile_definitions(nheko PRIVATE WIN32_LEAN_AND_MEAN)
|
||||
target_link_libraries (nheko ${NTDLIB} ${NHEKO_LIBS} Qt5::WinMain)
|
||||
else()
|
||||
add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})
|
||||
|
|
|
@ -7,7 +7,7 @@ RUN \
|
|||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
|
||||
apt-get update -qq && \
|
||||
apt-get install -y \
|
||||
qt510base qt510tools qt510svg qt510multimedia \
|
||||
qt510base qt510tools qt510svg qt510multimedia qt510quickcontrols2 qt510graphicaleffects \
|
||||
gcc-5 g++-5
|
||||
|
||||
RUN \
|
||||
|
|
2
Makefile
2
Makefile
|
@ -68,7 +68,7 @@ update-translations:
|
|||
-locations relative \
|
||||
-Iinclude/dialogs \
|
||||
-Iinclude \
|
||||
src/ -ts resources/langs/nheko_*.ts -no-obsolete
|
||||
src/ resources/qml/ -ts resources/langs/nheko_*.ts -no-obsolete
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
|
|
|
@ -89,8 +89,9 @@ sudo port install nheko
|
|||
|
||||
### Build Requirements
|
||||
|
||||
- Qt5 (5.7 or greater). Qt 5.7 adds support for color font rendering with
|
||||
Freetype, which is essential to properly support emoji.
|
||||
- Qt5 (5.8 or greater). Qt 5.7 adds support for color font rendering with
|
||||
Freetype, which is essential to properly support emoji, 5.8 adds some features
|
||||
to make interopability with Qml easier.
|
||||
- CMake 3.1 or greater.
|
||||
- [mtxclient](https://github.com/Nheko-Reborn/mtxclient)
|
||||
- [LMDB](https://symas.com/lightning-memory-mapped-database/)
|
||||
|
|
|
@ -21,4 +21,9 @@ if(NOT EXISTS ${_qrc})
|
|||
endif()
|
||||
|
||||
qt5_add_resources(LANG_QRC ${_qrc})
|
||||
qt5_add_resources(QRC resources/res.qrc)
|
||||
#qt5_add_resources(QRC resources/res.qrc)
|
||||
if(Qt5QuickCompiler_FOUND)
|
||||
qtquick_compiler_add_resources(QRC resources/res.qrc)
|
||||
else()
|
||||
qt5_add_resources(QRC resources/res.qrc)
|
||||
endif()
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="de">
|
||||
<context>
|
||||
<name>AudioItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/AudioItem.cpp" line="+118"/>
|
||||
<source>Save File</source>
|
||||
<translation>In Datei speichern</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
|
@ -32,7 +24,7 @@
|
|||
<translation>Hochladen der Videodatei fehlgeschlagen. Bitte versuche es erneut.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+380"/>
|
||||
<location line="+393"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation>Wiederherstellung des OLM Accounts fehlgeschlagen. Bitte logge dich erneut ein.</translation>
|
||||
</message>
|
||||
|
@ -42,18 +34,18 @@
|
|||
<translation>Gespeicherte Nachrichten konnten nicht wiederhergestellt werden. Bitte melde Dich erneut an.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+198"/>
|
||||
<location line="+181"/>
|
||||
<source>Failed to setup encryption keys. Server response: %1 %2. Please try again later.</source>
|
||||
<translation>Fehler beim Setup der Verschlüsselungsschlüssel. Servermeldung: %1 %2. Bitte versuche es später erneut.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+51"/>
|
||||
<location line="+153"/>
|
||||
<location line="+155"/>
|
||||
<source>Please try to login again: %1</source>
|
||||
<translation>Bitte melde dich erneut an: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-45"/>
|
||||
<location line="-47"/>
|
||||
<source>Room creation failed: %1</source>
|
||||
<translation>Raum konnte nicht erstellt werden: %1</translation>
|
||||
</message>
|
||||
|
@ -116,19 +108,11 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FileItem</name>
|
||||
<name>EncryptionIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/FileItem.cpp" line="+107"/>
|
||||
<source>Save File</source>
|
||||
<translation>Datei speichern</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ImageItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/ImageItem.cpp" line="+241"/>
|
||||
<source>Save image</source>
|
||||
<translation>Bild speichern</translation>
|
||||
<location filename="../qml/EncryptionIndicator.qml" line="+11"/>
|
||||
<source>Encrypted</source>
|
||||
<translation>Verschlüsselt</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -200,7 +184,7 @@
|
|||
<context>
|
||||
<name>MemberList</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+96"/>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+89"/>
|
||||
<source>Room members</source>
|
||||
<translation>Teilnehmerliste</translation>
|
||||
</message>
|
||||
|
@ -210,6 +194,14 @@
|
|||
<translation>OK</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Placeholder.qml" line="+4"/>
|
||||
<source>unimplemented event: </source>
|
||||
<translation>unimplementiertes event: </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QuickSwitcher</name>
|
||||
<message>
|
||||
|
@ -218,6 +210,14 @@
|
|||
<translation>Raum suchen…</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation>gelöscht</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -277,7 +277,7 @@
|
|||
<context>
|
||||
<name>RoomInfo</name>
|
||||
<message>
|
||||
<location filename="../../src/Cache.cpp" line="+2205"/>
|
||||
<location filename="../../src/Cache.cpp" line="+2307"/>
|
||||
<source>no version stored</source>
|
||||
<translation>keine Version gespeichert</translation>
|
||||
</message>
|
||||
|
@ -285,12 +285,12 @@
|
|||
<context>
|
||||
<name>RoomInfoListItem</name>
|
||||
<message>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+93"/>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+95"/>
|
||||
<source>Leave room</source>
|
||||
<translation>Raum verlassen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+181"/>
|
||||
<location line="+161"/>
|
||||
<source>Accept</source>
|
||||
<translation>Akzeptieren</translation>
|
||||
</message>
|
||||
|
@ -331,25 +331,25 @@
|
|||
<context>
|
||||
<name>StatusIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+107"/>
|
||||
<source>Encrypted</source>
|
||||
<translation>Verschlüsselt</translation>
|
||||
<location filename="../qml/StatusIndicator.qml" line="+13"/>
|
||||
<source>Failed</source>
|
||||
<translation>Fehlgeschlagen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Delivered</source>
|
||||
<translation>Erhalten</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Seen</source>
|
||||
<translation>Gelesen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<location line="+1"/>
|
||||
<source>Sent</source>
|
||||
<translation>Gesendet</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Received</source>
|
||||
<translation>Empfangen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Read</source>
|
||||
<translation>Gelesen</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TextInputWidget</name>
|
||||
|
@ -391,32 +391,9 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineItem</name>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+85"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation>Nachricht zurückziehen fehlgeschlagen: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+39"/>
|
||||
<source>Reply</source>
|
||||
<translation>Antworten</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+11"/>
|
||||
<source>Options</source>
|
||||
<translation>Optionen</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineView.cpp" line="+245"/>
|
||||
<source>Encryption is enabled</source>
|
||||
<translation>Verschlüsselung aktiv</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+65"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation>-- verschlüsselter Event (keine Schlüssel zur Entschlüsselung gefunden) --</translation>
|
||||
|
@ -440,16 +417,90 @@
|
|||
<translation>-- Entschlüsselungsfehler (%1) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+27"/>
|
||||
<location line="+25"/>
|
||||
<source>-- Encrypted Event (Unknown event type) --</source>
|
||||
<comment>Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet</comment>
|
||||
<translation>-- verschlüsselter Event (Unbekannter Eventtyp) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation>Nachricht zurückziehen fehlgeschlagen: %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineRow.qml" line="+57"/>
|
||||
<source>Reply</source>
|
||||
<translation>Antworten</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+14"/>
|
||||
<source>Options</source>
|
||||
<translation>Optionen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+12"/>
|
||||
<source>Read receipts</source>
|
||||
<translation>Lesebestätigungen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Mark as read</source>
|
||||
<translation>Als gelesen markieren</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>View raw message</source>
|
||||
<translation>Zeige rohen Nachrichteninhalt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Redact message</source>
|
||||
<translation>Nachricht löschen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>Save as</source>
|
||||
<translation>Speichern als...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineView.qml" line="+24"/>
|
||||
<source>No room open</source>
|
||||
<translation>Kein Raum geöffnet</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation>Bild speichern</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation>Video speichern</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation>Audiodatei speichern</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation>Datei speichern</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+79"/>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+78"/>
|
||||
<source>Room options</source>
|
||||
<translation>Raumoptionen</translation>
|
||||
</message>
|
||||
|
@ -515,7 +566,7 @@
|
|||
<context>
|
||||
<name>UserSettingsPage</name>
|
||||
<message>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+166"/>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+171"/>
|
||||
<source>Minimize to tray</source>
|
||||
<translation>Ins Benachrichtigungsfeld minimieren</translation>
|
||||
</message>
|
||||
|
@ -529,6 +580,11 @@
|
|||
<source>Group's sidebar</source>
|
||||
<translation>Gruppen-Seitenleiste</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Circular Avatars</source>
|
||||
<translation>Runde Profilbilder</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Typing notifications</source>
|
||||
|
@ -605,7 +661,7 @@
|
|||
<translation>ALLGEMEINES</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+156"/>
|
||||
<location line="+161"/>
|
||||
<source>Open Sessions File</source>
|
||||
<translation>Öffne Sessions Datei</translation>
|
||||
</message>
|
||||
|
@ -825,7 +881,7 @@ Medien-Größe: %2
|
|||
<context>
|
||||
<name>dialogs::ReadReceipts</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+121"/>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+117"/>
|
||||
<source>Read receipts</source>
|
||||
<translation>Lesebestätigungen</translation>
|
||||
</message>
|
||||
|
@ -951,7 +1007,7 @@ Medien-Größe: %2
|
|||
<translation>Aktivierung der Verschlüsselung fehlgeschlagen: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+149"/>
|
||||
<location line="+148"/>
|
||||
<source>Select an avatar</source>
|
||||
<translation>Wähle einen Avatar</translation>
|
||||
</message>
|
||||
|
@ -977,19 +1033,6 @@ Medien-Größe: %2
|
|||
<translation>Hochladen der Bilddatei fehlgeschlagen: %s</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserMentions</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/UserMentions.cpp" line="+53"/>
|
||||
<source>This Room</source>
|
||||
<translation>Dieser Raum</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation>Alle Räume</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserProfile</name>
|
||||
<message>
|
||||
|
@ -1013,7 +1056,7 @@ Medien-Größe: %2
|
|||
<translation>Gespräch beginnen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+57"/>
|
||||
<location line="+56"/>
|
||||
<source>Devices</source>
|
||||
<translation>Geräte</translation>
|
||||
</message>
|
||||
|
@ -1064,69 +1107,103 @@ Medien-Größe: %2
|
|||
<context>
|
||||
<name>message-description sent:</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="+104"/>
|
||||
<source>%1 an audio clip</source>
|
||||
<translation>%1 einen Audioclip</translation>
|
||||
<location filename="../../src/Utils.h" line="+95"/>
|
||||
<source>You sent an audio clip</source>
|
||||
<translation>Du hast eine Audiodatei gesendet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 an image</source>
|
||||
<translation>%1 ein Bild</translation>
|
||||
<source>%1 sent an audio clip</source>
|
||||
<translation>%1 hat eine Audiodatei gesendet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent an image</source>
|
||||
<translation>Du hast ein Bild gesendet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a file</source>
|
||||
<translation>%1 eine Datei</translation>
|
||||
<source>%1 sent an image</source>
|
||||
<translation>%1 hat ein Bild gesendet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a file</source>
|
||||
<translation>Du hast eine Datei gesendet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a video clip</source>
|
||||
<translation>%1 einen Videoclip</translation>
|
||||
<source>%1 sent a file</source>
|
||||
<translation>%1 hat eine Datei gesendet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a video</source>
|
||||
<translation>Du hast ein Video gesendet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a sticker</source>
|
||||
<translation>%1 einen Sticker</translation>
|
||||
<source>%1 sent a video</source>
|
||||
<translation>%1 hat ein Video gesendet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a sticker</source>
|
||||
<translation>Du hast einen Sticker gesendet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a notification</source>
|
||||
<translation>1% eine Benachrichtigung</translation>
|
||||
<source>%1 sent a sticker</source>
|
||||
<translation>%1 hat einen Sticker gesendet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a notification</source>
|
||||
<translation>Du hast eine Benachrichtigung gesendet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent a notification</source>
|
||||
<translation>%1 hat eine Benachrichtigung gesendet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You: %1</source>
|
||||
<translation>Du: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1: %2</source>
|
||||
<translation>%1: %2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+7"/>
|
||||
<source>%1 an encrypted message</source>
|
||||
<translation>1% eine verschüsselte Nachricht</translation>
|
||||
<source>You sent an encrypted message</source>
|
||||
<translation>Du hast eine verschlüsselte Nachricht gesendet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent an encrypted message</source>
|
||||
<translation>%1 hat eine verschlüsselte Nachricht gesendet.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description:</name>
|
||||
<name>popups::UserMentions</name>
|
||||
<message>
|
||||
<location line="-26"/>
|
||||
<source>sent</source>
|
||||
<comment>For when someone else is the sender</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
<location filename="../../src/popups/UserMentions.cpp" line="+61"/>
|
||||
<source>This Room</source>
|
||||
<translation>Dieser Raum</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description: </name>
|
||||
<message>
|
||||
<location line="-2"/>
|
||||
<source>sent</source>
|
||||
<comment>For when you are the sender</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation>Alle Räume</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>utils</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.cpp" line="+46"/>
|
||||
<location filename="../../src/Utils.h" line="+55"/>
|
||||
<source>You</source>
|
||||
<translation>Du</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+219"/>
|
||||
<location filename="../../src/Utils.cpp" line="+282"/>
|
||||
<source>sent a file.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1146,7 +1223,7 @@ Medien-Größe: %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="-23"/>
|
||||
<location filename="../../src/Utils.h" line="+4"/>
|
||||
<source>Unknown Message Type</source>
|
||||
<translation>Unbekannter Nachrichtentyp</translation>
|
||||
</message>
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="el">
|
||||
<context>
|
||||
<name>AudioItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/AudioItem.cpp" line="+118"/>
|
||||
<source>Save File</source>
|
||||
<translation>Αποθήκευση</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
|
@ -32,7 +24,7 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+380"/>
|
||||
<location line="+393"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -42,18 +34,18 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+198"/>
|
||||
<location line="+181"/>
|
||||
<source>Failed to setup encryption keys. Server response: %1 %2. Please try again later.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+51"/>
|
||||
<location line="+153"/>
|
||||
<location line="+155"/>
|
||||
<source>Please try to login again: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-45"/>
|
||||
<location line="-47"/>
|
||||
<source>Room creation failed: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -116,19 +108,11 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FileItem</name>
|
||||
<name>EncryptionIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/FileItem.cpp" line="+107"/>
|
||||
<source>Save File</source>
|
||||
<translation>Αποθήκευση</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ImageItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/ImageItem.cpp" line="+241"/>
|
||||
<source>Save image</source>
|
||||
<translation>Αποθήκευση Εικόνας</translation>
|
||||
<location filename="../qml/EncryptionIndicator.qml" line="+11"/>
|
||||
<source>Encrypted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -200,7 +184,7 @@
|
|||
<context>
|
||||
<name>MemberList</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+96"/>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+89"/>
|
||||
<source>Room members</source>
|
||||
<translation>Μέλη</translation>
|
||||
</message>
|
||||
|
@ -210,6 +194,14 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Placeholder.qml" line="+4"/>
|
||||
<source>unimplemented event: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QuickSwitcher</name>
|
||||
<message>
|
||||
|
@ -218,6 +210,14 @@
|
|||
<translation>Αναζήτηση συνομιλίας...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -277,7 +277,7 @@
|
|||
<context>
|
||||
<name>RoomInfo</name>
|
||||
<message>
|
||||
<location filename="../../src/Cache.cpp" line="+2205"/>
|
||||
<location filename="../../src/Cache.cpp" line="+2307"/>
|
||||
<source>no version stored</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -285,12 +285,12 @@
|
|||
<context>
|
||||
<name>RoomInfoListItem</name>
|
||||
<message>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+93"/>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+95"/>
|
||||
<source>Leave room</source>
|
||||
<translation>Βγές</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+181"/>
|
||||
<location line="+161"/>
|
||||
<source>Accept</source>
|
||||
<translation>Αποδοχή</translation>
|
||||
</message>
|
||||
|
@ -331,25 +331,25 @@
|
|||
<context>
|
||||
<name>StatusIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+107"/>
|
||||
<source>Encrypted</source>
|
||||
<location filename="../qml/StatusIndicator.qml" line="+13"/>
|
||||
<source>Failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Delivered</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Seen</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<location line="+1"/>
|
||||
<source>Sent</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Received</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TextInputWidget</name>
|
||||
|
@ -391,32 +391,9 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineItem</name>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+85"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+39"/>
|
||||
<source>Reply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+11"/>
|
||||
<source>Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineView.cpp" line="+245"/>
|
||||
<source>Encryption is enabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+65"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -440,16 +417,90 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+27"/>
|
||||
<location line="+25"/>
|
||||
<source>-- Encrypted Event (Unknown event type) --</source>
|
||||
<comment>Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineRow.qml" line="+57"/>
|
||||
<source>Reply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+14"/>
|
||||
<source>Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+12"/>
|
||||
<source>Read receipts</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Mark as read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>View raw message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Redact message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>Save as</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineView.qml" line="+24"/>
|
||||
<source>No room open</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Αποθήκευση Εικόνας</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+79"/>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+78"/>
|
||||
<source>Room options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -515,7 +566,7 @@
|
|||
<context>
|
||||
<name>UserSettingsPage</name>
|
||||
<message>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+166"/>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+171"/>
|
||||
<source>Minimize to tray</source>
|
||||
<translation>Ελαχιστοποίηση</translation>
|
||||
</message>
|
||||
|
@ -529,6 +580,11 @@
|
|||
<source>Group's sidebar</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Circular Avatars</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Typing notifications</source>
|
||||
|
@ -605,7 +661,7 @@
|
|||
<translation>ΓΕΝΙΚΑ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+156"/>
|
||||
<location line="+161"/>
|
||||
<source>Open Sessions File</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -823,7 +879,7 @@ Media size: %2
|
|||
<context>
|
||||
<name>dialogs::ReadReceipts</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+121"/>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+117"/>
|
||||
<source>Read receipts</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -949,7 +1005,7 @@ Media size: %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+149"/>
|
||||
<location line="+148"/>
|
||||
<source>Select an avatar</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -975,19 +1031,6 @@ Media size: %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserMentions</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/UserMentions.cpp" line="+53"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserProfile</name>
|
||||
<message>
|
||||
|
@ -1011,7 +1054,7 @@ Media size: %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+57"/>
|
||||
<location line="+56"/>
|
||||
<source>Devices</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1062,69 +1105,103 @@ Media size: %2
|
|||
<context>
|
||||
<name>message-description sent:</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="+104"/>
|
||||
<source>%1 an audio clip</source>
|
||||
<location filename="../../src/Utils.h" line="+95"/>
|
||||
<source>You sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 an image</source>
|
||||
<source>%1 sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a file</source>
|
||||
<source>%1 sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a video clip</source>
|
||||
<source>%1 sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a sticker</source>
|
||||
<source>%1 sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a notification</source>
|
||||
<source>%1 sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1: %2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+7"/>
|
||||
<source>%1 an encrypted message</source>
|
||||
<source>You sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description:</name>
|
||||
<name>popups::UserMentions</name>
|
||||
<message>
|
||||
<location line="-26"/>
|
||||
<source>sent</source>
|
||||
<comment>For when someone else is the sender</comment>
|
||||
<location filename="../../src/popups/UserMentions.cpp" line="+61"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description: </name>
|
||||
<message>
|
||||
<location line="-2"/>
|
||||
<source>sent</source>
|
||||
<comment>For when you are the sender</comment>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>utils</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.cpp" line="+46"/>
|
||||
<location filename="../../src/Utils.h" line="+55"/>
|
||||
<source>You</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+219"/>
|
||||
<location filename="../../src/Utils.cpp" line="+282"/>
|
||||
<source>sent a file.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1144,7 +1221,7 @@ Media size: %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="-23"/>
|
||||
<location filename="../../src/Utils.h" line="+4"/>
|
||||
<source>Unknown Message Type</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="en">
|
||||
<context>
|
||||
<name>AudioItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/AudioItem.cpp" line="+118"/>
|
||||
<source>Save File</source>
|
||||
<translation>Save File</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
|
@ -32,7 +24,7 @@
|
|||
<translation>Failed to upload video. Please try again.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+380"/>
|
||||
<location line="+393"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation>Failed to restore OLM account. Please login again.</translation>
|
||||
</message>
|
||||
|
@ -42,18 +34,18 @@
|
|||
<translation>Failed to restore save data. Please login again.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+198"/>
|
||||
<location line="+181"/>
|
||||
<source>Failed to setup encryption keys. Server response: %1 %2. Please try again later.</source>
|
||||
<translation>Failed to setup encryption keys. Server response: %1 %2. Please try again later.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+51"/>
|
||||
<location line="+153"/>
|
||||
<location line="+155"/>
|
||||
<source>Please try to login again: %1</source>
|
||||
<translation>Please try to login again: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-45"/>
|
||||
<location line="-47"/>
|
||||
<source>Room creation failed: %1</source>
|
||||
<translation>Room creation failed: %1</translation>
|
||||
</message>
|
||||
|
@ -116,19 +108,11 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FileItem</name>
|
||||
<name>EncryptionIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/FileItem.cpp" line="+107"/>
|
||||
<source>Save File</source>
|
||||
<translation>Save File</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ImageItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/ImageItem.cpp" line="+241"/>
|
||||
<source>Save image</source>
|
||||
<translation>Save image</translation>
|
||||
<location filename="../qml/EncryptionIndicator.qml" line="+11"/>
|
||||
<source>Encrypted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -200,7 +184,7 @@
|
|||
<context>
|
||||
<name>MemberList</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+96"/>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+89"/>
|
||||
<source>Room members</source>
|
||||
<translation>Room members</translation>
|
||||
</message>
|
||||
|
@ -210,6 +194,14 @@
|
|||
<translation>OK</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Placeholder.qml" line="+4"/>
|
||||
<source>unimplemented event: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QuickSwitcher</name>
|
||||
<message>
|
||||
|
@ -218,6 +210,14 @@
|
|||
<translation>Search for a room…</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -277,7 +277,7 @@
|
|||
<context>
|
||||
<name>RoomInfo</name>
|
||||
<message>
|
||||
<location filename="../../src/Cache.cpp" line="+2205"/>
|
||||
<location filename="../../src/Cache.cpp" line="+2307"/>
|
||||
<source>no version stored</source>
|
||||
<translation>no version stored</translation>
|
||||
</message>
|
||||
|
@ -285,12 +285,12 @@
|
|||
<context>
|
||||
<name>RoomInfoListItem</name>
|
||||
<message>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+93"/>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+95"/>
|
||||
<source>Leave room</source>
|
||||
<translation>Leave room</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+181"/>
|
||||
<location line="+161"/>
|
||||
<source>Accept</source>
|
||||
<translation>Accept</translation>
|
||||
</message>
|
||||
|
@ -331,24 +331,24 @@
|
|||
<context>
|
||||
<name>StatusIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+107"/>
|
||||
<source>Encrypted</source>
|
||||
<translation>Encrypted</translation>
|
||||
<location filename="../qml/StatusIndicator.qml" line="+13"/>
|
||||
<source>Failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Delivered</source>
|
||||
<translation>Delivered</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Seen</source>
|
||||
<translation>Seen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<location line="+1"/>
|
||||
<source>Sent</source>
|
||||
<translation>Sent</translation>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Received</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -391,65 +391,116 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineItem</name>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+85"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation>Message redaction failed: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+39"/>
|
||||
<source>Reply</source>
|
||||
<translation>Reply</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+11"/>
|
||||
<source>Options</source>
|
||||
<translation>Options</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineView.cpp" line="+245"/>
|
||||
<source>Encryption is enabled</source>
|
||||
<translation>Encryption is enabled</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+65"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation>-- Encrypted Event (No keys found for decryption) --</translation>
|
||||
<translation type="unfinished">-- Encrypted Event (No keys found for decryption) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+15"/>
|
||||
<source>-- Decryption Error (failed to communicate with DB) --</source>
|
||||
<comment>Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session.</comment>
|
||||
<translation>-- Decryption Error (failed to communicate with DB) --</translation>
|
||||
<translation type="unfinished">-- Decryption Error (failed to communicate with DB) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+19"/>
|
||||
<source>-- Decryption Error (failed to retrieve megolm keys from db) --</source>
|
||||
<comment>Placeholder, when the message can't be decrypted, because the DB access failed.</comment>
|
||||
<translation>-- Decryption Error (failed to retrieve megolm keys from db) --</translation>
|
||||
<translation type="unfinished">-- Decryption Error (failed to retrieve megolm keys from db) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+12"/>
|
||||
<source>-- Decryption Error (%1) --</source>
|
||||
<comment>Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1</comment>
|
||||
<translation>-- Decryption Error (%1) --</translation>
|
||||
<translation type="unfinished">-- Decryption Error (%1) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+27"/>
|
||||
<location line="+25"/>
|
||||
<source>-- Encrypted Event (Unknown event type) --</source>
|
||||
<comment>Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet</comment>
|
||||
<translation>-- Encrypted Event (Unknown event type) --</translation>
|
||||
<translation type="unfinished">-- Encrypted Event (Unknown event type) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished">Message redaction failed: %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineRow.qml" line="+57"/>
|
||||
<source>Reply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+14"/>
|
||||
<source>Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+12"/>
|
||||
<source>Read receipts</source>
|
||||
<translation type="unfinished">Read receipts</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Mark as read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>View raw message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Redact message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>Save as</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineView.qml" line="+24"/>
|
||||
<source>No room open</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Save image</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+79"/>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+78"/>
|
||||
<source>Room options</source>
|
||||
<translation>Room options</translation>
|
||||
</message>
|
||||
|
@ -515,7 +566,7 @@
|
|||
<context>
|
||||
<name>UserSettingsPage</name>
|
||||
<message>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+166"/>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+171"/>
|
||||
<source>Minimize to tray</source>
|
||||
<translation>Minimize to tray</translation>
|
||||
</message>
|
||||
|
@ -529,6 +580,11 @@
|
|||
<source>Group's sidebar</source>
|
||||
<translation>Group's sidebar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Circular Avatars</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Typing notifications</source>
|
||||
|
@ -605,7 +661,7 @@
|
|||
<translation>GENERAL</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+156"/>
|
||||
<location line="+161"/>
|
||||
<source>Open Sessions File</source>
|
||||
<translation>Open Sessions File</translation>
|
||||
</message>
|
||||
|
@ -825,7 +881,7 @@ Media size: %2
|
|||
<context>
|
||||
<name>dialogs::ReadReceipts</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+121"/>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+117"/>
|
||||
<source>Read receipts</source>
|
||||
<translation>Read receipts</translation>
|
||||
</message>
|
||||
|
@ -953,7 +1009,7 @@ Media size: %2
|
|||
<translation>Failed to enable encryption: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+149"/>
|
||||
<location line="+148"/>
|
||||
<source>Select an avatar</source>
|
||||
<translation>Select an avatar</translation>
|
||||
</message>
|
||||
|
@ -979,19 +1035,6 @@ Media size: %2
|
|||
<translation>Failed to upload image: %s</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserMentions</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/UserMentions.cpp" line="+53"/>
|
||||
<source>This Room</source>
|
||||
<translation>This Room</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation>All Rooms</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserProfile</name>
|
||||
<message>
|
||||
|
@ -1015,7 +1058,7 @@ Media size: %2
|
|||
<translation>Start a conversation</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+57"/>
|
||||
<location line="+56"/>
|
||||
<source>Devices</source>
|
||||
<translation>Devices</translation>
|
||||
</message>
|
||||
|
@ -1066,69 +1109,103 @@ Media size: %2
|
|||
<context>
|
||||
<name>message-description sent:</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="+104"/>
|
||||
<source>%1 an audio clip</source>
|
||||
<translation>%1 an audio clip</translation>
|
||||
<location filename="../../src/Utils.h" line="+95"/>
|
||||
<source>You sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 an image</source>
|
||||
<translation>%1 an image</translation>
|
||||
<source>%1 sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a file</source>
|
||||
<translation>%1 a file</translation>
|
||||
<source>%1 sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a video clip</source>
|
||||
<translation>%1 a video clip</translation>
|
||||
<source>%1 sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a sticker</source>
|
||||
<translation>%1 a sticker</translation>
|
||||
<source>%1 sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a notification</source>
|
||||
<translation>%1 a notification</translation>
|
||||
<source>%1 sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1: %2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+7"/>
|
||||
<source>%1 an encrypted message</source>
|
||||
<translation>%1 an encrypted message</translation>
|
||||
<source>You sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description:</name>
|
||||
<name>popups::UserMentions</name>
|
||||
<message>
|
||||
<location line="-26"/>
|
||||
<source>sent</source>
|
||||
<comment>For when someone else is the sender</comment>
|
||||
<translation>sent</translation>
|
||||
<location filename="../../src/popups/UserMentions.cpp" line="+61"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished">This Room</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description: </name>
|
||||
<message>
|
||||
<location line="-2"/>
|
||||
<source>sent</source>
|
||||
<comment>For when you are the sender</comment>
|
||||
<translation>sent</translation>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished">All Rooms</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>utils</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.cpp" line="+46"/>
|
||||
<location filename="../../src/Utils.h" line="+55"/>
|
||||
<source>You</source>
|
||||
<translation>You</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+219"/>
|
||||
<location filename="../../src/Utils.cpp" line="+282"/>
|
||||
<source>sent a file.</source>
|
||||
<translation>sent a file.</translation>
|
||||
</message>
|
||||
|
@ -1148,7 +1225,7 @@ Media size: %2
|
|||
<translation>sent a video.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="-23"/>
|
||||
<location filename="../../src/Utils.h" line="+4"/>
|
||||
<source>Unknown Message Type</source>
|
||||
<translation>Unknown Message Type</translation>
|
||||
</message>
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="fi">
|
||||
<context>
|
||||
<name>AudioItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/AudioItem.cpp" line="+118"/>
|
||||
<source>Save File</source>
|
||||
<translation>Tallenna tiedosto</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
|
@ -32,7 +24,7 @@
|
|||
<translation>Videon lähettäminen epäonnistui. Ole hyvä ja yritä uudelleen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+380"/>
|
||||
<location line="+393"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation>OLM-tilin palauttaminen epäonnistui. Ole hyvä ja kirjaudu sisään uudelleen.</translation>
|
||||
</message>
|
||||
|
@ -42,18 +34,18 @@
|
|||
<translation>Tallennettujen tietojen palauttaminen epäonnistui. Ole hyvä ja kirjaudu sisään uudelleen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+198"/>
|
||||
<location line="+181"/>
|
||||
<source>Failed to setup encryption keys. Server response: %1 %2. Please try again later.</source>
|
||||
<translation>Salausavainten lähetys epäonnistui. Palvelimen vastaus: %1 %2. Ole hyvä ja yritä uudelleen myöhemmin.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+51"/>
|
||||
<location line="+153"/>
|
||||
<location line="+155"/>
|
||||
<source>Please try to login again: %1</source>
|
||||
<translation>Ole hyvä ja yritä kirjautua sisään uudelleen: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-45"/>
|
||||
<location line="-47"/>
|
||||
<source>Room creation failed: %1</source>
|
||||
<translation>Huoneen luominen epäonnistui: %1</translation>
|
||||
</message>
|
||||
|
@ -116,19 +108,11 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FileItem</name>
|
||||
<name>EncryptionIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/FileItem.cpp" line="+107"/>
|
||||
<source>Save File</source>
|
||||
<translation>Tallenna tiedosto</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ImageItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/ImageItem.cpp" line="+241"/>
|
||||
<source>Save image</source>
|
||||
<translation>Tallenna kuva</translation>
|
||||
<location filename="../qml/EncryptionIndicator.qml" line="+11"/>
|
||||
<source>Encrypted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -200,7 +184,7 @@
|
|||
<context>
|
||||
<name>MemberList</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+96"/>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+89"/>
|
||||
<source>Room members</source>
|
||||
<translation>Huoneen jäsenet</translation>
|
||||
</message>
|
||||
|
@ -210,6 +194,14 @@
|
|||
<translation>OK</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Placeholder.qml" line="+4"/>
|
||||
<source>unimplemented event: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QuickSwitcher</name>
|
||||
<message>
|
||||
|
@ -218,6 +210,14 @@
|
|||
<translation>Etsi huonetta…</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -277,7 +277,7 @@
|
|||
<context>
|
||||
<name>RoomInfo</name>
|
||||
<message>
|
||||
<location filename="../../src/Cache.cpp" line="+2205"/>
|
||||
<location filename="../../src/Cache.cpp" line="+2307"/>
|
||||
<source>no version stored</source>
|
||||
<translation>ei tallennettua versiota</translation>
|
||||
</message>
|
||||
|
@ -285,12 +285,12 @@
|
|||
<context>
|
||||
<name>RoomInfoListItem</name>
|
||||
<message>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+93"/>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+95"/>
|
||||
<source>Leave room</source>
|
||||
<translation>Poistu huoneesta</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+181"/>
|
||||
<location line="+161"/>
|
||||
<source>Accept</source>
|
||||
<translation>Hyväksy</translation>
|
||||
</message>
|
||||
|
@ -331,24 +331,24 @@
|
|||
<context>
|
||||
<name>StatusIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+107"/>
|
||||
<source>Encrypted</source>
|
||||
<translation>Salattu</translation>
|
||||
<location filename="../qml/StatusIndicator.qml" line="+13"/>
|
||||
<source>Failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Delivered</source>
|
||||
<translation>Toimitettu</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Seen</source>
|
||||
<translation>Luettu</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<location line="+1"/>
|
||||
<source>Sent</source>
|
||||
<translation>Lähetetty</translation>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Received</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -391,65 +391,116 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineItem</name>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+85"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation>Viestin poisto epäonnistui: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+39"/>
|
||||
<source>Reply</source>
|
||||
<translation>Vastaa</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+11"/>
|
||||
<source>Options</source>
|
||||
<translation>Asetukset</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineView.cpp" line="+245"/>
|
||||
<source>Encryption is enabled</source>
|
||||
<translation>Salaus on käytössä</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+65"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation>-- Salattu viesti (salauksen purkuavaimia ei löydetty) --</translation>
|
||||
<translation type="unfinished">-- Salattu viesti (salauksen purkuavaimia ei löydetty) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+15"/>
|
||||
<source>-- Decryption Error (failed to communicate with DB) --</source>
|
||||
<comment>Placeholder, when the message can't be decrypted, because the DB access failed when trying to lookup the session.</comment>
|
||||
<translation>-- Virhe purkaessa salausta (tietokannan kanssa kommunikointi epäonnistui) --</translation>
|
||||
<translation type="unfinished">-- Virhe purkaessa salausta (tietokannan kanssa kommunikointi epäonnistui) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+19"/>
|
||||
<source>-- Decryption Error (failed to retrieve megolm keys from db) --</source>
|
||||
<comment>Placeholder, when the message can't be decrypted, because the DB access failed.</comment>
|
||||
<translation>-- Virhe purkaessa salausta (megolm-avaimien hakeminen tietokannasta epäonnistui) --</translation>
|
||||
<translation type="unfinished">-- Virhe purkaessa salausta (megolm-avaimien hakeminen tietokannasta epäonnistui) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+12"/>
|
||||
<source>-- Decryption Error (%1) --</source>
|
||||
<comment>Placeholder, when the message can't be decrypted. In this case, the Olm decrytion returned an error, which is passed ad %1</comment>
|
||||
<translation>-- Virhe purkaessa salausta (%1) --</translation>
|
||||
<translation type="unfinished">-- Virhe purkaessa salausta (%1) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+27"/>
|
||||
<location line="+25"/>
|
||||
<source>-- Encrypted Event (Unknown event type) --</source>
|
||||
<comment>Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet</comment>
|
||||
<translation>-- Salattu viesti (tuntematon viestityyppi) --</translation>
|
||||
<translation type="unfinished">-- Salattu viesti (tuntematon viestityyppi) --</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished">Viestin poisto epäonnistui: %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineRow.qml" line="+57"/>
|
||||
<source>Reply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+14"/>
|
||||
<source>Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+12"/>
|
||||
<source>Read receipts</source>
|
||||
<translation type="unfinished">Lukukuittaukset</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Mark as read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>View raw message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Redact message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>Save as</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineView.qml" line="+24"/>
|
||||
<source>No room open</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Tallenna kuva</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+79"/>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+78"/>
|
||||
<source>Room options</source>
|
||||
<translation>Huonevaihtoehdot</translation>
|
||||
</message>
|
||||
|
@ -515,7 +566,7 @@
|
|||
<context>
|
||||
<name>UserSettingsPage</name>
|
||||
<message>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+166"/>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+171"/>
|
||||
<source>Minimize to tray</source>
|
||||
<translation>Pienennä ilmoitusalueelle</translation>
|
||||
</message>
|
||||
|
@ -529,6 +580,11 @@
|
|||
<source>Group's sidebar</source>
|
||||
<translation>Ryhmäsivupalkki</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Circular Avatars</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Typing notifications</source>
|
||||
|
@ -605,7 +661,7 @@
|
|||
<translation>YLEISET ASETUKSET</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+156"/>
|
||||
<location line="+161"/>
|
||||
<source>Open Sessions File</source>
|
||||
<translation>Avaa Istuntoavaintiedosto</translation>
|
||||
</message>
|
||||
|
@ -825,7 +881,7 @@ Median koko: %2
|
|||
<context>
|
||||
<name>dialogs::ReadReceipts</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+121"/>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+117"/>
|
||||
<source>Read receipts</source>
|
||||
<translation>Lukukuittaukset</translation>
|
||||
</message>
|
||||
|
@ -953,7 +1009,7 @@ Median koko: %2
|
|||
<translation>Salauksen aktivointi epäonnistui: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+149"/>
|
||||
<location line="+148"/>
|
||||
<source>Select an avatar</source>
|
||||
<translation>Valitse profiilikuva</translation>
|
||||
</message>
|
||||
|
@ -979,19 +1035,6 @@ Median koko: %2
|
|||
<translation>Kuvan lähetys epäonnistui: %s</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserMentions</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/UserMentions.cpp" line="+53"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserProfile</name>
|
||||
<message>
|
||||
|
@ -1015,7 +1058,7 @@ Median koko: %2
|
|||
<translation>Aloita keskustelu</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+57"/>
|
||||
<location line="+56"/>
|
||||
<source>Devices</source>
|
||||
<translation>Laitteet</translation>
|
||||
</message>
|
||||
|
@ -1066,69 +1109,103 @@ Median koko: %2
|
|||
<context>
|
||||
<name>message-description sent:</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="+104"/>
|
||||
<source>%1 an audio clip</source>
|
||||
<location filename="../../src/Utils.h" line="+95"/>
|
||||
<source>You sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 an image</source>
|
||||
<source>%1 sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a file</source>
|
||||
<source>%1 sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a video clip</source>
|
||||
<source>%1 sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a sticker</source>
|
||||
<source>%1 sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a notification</source>
|
||||
<source>%1 sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1: %2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+7"/>
|
||||
<source>%1 an encrypted message</source>
|
||||
<source>You sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description:</name>
|
||||
<name>popups::UserMentions</name>
|
||||
<message>
|
||||
<location line="-26"/>
|
||||
<source>sent</source>
|
||||
<comment>For when someone else is the sender</comment>
|
||||
<location filename="../../src/popups/UserMentions.cpp" line="+61"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description: </name>
|
||||
<message>
|
||||
<location line="-2"/>
|
||||
<source>sent</source>
|
||||
<comment>For when you are the sender</comment>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>utils</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.cpp" line="+46"/>
|
||||
<location filename="../../src/Utils.h" line="+55"/>
|
||||
<source>You</source>
|
||||
<translation>Sinä</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+219"/>
|
||||
<location filename="../../src/Utils.cpp" line="+282"/>
|
||||
<source>sent a file.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1148,7 +1225,7 @@ Median koko: %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="-23"/>
|
||||
<location filename="../../src/Utils.h" line="+4"/>
|
||||
<source>Unknown Message Type</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="fr">
|
||||
<context>
|
||||
<name>AudioItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/AudioItem.cpp" line="+118"/>
|
||||
<source>Save File</source>
|
||||
<translation>Enregistrer le fichier</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
|
@ -32,7 +24,7 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+380"/>
|
||||
<location line="+393"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -42,18 +34,18 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+198"/>
|
||||
<location line="+181"/>
|
||||
<source>Failed to setup encryption keys. Server response: %1 %2. Please try again later.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+51"/>
|
||||
<location line="+153"/>
|
||||
<location line="+155"/>
|
||||
<source>Please try to login again: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-45"/>
|
||||
<location line="-47"/>
|
||||
<source>Room creation failed: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -116,19 +108,11 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FileItem</name>
|
||||
<name>EncryptionIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/FileItem.cpp" line="+107"/>
|
||||
<source>Save File</source>
|
||||
<translation>Enregistrer le fichier</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ImageItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/ImageItem.cpp" line="+241"/>
|
||||
<source>Save image</source>
|
||||
<translation>Enregistrer l'image</translation>
|
||||
<location filename="../qml/EncryptionIndicator.qml" line="+11"/>
|
||||
<source>Encrypted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -200,7 +184,7 @@
|
|||
<context>
|
||||
<name>MemberList</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+96"/>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+89"/>
|
||||
<source>Room members</source>
|
||||
<translation>Membres du salon</translation>
|
||||
</message>
|
||||
|
@ -210,6 +194,14 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Placeholder.qml" line="+4"/>
|
||||
<source>unimplemented event: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QuickSwitcher</name>
|
||||
<message>
|
||||
|
@ -218,6 +210,14 @@
|
|||
<translation>Chercher un salon…</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -278,7 +278,7 @@
|
|||
<context>
|
||||
<name>RoomInfo</name>
|
||||
<message>
|
||||
<location filename="../../src/Cache.cpp" line="+2205"/>
|
||||
<location filename="../../src/Cache.cpp" line="+2307"/>
|
||||
<source>no version stored</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -286,12 +286,12 @@
|
|||
<context>
|
||||
<name>RoomInfoListItem</name>
|
||||
<message>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+93"/>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+95"/>
|
||||
<source>Leave room</source>
|
||||
<translation>Quitter le salon</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+181"/>
|
||||
<location line="+161"/>
|
||||
<source>Accept</source>
|
||||
<translation>Accepter</translation>
|
||||
</message>
|
||||
|
@ -332,25 +332,25 @@
|
|||
<context>
|
||||
<name>StatusIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+107"/>
|
||||
<source>Encrypted</source>
|
||||
<location filename="../qml/StatusIndicator.qml" line="+13"/>
|
||||
<source>Failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Delivered</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Seen</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<location line="+1"/>
|
||||
<source>Sent</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Received</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TextInputWidget</name>
|
||||
|
@ -392,32 +392,9 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineItem</name>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+85"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+39"/>
|
||||
<source>Reply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+11"/>
|
||||
<source>Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineView.cpp" line="+245"/>
|
||||
<source>Encryption is enabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+65"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -441,16 +418,90 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+27"/>
|
||||
<location line="+25"/>
|
||||
<source>-- Encrypted Event (Unknown event type) --</source>
|
||||
<comment>Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineRow.qml" line="+57"/>
|
||||
<source>Reply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+14"/>
|
||||
<source>Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+12"/>
|
||||
<source>Read receipts</source>
|
||||
<translation type="unfinished">Accusés de lecture</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Mark as read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>View raw message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Redact message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>Save as</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineView.qml" line="+24"/>
|
||||
<source>No room open</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Enregistrer l'image</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+79"/>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+78"/>
|
||||
<source>Room options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -516,7 +567,7 @@
|
|||
<context>
|
||||
<name>UserSettingsPage</name>
|
||||
<message>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+166"/>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+171"/>
|
||||
<source>Minimize to tray</source>
|
||||
<translation>Réduire à la barre des tâches</translation>
|
||||
</message>
|
||||
|
@ -530,6 +581,11 @@
|
|||
<source>Group's sidebar</source>
|
||||
<translation>Barre latérale des groupes</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Circular Avatars</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Typing notifications</source>
|
||||
|
@ -606,7 +662,7 @@
|
|||
<translation>GÉNÉRAL</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+156"/>
|
||||
<location line="+161"/>
|
||||
<source>Open Sessions File</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -826,7 +882,7 @@ Taille du média : %2
|
|||
<context>
|
||||
<name>dialogs::ReadReceipts</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+121"/>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+117"/>
|
||||
<source>Read receipts</source>
|
||||
<translation>Accusés de lecture</translation>
|
||||
</message>
|
||||
|
@ -952,7 +1008,7 @@ Taille du média : %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+149"/>
|
||||
<location line="+148"/>
|
||||
<source>Select an avatar</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -978,19 +1034,6 @@ Taille du média : %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserMentions</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/UserMentions.cpp" line="+53"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserProfile</name>
|
||||
<message>
|
||||
|
@ -1014,7 +1057,7 @@ Taille du média : %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+57"/>
|
||||
<location line="+56"/>
|
||||
<source>Devices</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1065,69 +1108,103 @@ Taille du média : %2
|
|||
<context>
|
||||
<name>message-description sent:</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="+104"/>
|
||||
<source>%1 an audio clip</source>
|
||||
<location filename="../../src/Utils.h" line="+95"/>
|
||||
<source>You sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 an image</source>
|
||||
<source>%1 sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a file</source>
|
||||
<source>%1 sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a video clip</source>
|
||||
<source>%1 sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a sticker</source>
|
||||
<source>%1 sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a notification</source>
|
||||
<source>%1 sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1: %2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+7"/>
|
||||
<source>%1 an encrypted message</source>
|
||||
<source>You sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description:</name>
|
||||
<name>popups::UserMentions</name>
|
||||
<message>
|
||||
<location line="-26"/>
|
||||
<source>sent</source>
|
||||
<comment>For when someone else is the sender</comment>
|
||||
<location filename="../../src/popups/UserMentions.cpp" line="+61"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description: </name>
|
||||
<message>
|
||||
<location line="-2"/>
|
||||
<source>sent</source>
|
||||
<comment>For when you are the sender</comment>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>utils</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.cpp" line="+46"/>
|
||||
<location filename="../../src/Utils.h" line="+55"/>
|
||||
<source>You</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+219"/>
|
||||
<location filename="../../src/Utils.cpp" line="+282"/>
|
||||
<source>sent a file.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1147,7 +1224,7 @@ Taille du média : %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="-23"/>
|
||||
<location filename="../../src/Utils.h" line="+4"/>
|
||||
<source>Unknown Message Type</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="nl_NL">
|
||||
<context>
|
||||
<name>AudioItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/AudioItem.cpp" line="+118"/>
|
||||
<source>Save File</source>
|
||||
<translation>Bestand opslaan</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
|
@ -32,7 +24,7 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+380"/>
|
||||
<location line="+393"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -42,18 +34,18 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+198"/>
|
||||
<location line="+181"/>
|
||||
<source>Failed to setup encryption keys. Server response: %1 %2. Please try again later.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+51"/>
|
||||
<location line="+153"/>
|
||||
<location line="+155"/>
|
||||
<source>Please try to login again: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-45"/>
|
||||
<location line="-47"/>
|
||||
<source>Room creation failed: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -116,19 +108,11 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FileItem</name>
|
||||
<name>EncryptionIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/FileItem.cpp" line="+107"/>
|
||||
<source>Save File</source>
|
||||
<translation>Bestand opslaan</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ImageItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/ImageItem.cpp" line="+241"/>
|
||||
<source>Save image</source>
|
||||
<translation>Afbeelding opslaan</translation>
|
||||
<location filename="../qml/EncryptionIndicator.qml" line="+11"/>
|
||||
<source>Encrypted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -200,7 +184,7 @@
|
|||
<context>
|
||||
<name>MemberList</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+96"/>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+89"/>
|
||||
<source>Room members</source>
|
||||
<translation>Kamerleden</translation>
|
||||
</message>
|
||||
|
@ -210,6 +194,14 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Placeholder.qml" line="+4"/>
|
||||
<source>unimplemented event: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QuickSwitcher</name>
|
||||
<message>
|
||||
|
@ -218,6 +210,14 @@
|
|||
<translation>Zoek een kamer...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -277,7 +277,7 @@
|
|||
<context>
|
||||
<name>RoomInfo</name>
|
||||
<message>
|
||||
<location filename="../../src/Cache.cpp" line="+2205"/>
|
||||
<location filename="../../src/Cache.cpp" line="+2307"/>
|
||||
<source>no version stored</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -285,12 +285,12 @@
|
|||
<context>
|
||||
<name>RoomInfoListItem</name>
|
||||
<message>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+93"/>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+95"/>
|
||||
<source>Leave room</source>
|
||||
<translation>Kamer verlaten</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+181"/>
|
||||
<location line="+161"/>
|
||||
<source>Accept</source>
|
||||
<translation>Accepteren</translation>
|
||||
</message>
|
||||
|
@ -331,25 +331,25 @@
|
|||
<context>
|
||||
<name>StatusIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+107"/>
|
||||
<source>Encrypted</source>
|
||||
<location filename="../qml/StatusIndicator.qml" line="+13"/>
|
||||
<source>Failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Delivered</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Seen</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<location line="+1"/>
|
||||
<source>Sent</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Received</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TextInputWidget</name>
|
||||
|
@ -391,32 +391,9 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineItem</name>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+85"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+39"/>
|
||||
<source>Reply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+11"/>
|
||||
<source>Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineView.cpp" line="+245"/>
|
||||
<source>Encryption is enabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+65"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -440,16 +417,90 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+27"/>
|
||||
<location line="+25"/>
|
||||
<source>-- Encrypted Event (Unknown event type) --</source>
|
||||
<comment>Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineRow.qml" line="+57"/>
|
||||
<source>Reply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+14"/>
|
||||
<source>Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+12"/>
|
||||
<source>Read receipts</source>
|
||||
<translation type="unfinished">Leesbevestigingen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Mark as read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>View raw message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Redact message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>Save as</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineView.qml" line="+24"/>
|
||||
<source>No room open</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Afbeelding opslaan</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+79"/>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+78"/>
|
||||
<source>Room options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -515,7 +566,7 @@
|
|||
<context>
|
||||
<name>UserSettingsPage</name>
|
||||
<message>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+166"/>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+171"/>
|
||||
<source>Minimize to tray</source>
|
||||
<translation>Minimaliseren naar systeemvak</translation>
|
||||
</message>
|
||||
|
@ -529,6 +580,11 @@
|
|||
<source>Group's sidebar</source>
|
||||
<translation>Zijbalk van groep</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Circular Avatars</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Typing notifications</source>
|
||||
|
@ -605,7 +661,7 @@
|
|||
<translation>ALGEMEEN</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+156"/>
|
||||
<location line="+161"/>
|
||||
<source>Open Sessions File</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -825,7 +881,7 @@ Mediagrootte: %2
|
|||
<context>
|
||||
<name>dialogs::ReadReceipts</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+121"/>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+117"/>
|
||||
<source>Read receipts</source>
|
||||
<translation>Leesbevestigingen</translation>
|
||||
</message>
|
||||
|
@ -951,7 +1007,7 @@ Mediagrootte: %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+149"/>
|
||||
<location line="+148"/>
|
||||
<source>Select an avatar</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -977,19 +1033,6 @@ Mediagrootte: %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserMentions</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/UserMentions.cpp" line="+53"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserProfile</name>
|
||||
<message>
|
||||
|
@ -1013,7 +1056,7 @@ Mediagrootte: %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+57"/>
|
||||
<location line="+56"/>
|
||||
<source>Devices</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1064,69 +1107,103 @@ Mediagrootte: %2
|
|||
<context>
|
||||
<name>message-description sent:</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="+104"/>
|
||||
<source>%1 an audio clip</source>
|
||||
<location filename="../../src/Utils.h" line="+95"/>
|
||||
<source>You sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 an image</source>
|
||||
<source>%1 sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a file</source>
|
||||
<source>%1 sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a video clip</source>
|
||||
<source>%1 sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a sticker</source>
|
||||
<source>%1 sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a notification</source>
|
||||
<source>%1 sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1: %2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+7"/>
|
||||
<source>%1 an encrypted message</source>
|
||||
<source>You sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description:</name>
|
||||
<name>popups::UserMentions</name>
|
||||
<message>
|
||||
<location line="-26"/>
|
||||
<source>sent</source>
|
||||
<comment>For when someone else is the sender</comment>
|
||||
<location filename="../../src/popups/UserMentions.cpp" line="+61"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description: </name>
|
||||
<message>
|
||||
<location line="-2"/>
|
||||
<source>sent</source>
|
||||
<comment>For when you are the sender</comment>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>utils</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.cpp" line="+46"/>
|
||||
<location filename="../../src/Utils.h" line="+55"/>
|
||||
<source>You</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+219"/>
|
||||
<location filename="../../src/Utils.cpp" line="+282"/>
|
||||
<source>sent a file.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1146,7 +1223,7 @@ Mediagrootte: %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="-23"/>
|
||||
<location filename="../../src/Utils.h" line="+4"/>
|
||||
<source>Unknown Message Type</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="pl">
|
||||
<context>
|
||||
<name>AudioItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/AudioItem.cpp" line="+118"/>
|
||||
<source>Save File</source>
|
||||
<translation>Zapisz plik</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
|
@ -32,7 +24,7 @@
|
|||
<translation>Nie udało się wysłać filmu. Spróbuj ponownie.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+380"/>
|
||||
<location line="+393"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation>Nie udało się przywrócić konta OLM. Spróbuj zalogować się ponownie.</translation>
|
||||
</message>
|
||||
|
@ -42,18 +34,18 @@
|
|||
<translation>Nie udało się przywrócić zapisanych danych. Spróbuj zalogować się ponownie.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+198"/>
|
||||
<location line="+181"/>
|
||||
<source>Failed to setup encryption keys. Server response: %1 %2. Please try again later.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+51"/>
|
||||
<location line="+153"/>
|
||||
<location line="+155"/>
|
||||
<source>Please try to login again: %1</source>
|
||||
<translation>Spróbuj zalogować się ponownie: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-45"/>
|
||||
<location line="-47"/>
|
||||
<source>Room creation failed: %1</source>
|
||||
<translation>Tworzenie pokoju nie powiodło się: %1</translation>
|
||||
</message>
|
||||
|
@ -116,19 +108,11 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FileItem</name>
|
||||
<name>EncryptionIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/FileItem.cpp" line="+107"/>
|
||||
<source>Save File</source>
|
||||
<translation>Zapisz plik</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ImageItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/ImageItem.cpp" line="+241"/>
|
||||
<source>Save image</source>
|
||||
<translation>Zapisz obraz</translation>
|
||||
<location filename="../qml/EncryptionIndicator.qml" line="+11"/>
|
||||
<source>Encrypted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -200,7 +184,7 @@
|
|||
<context>
|
||||
<name>MemberList</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+96"/>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+89"/>
|
||||
<source>Room members</source>
|
||||
<translation>Członkowie pokoju</translation>
|
||||
</message>
|
||||
|
@ -210,6 +194,14 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Placeholder.qml" line="+4"/>
|
||||
<source>unimplemented event: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QuickSwitcher</name>
|
||||
<message>
|
||||
|
@ -218,6 +210,14 @@
|
|||
<translation>Wyszukaj pokoju…</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -277,7 +277,7 @@
|
|||
<context>
|
||||
<name>RoomInfo</name>
|
||||
<message>
|
||||
<location filename="../../src/Cache.cpp" line="+2205"/>
|
||||
<location filename="../../src/Cache.cpp" line="+2307"/>
|
||||
<source>no version stored</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -285,12 +285,12 @@
|
|||
<context>
|
||||
<name>RoomInfoListItem</name>
|
||||
<message>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+93"/>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+95"/>
|
||||
<source>Leave room</source>
|
||||
<translation>Opuść pokój</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+181"/>
|
||||
<location line="+161"/>
|
||||
<source>Accept</source>
|
||||
<translation>Akceptuj</translation>
|
||||
</message>
|
||||
|
@ -331,24 +331,24 @@
|
|||
<context>
|
||||
<name>StatusIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+107"/>
|
||||
<source>Encrypted</source>
|
||||
<translation>Szyfrowana</translation>
|
||||
<location filename="../qml/StatusIndicator.qml" line="+13"/>
|
||||
<source>Failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Delivered</source>
|
||||
<translation>Dostarczono</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Seen</source>
|
||||
<translation>Wyświetlona</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<location line="+1"/>
|
||||
<source>Sent</source>
|
||||
<translation>Wysłana</translation>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Received</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -391,32 +391,9 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineItem</name>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+85"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation>Redagowanie wiadomości nie powiodło się: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+39"/>
|
||||
<source>Reply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+11"/>
|
||||
<source>Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineView.cpp" line="+245"/>
|
||||
<source>Encryption is enabled</source>
|
||||
<translation>Szyfrowanie jest włączone</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+65"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -440,16 +417,90 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+27"/>
|
||||
<location line="+25"/>
|
||||
<source>-- Encrypted Event (Unknown event type) --</source>
|
||||
<comment>Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished">Redagowanie wiadomości nie powiodło się: %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineRow.qml" line="+57"/>
|
||||
<source>Reply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+14"/>
|
||||
<source>Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+12"/>
|
||||
<source>Read receipts</source>
|
||||
<translation type="unfinished">Potwierdzenia przeczytania</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Mark as read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>View raw message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Redact message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>Save as</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineView.qml" line="+24"/>
|
||||
<source>No room open</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Zapisz obraz</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+79"/>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+78"/>
|
||||
<source>Room options</source>
|
||||
<translation>Ustawienia pokoju</translation>
|
||||
</message>
|
||||
|
@ -516,7 +567,7 @@
|
|||
<context>
|
||||
<name>UserSettingsPage</name>
|
||||
<message>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+166"/>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+171"/>
|
||||
<source>Minimize to tray</source>
|
||||
<translation>Zminimalizuj do paska zadań</translation>
|
||||
</message>
|
||||
|
@ -530,6 +581,11 @@
|
|||
<source>Group's sidebar</source>
|
||||
<translation>Pasek boczny grupy</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Circular Avatars</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Typing notifications</source>
|
||||
|
@ -606,7 +662,7 @@
|
|||
<translation>OGÓLNE</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+156"/>
|
||||
<location line="+161"/>
|
||||
<source>Open Sessions File</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -826,7 +882,7 @@ Rozmiar multimediów: %2
|
|||
<context>
|
||||
<name>dialogs::ReadReceipts</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+121"/>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+117"/>
|
||||
<source>Read receipts</source>
|
||||
<translation>Potwierdzenia przeczytania</translation>
|
||||
</message>
|
||||
|
@ -955,7 +1011,7 @@ Rozmiar multimediów: %2
|
|||
<translation>Nie udało się włączyć szyfrowania: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+149"/>
|
||||
<location line="+148"/>
|
||||
<source>Select an avatar</source>
|
||||
<translation>Wybierz awatar</translation>
|
||||
</message>
|
||||
|
@ -981,19 +1037,6 @@ Rozmiar multimediów: %2
|
|||
<translation>Nie udało się wysłać obrazu: %s</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserMentions</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/UserMentions.cpp" line="+53"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserProfile</name>
|
||||
<message>
|
||||
|
@ -1017,7 +1060,7 @@ Rozmiar multimediów: %2
|
|||
<translation>Rozpocznij rozmowę</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+57"/>
|
||||
<location line="+56"/>
|
||||
<source>Devices</source>
|
||||
<translation>Urządzenia</translation>
|
||||
</message>
|
||||
|
@ -1068,69 +1111,103 @@ Rozmiar multimediów: %2
|
|||
<context>
|
||||
<name>message-description sent:</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="+104"/>
|
||||
<source>%1 an audio clip</source>
|
||||
<location filename="../../src/Utils.h" line="+95"/>
|
||||
<source>You sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 an image</source>
|
||||
<source>%1 sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a file</source>
|
||||
<source>%1 sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a video clip</source>
|
||||
<source>%1 sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a sticker</source>
|
||||
<source>%1 sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a notification</source>
|
||||
<source>%1 sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1: %2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+7"/>
|
||||
<source>%1 an encrypted message</source>
|
||||
<source>You sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description:</name>
|
||||
<name>popups::UserMentions</name>
|
||||
<message>
|
||||
<location line="-26"/>
|
||||
<source>sent</source>
|
||||
<comment>For when someone else is the sender</comment>
|
||||
<location filename="../../src/popups/UserMentions.cpp" line="+61"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description: </name>
|
||||
<message>
|
||||
<location line="-2"/>
|
||||
<source>sent</source>
|
||||
<comment>For when you are the sender</comment>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>utils</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.cpp" line="+46"/>
|
||||
<location filename="../../src/Utils.h" line="+55"/>
|
||||
<source>You</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+219"/>
|
||||
<location filename="../../src/Utils.cpp" line="+282"/>
|
||||
<source>sent a file.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1150,7 +1227,7 @@ Rozmiar multimediów: %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="-23"/>
|
||||
<location filename="../../src/Utils.h" line="+4"/>
|
||||
<source>Unknown Message Type</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="ru">
|
||||
<context>
|
||||
<name>AudioItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/AudioItem.cpp" line="+118"/>
|
||||
<source>Save File</source>
|
||||
<translation>Сохранить файл</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
|
@ -32,7 +24,7 @@
|
|||
<translation>Не удалось загрузить видео. Пожалуйста, попробуйте еще раз.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+380"/>
|
||||
<location line="+393"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation>Не удалось восстановить учетную запись OLM. Пожалуйста, войдите снова.</translation>
|
||||
</message>
|
||||
|
@ -42,18 +34,18 @@
|
|||
<translation>Не удалось восстановить сохраненные данные. Пожалуйста, войдите снова.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+198"/>
|
||||
<location line="+181"/>
|
||||
<source>Failed to setup encryption keys. Server response: %1 %2. Please try again later.</source>
|
||||
<translation>Не удалось настроить ключи шифрования. Ответ сервера:%1 %2. Пожалуйста, попробуйте позже.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+51"/>
|
||||
<location line="+153"/>
|
||||
<location line="+155"/>
|
||||
<source>Please try to login again: %1</source>
|
||||
<translation>Повторите попытку входа: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-45"/>
|
||||
<location line="-47"/>
|
||||
<source>Room creation failed: %1</source>
|
||||
<translation>Не удалось создать комнату: %1</translation>
|
||||
</message>
|
||||
|
@ -116,19 +108,11 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FileItem</name>
|
||||
<name>EncryptionIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/FileItem.cpp" line="+107"/>
|
||||
<source>Save File</source>
|
||||
<translation>Сохранить файл</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ImageItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/ImageItem.cpp" line="+241"/>
|
||||
<source>Save image</source>
|
||||
<translation>Сохранить изображение</translation>
|
||||
<location filename="../qml/EncryptionIndicator.qml" line="+11"/>
|
||||
<source>Encrypted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -200,7 +184,7 @@
|
|||
<context>
|
||||
<name>MemberList</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+96"/>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+89"/>
|
||||
<source>Room members</source>
|
||||
<translation>Участники комнаты</translation>
|
||||
</message>
|
||||
|
@ -210,6 +194,14 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Placeholder.qml" line="+4"/>
|
||||
<source>unimplemented event: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QuickSwitcher</name>
|
||||
<message>
|
||||
|
@ -218,6 +210,14 @@
|
|||
<translation>Поиск комнаты...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -277,7 +277,7 @@
|
|||
<context>
|
||||
<name>RoomInfo</name>
|
||||
<message>
|
||||
<location filename="../../src/Cache.cpp" line="+2205"/>
|
||||
<location filename="../../src/Cache.cpp" line="+2307"/>
|
||||
<source>no version stored</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -285,12 +285,12 @@
|
|||
<context>
|
||||
<name>RoomInfoListItem</name>
|
||||
<message>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+93"/>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+95"/>
|
||||
<source>Leave room</source>
|
||||
<translation>Покинуть комнату</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+181"/>
|
||||
<location line="+161"/>
|
||||
<source>Accept</source>
|
||||
<translation>Принять</translation>
|
||||
</message>
|
||||
|
@ -331,24 +331,24 @@
|
|||
<context>
|
||||
<name>StatusIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+107"/>
|
||||
<source>Encrypted</source>
|
||||
<translation>Зашифровано</translation>
|
||||
<location filename="../qml/StatusIndicator.qml" line="+13"/>
|
||||
<source>Failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Delivered</source>
|
||||
<translation>Доставлено</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Seen</source>
|
||||
<translation>Прочитано</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<location line="+1"/>
|
||||
<source>Sent</source>
|
||||
<translation>Отправлено</translation>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Received</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -391,32 +391,9 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineItem</name>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+85"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation>Ошибка редактирования сообщения: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+39"/>
|
||||
<source>Reply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+11"/>
|
||||
<source>Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineView.cpp" line="+245"/>
|
||||
<source>Encryption is enabled</source>
|
||||
<translation>Шифрование включено</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+65"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -440,16 +417,90 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+27"/>
|
||||
<location line="+25"/>
|
||||
<source>-- Encrypted Event (Unknown event type) --</source>
|
||||
<comment>Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished">Ошибка редактирования сообщения: %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineRow.qml" line="+57"/>
|
||||
<source>Reply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+14"/>
|
||||
<source>Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+12"/>
|
||||
<source>Read receipts</source>
|
||||
<translation type="unfinished">Подтверждать прочтение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Mark as read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>View raw message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Redact message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>Save as</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineView.qml" line="+24"/>
|
||||
<source>No room open</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">Сохранить изображение</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+79"/>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+78"/>
|
||||
<source>Room options</source>
|
||||
<translation>Настройки комнаты</translation>
|
||||
</message>
|
||||
|
@ -516,7 +567,7 @@
|
|||
<context>
|
||||
<name>UserSettingsPage</name>
|
||||
<message>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+166"/>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+171"/>
|
||||
<source>Minimize to tray</source>
|
||||
<translation>Сворачивать в системную панель</translation>
|
||||
</message>
|
||||
|
@ -530,6 +581,11 @@
|
|||
<source>Group's sidebar</source>
|
||||
<translation>Боковая панель групп</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Circular Avatars</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Typing notifications</source>
|
||||
|
@ -606,7 +662,7 @@
|
|||
<translation>ГЛАВНОЕ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+156"/>
|
||||
<location line="+161"/>
|
||||
<source>Open Sessions File</source>
|
||||
<translation>Открыть файл сеансов</translation>
|
||||
</message>
|
||||
|
@ -827,7 +883,7 @@ Media size: %2
|
|||
<context>
|
||||
<name>dialogs::ReadReceipts</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+121"/>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+117"/>
|
||||
<source>Read receipts</source>
|
||||
<translation>Подтверждать прочтение</translation>
|
||||
</message>
|
||||
|
@ -954,7 +1010,7 @@ Media size: %2
|
|||
<translation>Не удалось включить шифрование: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+149"/>
|
||||
<location line="+148"/>
|
||||
<source>Select an avatar</source>
|
||||
<translation>Выберите аватар</translation>
|
||||
</message>
|
||||
|
@ -980,19 +1036,6 @@ Media size: %2
|
|||
<translation>Не удалось загрузить изображение: %s</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserMentions</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/UserMentions.cpp" line="+53"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserProfile</name>
|
||||
<message>
|
||||
|
@ -1016,7 +1059,7 @@ Media size: %2
|
|||
<translation>Начать разговор</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+57"/>
|
||||
<location line="+56"/>
|
||||
<source>Devices</source>
|
||||
<translation>Устройства</translation>
|
||||
</message>
|
||||
|
@ -1067,69 +1110,103 @@ Media size: %2
|
|||
<context>
|
||||
<name>message-description sent:</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="+104"/>
|
||||
<source>%1 an audio clip</source>
|
||||
<location filename="../../src/Utils.h" line="+95"/>
|
||||
<source>You sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 an image</source>
|
||||
<source>%1 sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a file</source>
|
||||
<source>%1 sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a video clip</source>
|
||||
<source>%1 sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a sticker</source>
|
||||
<source>%1 sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a notification</source>
|
||||
<source>%1 sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1: %2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+7"/>
|
||||
<source>%1 an encrypted message</source>
|
||||
<source>You sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description:</name>
|
||||
<name>popups::UserMentions</name>
|
||||
<message>
|
||||
<location line="-26"/>
|
||||
<source>sent</source>
|
||||
<comment>For when someone else is the sender</comment>
|
||||
<location filename="../../src/popups/UserMentions.cpp" line="+61"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description: </name>
|
||||
<message>
|
||||
<location line="-2"/>
|
||||
<source>sent</source>
|
||||
<comment>For when you are the sender</comment>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>utils</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.cpp" line="+46"/>
|
||||
<location filename="../../src/Utils.h" line="+55"/>
|
||||
<source>You</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+219"/>
|
||||
<location filename="../../src/Utils.cpp" line="+282"/>
|
||||
<source>sent a file.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1149,7 +1226,7 @@ Media size: %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="-23"/>
|
||||
<location filename="../../src/Utils.h" line="+4"/>
|
||||
<source>Unknown Message Type</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="zh_CN">
|
||||
<context>
|
||||
<name>AudioItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/AudioItem.cpp" line="+118"/>
|
||||
<source>Save File</source>
|
||||
<translation>保存文件</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatPage</name>
|
||||
<message>
|
||||
|
@ -32,7 +24,7 @@
|
|||
<translation>上传视频失败。请重试。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+380"/>
|
||||
<location line="+393"/>
|
||||
<source>Failed to restore OLM account. Please login again.</source>
|
||||
<translation>恢复 OLM 账户失败。请重新登录。</translation>
|
||||
</message>
|
||||
|
@ -42,18 +34,18 @@
|
|||
<translation>恢复保存的数据失败。请重新登录。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+198"/>
|
||||
<location line="+181"/>
|
||||
<source>Failed to setup encryption keys. Server response: %1 %2. Please try again later.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+51"/>
|
||||
<location line="+153"/>
|
||||
<location line="+155"/>
|
||||
<source>Please try to login again: %1</source>
|
||||
<translation>请尝试再次登录:%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="-45"/>
|
||||
<location line="-47"/>
|
||||
<source>Room creation failed: %1</source>
|
||||
<translation>创建聊天室失败:%1</translation>
|
||||
</message>
|
||||
|
@ -116,19 +108,11 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>FileItem</name>
|
||||
<name>EncryptionIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/FileItem.cpp" line="+107"/>
|
||||
<source>Save File</source>
|
||||
<translation>保存文件</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ImageItem</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/widgets/ImageItem.cpp" line="+241"/>
|
||||
<source>Save image</source>
|
||||
<translation>保存图像</translation>
|
||||
<location filename="../qml/EncryptionIndicator.qml" line="+11"/>
|
||||
<source>Encrypted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -200,7 +184,7 @@
|
|||
<context>
|
||||
<name>MemberList</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+96"/>
|
||||
<location filename="../../src/dialogs/MemberList.cpp" line="+89"/>
|
||||
<source>Room members</source>
|
||||
<translation>聊天室成员</translation>
|
||||
</message>
|
||||
|
@ -210,6 +194,14 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Placeholder</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Placeholder.qml" line="+4"/>
|
||||
<source>unimplemented event: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QuickSwitcher</name>
|
||||
<message>
|
||||
|
@ -218,6 +210,14 @@
|
|||
<translation>寻找一个聊天室...</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Redacted</name>
|
||||
<message>
|
||||
<location filename="../qml/delegates/Redacted.qml" line="+5"/>
|
||||
<source>redacted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>RegisterPage</name>
|
||||
<message>
|
||||
|
@ -277,7 +277,7 @@
|
|||
<context>
|
||||
<name>RoomInfo</name>
|
||||
<message>
|
||||
<location filename="../../src/Cache.cpp" line="+2205"/>
|
||||
<location filename="../../src/Cache.cpp" line="+2307"/>
|
||||
<source>no version stored</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -285,12 +285,12 @@
|
|||
<context>
|
||||
<name>RoomInfoListItem</name>
|
||||
<message>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+93"/>
|
||||
<location filename="../../src/RoomInfoListItem.cpp" line="+95"/>
|
||||
<source>Leave room</source>
|
||||
<translation>离开聊天室</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+181"/>
|
||||
<location line="+161"/>
|
||||
<source>Accept</source>
|
||||
<translation>接受</translation>
|
||||
</message>
|
||||
|
@ -331,24 +331,24 @@
|
|||
<context>
|
||||
<name>StatusIndicator</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+107"/>
|
||||
<source>Encrypted</source>
|
||||
<translation>加密的</translation>
|
||||
<location filename="../qml/StatusIndicator.qml" line="+13"/>
|
||||
<source>Failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Delivered</source>
|
||||
<translation>已送达</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>Seen</source>
|
||||
<translation>已阅读</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<location line="+1"/>
|
||||
<source>Sent</source>
|
||||
<translation>已发送</translation>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Received</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>Read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -391,32 +391,9 @@
|
|||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineItem</name>
|
||||
<name>TimelineModel</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineItem.cpp" line="+85"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation>删除消息失败:%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+39"/>
|
||||
<source>Reply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+11"/>
|
||||
<source>Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineView.cpp" line="+245"/>
|
||||
<source>Encryption is enabled</source>
|
||||
<translation>加密已启用</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+65"/>
|
||||
<location filename="../../src/timeline/TimelineModel.cpp" line="+780"/>
|
||||
<source>-- Encrypted Event (No keys found for decryption) --</source>
|
||||
<comment>Placeholder, when the message was not decrypted yet or can't be decrypted</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -440,16 +417,90 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+27"/>
|
||||
<location line="+25"/>
|
||||
<source>-- Encrypted Event (Unknown event type) --</source>
|
||||
<comment>Placeholder, when the message was decrypted, but we couldn't parse it, because Nheko/mtxclient don't support that event type yet</comment>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+50"/>
|
||||
<source>Message redaction failed: %1</source>
|
||||
<translation type="unfinished">删除消息失败:%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineRow</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineRow.qml" line="+57"/>
|
||||
<source>Reply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+14"/>
|
||||
<source>Options</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+12"/>
|
||||
<source>Read receipts</source>
|
||||
<translation type="unfinished">阅读回执</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Mark as read</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>View raw message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+4"/>
|
||||
<source>Redact message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>Save as</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineView</name>
|
||||
<message>
|
||||
<location filename="../qml/TimelineView.qml" line="+24"/>
|
||||
<source>No room open</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TimelineViewManager</name>
|
||||
<message>
|
||||
<location filename="../../src/timeline/TimelineViewManager.cpp" line="+161"/>
|
||||
<source>Save image</source>
|
||||
<translation type="unfinished">保存图像</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+2"/>
|
||||
<source>Save file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TopRoomBar</name>
|
||||
<message>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+79"/>
|
||||
<location filename="../../src/TopRoomBar.cpp" line="+78"/>
|
||||
<source>Room options</source>
|
||||
<translation>聊天室选项</translation>
|
||||
</message>
|
||||
|
@ -514,7 +565,7 @@
|
|||
<context>
|
||||
<name>UserSettingsPage</name>
|
||||
<message>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+166"/>
|
||||
<location filename="../../src/UserSettingsPage.cpp" line="+171"/>
|
||||
<source>Minimize to tray</source>
|
||||
<translation>最小化至托盘</translation>
|
||||
</message>
|
||||
|
@ -528,6 +579,11 @@
|
|||
<source>Group's sidebar</source>
|
||||
<translation>群组侧边栏</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Circular Avatars</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+9"/>
|
||||
<source>Typing notifications</source>
|
||||
|
@ -604,7 +660,7 @@
|
|||
<translation>通用</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+156"/>
|
||||
<location line="+161"/>
|
||||
<source>Open Sessions File</source>
|
||||
<translation>打开会话文件</translation>
|
||||
</message>
|
||||
|
@ -824,7 +880,7 @@ Media size: %2
|
|||
<context>
|
||||
<name>dialogs::ReadReceipts</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+121"/>
|
||||
<location filename="../../src/dialogs/ReadReceipts.cpp" line="+117"/>
|
||||
<source>Read receipts</source>
|
||||
<translation>阅读回执</translation>
|
||||
</message>
|
||||
|
@ -951,7 +1007,7 @@ Media size: %2
|
|||
<translation>启用加密失败:%1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+149"/>
|
||||
<location line="+148"/>
|
||||
<source>Select an avatar</source>
|
||||
<translation>选择一个头像</translation>
|
||||
</message>
|
||||
|
@ -977,19 +1033,6 @@ Media size: %2
|
|||
<translation>上传图像失败:%s</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserMentions</name>
|
||||
<message>
|
||||
<location filename="../../src/dialogs/UserMentions.cpp" line="+53"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>dialogs::UserProfile</name>
|
||||
<message>
|
||||
|
@ -1013,7 +1056,7 @@ Media size: %2
|
|||
<translation>开始一个聊天</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+57"/>
|
||||
<location line="+56"/>
|
||||
<source>Devices</source>
|
||||
<translation>设备</translation>
|
||||
</message>
|
||||
|
@ -1072,69 +1115,103 @@ Media size: %2
|
|||
<context>
|
||||
<name>message-description sent:</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="+104"/>
|
||||
<source>%1 an audio clip</source>
|
||||
<location filename="../../src/Utils.h" line="+95"/>
|
||||
<source>You sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 an image</source>
|
||||
<source>%1 sent an audio clip</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a file</source>
|
||||
<source>%1 sent an image</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a video clip</source>
|
||||
<source>%1 sent a file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a sticker</source>
|
||||
<source>%1 sent a video</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 a notification</source>
|
||||
<source>%1 sent a sticker</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent a notification</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+5"/>
|
||||
<source>You: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1: %2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+7"/>
|
||||
<source>%1 an encrypted message</source>
|
||||
<source>You sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+3"/>
|
||||
<source>%1 sent an encrypted message</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description:</name>
|
||||
<name>popups::UserMentions</name>
|
||||
<message>
|
||||
<location line="-26"/>
|
||||
<source>sent</source>
|
||||
<comment>For when someone else is the sender</comment>
|
||||
<location filename="../../src/popups/UserMentions.cpp" line="+61"/>
|
||||
<source>This Room</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>message-description: </name>
|
||||
<message>
|
||||
<location line="-2"/>
|
||||
<source>sent</source>
|
||||
<comment>For when you are the sender</comment>
|
||||
<location line="+1"/>
|
||||
<source>All Rooms</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>utils</name>
|
||||
<message>
|
||||
<location filename="../../src/Utils.cpp" line="+46"/>
|
||||
<location filename="../../src/Utils.h" line="+55"/>
|
||||
<source>You</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location line="+219"/>
|
||||
<location filename="../../src/Utils.cpp" line="+282"/>
|
||||
<source>sent a file.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1154,7 +1231,7 @@ Media size: %2
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../src/Utils.h" line="-23"/>
|
||||
<location filename="../../src/Utils.h" line="+4"/>
|
||||
<source>Unknown Message Type</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
|
51
resources/qml/Avatar.qml
Normal file
51
resources/qml/Avatar.qml
Normal file
|
@ -0,0 +1,51 @@
|
|||
import QtQuick 2.6
|
||||
import QtGraphicalEffects 1.0
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
Rectangle {
|
||||
id: avatar
|
||||
width: 48
|
||||
height: 48
|
||||
radius: settings.avatar_circles ? height/2 : 3
|
||||
|
||||
Settings {
|
||||
id: settings
|
||||
category: "user"
|
||||
property bool avatar_circles: true
|
||||
}
|
||||
|
||||
property alias url: img.source
|
||||
property string displayName
|
||||
|
||||
Text {
|
||||
anchors.fill: parent
|
||||
text: String.fromCodePoint(displayName.codePointAt(0))
|
||||
color: colors.text
|
||||
font.pixelSize: avatar.height/2
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Image {
|
||||
id: img
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
mipmap: true
|
||||
smooth: false
|
||||
|
||||
sourceSize.width: avatar.width
|
||||
sourceSize.height: avatar.height
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
anchors.fill: parent
|
||||
width: avatar.width
|
||||
height: avatar.height
|
||||
radius: settings.avatar_circles ? height/2 : 3
|
||||
}
|
||||
}
|
||||
}
|
||||
color: colors.dark
|
||||
}
|
24
resources/qml/EncryptionIndicator.qml
Normal file
24
resources/qml/EncryptionIndicator.qml
Normal file
|
@ -0,0 +1,24 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import com.github.nheko 1.0
|
||||
|
||||
Rectangle {
|
||||
id: indicator
|
||||
color: "transparent"
|
||||
width: 16
|
||||
height: 16
|
||||
ToolTip.visible: ma.containsMouse && indicator.visible
|
||||
ToolTip.text: qsTr("Encrypted")
|
||||
MouseArea{
|
||||
id: ma
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
Image {
|
||||
id: stateImg
|
||||
anchors.fill: parent
|
||||
source: "image://colorimage/:/icons/icons/ui/lock.png?"+colors.buttonText
|
||||
}
|
||||
}
|
||||
|
29
resources/qml/ImageButton.qml
Normal file
29
resources/qml/ImageButton.qml
Normal file
|
@ -0,0 +1,29 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
Button {
|
||||
property string image: undefined
|
||||
|
||||
id: button
|
||||
|
||||
flat: true
|
||||
|
||||
// disable background, because we don't want a border on hover
|
||||
background: Item {
|
||||
}
|
||||
|
||||
Image {
|
||||
id: buttonImg
|
||||
// Workaround, can't get icon.source working for now...
|
||||
anchors.fill: parent
|
||||
source: "image://colorimage/" + image + "?" + (button.hovered ? colors.highlight : colors.buttonText)
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
33
resources/qml/MatrixText.qml
Normal file
33
resources/qml/MatrixText.qml
Normal file
|
@ -0,0 +1,33 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
TextEdit {
|
||||
textFormat: TextEdit.RichText
|
||||
readOnly: true
|
||||
wrapMode: Text.Wrap
|
||||
selectByMouse: true
|
||||
color: colors.text
|
||||
|
||||
onLinkActivated: {
|
||||
if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1])
|
||||
else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) timelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1])
|
||||
else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) {
|
||||
var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link)
|
||||
timelineManager.setHistoryView(match[1])
|
||||
chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain)
|
||||
}
|
||||
else Qt.openUrlExternally(link)
|
||||
}
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
|
||||
ToolTip {
|
||||
visible: parent.hoveredLink
|
||||
text: parent.hoveredLink
|
||||
palette: colors
|
||||
}
|
||||
}
|
38
resources/qml/StatusIndicator.qml
Normal file
38
resources/qml/StatusIndicator.qml
Normal file
|
@ -0,0 +1,38 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
import com.github.nheko 1.0
|
||||
|
||||
Rectangle {
|
||||
id: indicator
|
||||
property int state: 0
|
||||
color: "transparent"
|
||||
width: 16
|
||||
height: 16
|
||||
ToolTip.visible: ma.containsMouse && state != MtxEvent.Empty
|
||||
ToolTip.text: switch (state) {
|
||||
case MtxEvent.Failed: return qsTr("Failed")
|
||||
case MtxEvent.Sent: return qsTr("Sent")
|
||||
case MtxEvent.Received: return qsTr("Received")
|
||||
case MtxEvent.Read: return qsTr("Read")
|
||||
default: return ""
|
||||
}
|
||||
MouseArea{
|
||||
id: ma
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
Image {
|
||||
id: stateImg
|
||||
// Workaround, can't get icon.source working for now...
|
||||
anchors.fill: parent
|
||||
source: switch (indicator.state) {
|
||||
case MtxEvent.Failed: return "image://colorimage/:/icons/icons/ui/remove-symbol.png?" + colors.buttonText
|
||||
case MtxEvent.Sent: return "image://colorimage/:/icons/icons/ui/clock.png?" + colors.buttonText
|
||||
case MtxEvent.Received: return "image://colorimage/:/icons/icons/ui/checkmark.png?" + colors.buttonText
|
||||
case MtxEvent.Read: return "image://colorimage/:/icons/icons/ui/double-tick-indicator.png?" + colors.buttonText
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
122
resources/qml/TimelineRow.qml
Normal file
122
resources/qml/TimelineRow.qml
Normal file
|
@ -0,0 +1,122 @@
|
|||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import com.github.nheko 1.0
|
||||
|
||||
import "./delegates"
|
||||
|
||||
RowLayout {
|
||||
property var view: chat
|
||||
|
||||
anchors.leftMargin: avatarSize + 4
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
height: Math.max(contentItem.height, 16)
|
||||
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
//property var replyTo: model.replyTo
|
||||
|
||||
//Text {
|
||||
// property int idx: timelineManager.timeline.idToIndex(replyTo)
|
||||
// text: "" + (idx != -1 ? timelineManager.timeline.data(timelineManager.timeline.index(idx, 0), 2) : "nothing")
|
||||
//}
|
||||
MessageDelegate {
|
||||
id: contentItem
|
||||
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
}
|
||||
}
|
||||
|
||||
StatusIndicator {
|
||||
state: model.state
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
Layout.preferredHeight: 16
|
||||
}
|
||||
|
||||
EncryptionIndicator {
|
||||
visible: model.isEncrypted
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
Layout.preferredHeight: 16
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
Layout.preferredHeight: 16
|
||||
id: replyButton
|
||||
|
||||
image: ":/icons/icons/ui/mail-reply.png"
|
||||
ToolTip {
|
||||
visible: replyButton.hovered
|
||||
text: qsTr("Reply")
|
||||
palette: colors
|
||||
}
|
||||
|
||||
onClicked: view.model.replyAction(model.id)
|
||||
}
|
||||
ImageButton {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
Layout.preferredHeight: 16
|
||||
id: optionsButton
|
||||
|
||||
image: ":/icons/icons/ui/vertical-ellipsis.png"
|
||||
ToolTip {
|
||||
visible: optionsButton.hovered
|
||||
text: qsTr("Options")
|
||||
palette: colors
|
||||
}
|
||||
|
||||
onClicked: contextMenu.open()
|
||||
|
||||
Menu {
|
||||
y: optionsButton.height
|
||||
id: contextMenu
|
||||
palette: colors
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("Read receipts")
|
||||
onTriggered: view.model.readReceiptsAction(model.id)
|
||||
}
|
||||
MenuItem {
|
||||
text: qsTr("Mark as read")
|
||||
}
|
||||
MenuItem {
|
||||
text: qsTr("View raw message")
|
||||
onTriggered: view.model.viewRawMessage(model.id)
|
||||
}
|
||||
MenuItem {
|
||||
text: qsTr("Redact message")
|
||||
onTriggered: view.model.redactEvent(model.id)
|
||||
}
|
||||
MenuItem {
|
||||
visible: model.type == MtxEvent.ImageMessage || model.type == MtxEvent.VideoMessage || model.type == MtxEvent.AudioMessage || model.type == MtxEvent.FileMessage || model.type == MtxEvent.Sticker
|
||||
text: qsTr("Save as")
|
||||
onTriggered: timelineManager.saveMedia(model.url, model.filename, model.mimetype, model.type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||
text: model.timestamp.toLocaleTimeString("HH:mm")
|
||||
color: inactiveColors.text
|
||||
|
||||
MouseArea{
|
||||
id: ma
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
ToolTip {
|
||||
visible: ma.containsMouse
|
||||
text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate)
|
||||
palette: colors
|
||||
}
|
||||
}
|
||||
}
|
165
resources/qml/TimelineView.qml
Normal file
165
resources/qml/TimelineView.qml
Normal file
|
@ -0,0 +1,165 @@
|
|||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import com.github.nheko 1.0
|
||||
|
||||
import "./delegates"
|
||||
|
||||
Item {
|
||||
property var colors: currentActivePalette
|
||||
property var systemInactive: SystemPalette { colorGroup: SystemPalette.Disabled }
|
||||
property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive
|
||||
property int avatarSize: 40
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: colors.window
|
||||
|
||||
Text {
|
||||
visible: !timelineManager.timeline && !timelineManager.isInitialSync
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("No room open")
|
||||
font.pointSize: 24
|
||||
color: colors.windowText
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
running: timelineManager.isInitialSync
|
||||
height: 200
|
||||
width: 200
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: chat
|
||||
|
||||
cacheBuffer: 2000
|
||||
|
||||
visible: timelineManager.timeline != null
|
||||
anchors.fill: parent
|
||||
|
||||
anchors.leftMargin: 4
|
||||
anchors.rightMargin: scrollbar.width
|
||||
|
||||
model: timelineManager.timeline
|
||||
|
||||
onModelChanged: {
|
||||
if (model) {
|
||||
currentIndex = model.currentIndex
|
||||
if (model.currentIndex == count - 1) {
|
||||
positionViewAtEnd()
|
||||
} else {
|
||||
positionViewAtIndex(model.currentIndex, ListView.End)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: scrollbar
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
onPressedChanged: if (!pressed) chat.updatePosition()
|
||||
}
|
||||
|
||||
property bool atBottom: false
|
||||
onCountChanged: {
|
||||
if (atBottom) {
|
||||
var newIndex = count - 1 // last index
|
||||
positionViewAtEnd()
|
||||
currentIndex = newIndex
|
||||
model.currentIndex = newIndex
|
||||
}
|
||||
|
||||
if (contentHeight < height && model) {
|
||||
model.fetchHistory();
|
||||
}
|
||||
}
|
||||
|
||||
onAtYBeginningChanged: if (atYBeginning) { chat.model.currentIndex = 0; chat.currentIndex = 0; model.fetchHistory(); }
|
||||
|
||||
function updatePosition() {
|
||||
for (var y = chat.contentY + chat.height; y > chat.height; y -= 9) {
|
||||
var i = chat.itemAt(100, y);
|
||||
if (!i) continue;
|
||||
if (!i.isFullyVisible()) continue;
|
||||
chat.model.currentIndex = i.getIndex();
|
||||
chat.currentIndex = i.getIndex()
|
||||
atBottom = i.getIndex() == count - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
onMovementEnded: updatePosition()
|
||||
|
||||
spacing: 4
|
||||
delegate: TimelineRow {
|
||||
function isFullyVisible() {
|
||||
return height > 1 && (y - chat.contentY - 1) + height < chat.height
|
||||
}
|
||||
function getIndex() {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
property: "section"
|
||||
delegate: Column {
|
||||
topPadding: 4
|
||||
bottomPadding: 4
|
||||
spacing: 8
|
||||
|
||||
width: parent.width
|
||||
height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
|
||||
|
||||
Label {
|
||||
id: dateBubble
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: section.includes(" ")
|
||||
text: chat.model.formatDateSeparator(new Date(Number(section.split(" ")[1])))
|
||||
color: colors.windowText
|
||||
|
||||
height: contentHeight * 1.2
|
||||
width: contentWidth * 1.2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
background: Rectangle {
|
||||
radius: parent.height / 2
|
||||
color: colors.dark
|
||||
}
|
||||
}
|
||||
Row {
|
||||
height: userName.height
|
||||
spacing: 4
|
||||
Avatar {
|
||||
width: avatarSize
|
||||
height: avatarSize
|
||||
url: chat.model.avatarUrl(section.split(" ")[0]).replace("mxc://", "image://MxcImage/")
|
||||
displayName: chat.model.displayName(section.split(" ")[0])
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: chat.model.openUserProfile(section.split(" ")[0])
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: userName
|
||||
text: chat.model.escapeEmoji(chat.model.displayName(section.split(" ")[0]))
|
||||
color: chat.model.userColor(section.split(" ")[0], colors.window)
|
||||
textFormat: Text.RichText
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: chat.model.openUserProfile(section.split(" ")[0])
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
57
resources/qml/delegates/FileMessage.qml
Normal file
57
resources/qml/delegates/FileMessage.qml
Normal file
|
@ -0,0 +1,57 @@
|
|||
import QtQuick 2.6
|
||||
import QtQuick.Layouts 1.2
|
||||
|
||||
Rectangle {
|
||||
radius: 10
|
||||
color: colors.dark
|
||||
height: row.height + 24
|
||||
width: parent ? parent.width : undefined
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 24
|
||||
|
||||
spacing: 15
|
||||
|
||||
Rectangle {
|
||||
id: button
|
||||
color: colors.light
|
||||
radius: 22
|
||||
height: 44
|
||||
width: 44
|
||||
Image {
|
||||
id: img
|
||||
anchors.centerIn: parent
|
||||
|
||||
source: "qrc:/icons/icons/ui/arrow-pointing-down.png"
|
||||
fillMode: Image.Pad
|
||||
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: timelineManager.saveMedia(model.url, model.filename, model.mimetype, model.type)
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
id: col
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: model.body
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
color: colors.text
|
||||
}
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: model.filesize
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
color: colors.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
resources/qml/delegates/ImageMessage.qml
Normal file
23
resources/qml/delegates/ImageMessage.qml
Normal file
|
@ -0,0 +1,23 @@
|
|||
import QtQuick 2.6
|
||||
|
||||
import com.github.nheko 1.0
|
||||
|
||||
Item {
|
||||
width: Math.min(parent ? parent.width : undefined, model.width)
|
||||
height: width * model.proportionalHeight
|
||||
|
||||
Image {
|
||||
id: img
|
||||
anchors.fill: parent
|
||||
|
||||
source: model.url.replace("mxc://", "image://MxcImage/")
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
MouseArea {
|
||||
enabled: model.type == MtxEvent.ImageMessage
|
||||
anchors.fill: parent
|
||||
onClicked: timelineManager.openImageOverlay(model.url, model.filename, model.mimetype, model.type)
|
||||
}
|
||||
}
|
||||
}
|
55
resources/qml/delegates/MessageDelegate.qml
Normal file
55
resources/qml/delegates/MessageDelegate.qml
Normal file
|
@ -0,0 +1,55 @@
|
|||
import QtQuick 2.6
|
||||
import com.github.nheko 1.0
|
||||
|
||||
DelegateChooser {
|
||||
//role: "type" //< not supported in our custom implementation, have to use roleValue
|
||||
roleValue: model.type
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.TextMessage
|
||||
TextMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.NoticeMessage
|
||||
NoticeMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.EmoteMessage
|
||||
TextMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.ImageMessage
|
||||
ImageMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Sticker
|
||||
ImageMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.FileMessage
|
||||
FileMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.VideoMessage
|
||||
PlayableMediaMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.AudioMessage
|
||||
PlayableMediaMessage {}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Redacted
|
||||
Pill {
|
||||
text: qsTr("redacted")
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: MtxEvent.Encryption
|
||||
Pill {
|
||||
text: qsTr("Encryption enabled")
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
Placeholder {}
|
||||
}
|
||||
}
|
8
resources/qml/delegates/NoticeMessage.qml
Normal file
8
resources/qml/delegates/NoticeMessage.qml
Normal file
|
@ -0,0 +1,8 @@
|
|||
import ".."
|
||||
|
||||
MatrixText {
|
||||
text: model.formattedBody
|
||||
width: parent ? parent.width : undefined
|
||||
font.italic: true
|
||||
color: inactiveColors.text
|
||||
}
|
14
resources/qml/delegates/Pill.qml
Normal file
14
resources/qml/delegates/Pill.qml
Normal file
|
@ -0,0 +1,14 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
|
||||
Label {
|
||||
color: inactiveColors.text
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
height: contentHeight * 1.2
|
||||
width: contentWidth * 1.2
|
||||
background: Rectangle {
|
||||
radius: parent.height / 2
|
||||
color: colors.dark
|
||||
}
|
||||
}
|
7
resources/qml/delegates/Placeholder.qml
Normal file
7
resources/qml/delegates/Placeholder.qml
Normal file
|
@ -0,0 +1,7 @@
|
|||
import ".."
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("unimplemented event: ") + model.type
|
||||
width: parent ? parent.width : undefined
|
||||
color: inactiveColors.text
|
||||
}
|
164
resources/qml/delegates/PlayableMediaMessage.qml
Normal file
164
resources/qml/delegates/PlayableMediaMessage.qml
Normal file
|
@ -0,0 +1,164 @@
|
|||
import QtQuick 2.6
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Controls 2.1
|
||||
import QtMultimedia 5.6
|
||||
|
||||
import com.github.nheko 1.0
|
||||
|
||||
Rectangle {
|
||||
id: bg
|
||||
radius: 10
|
||||
color: colors.dark
|
||||
height: content.height + 24
|
||||
width: parent ? parent.width : undefined
|
||||
|
||||
Column {
|
||||
id: content
|
||||
width: parent.width - 24
|
||||
anchors.centerIn: parent
|
||||
|
||||
Rectangle {
|
||||
id: videoContainer
|
||||
visible: model.type == MtxEvent.VideoMessage
|
||||
width: Math.min(parent.width, model.width ? model.width : 400) // some media has 0 as size...
|
||||
height: width*model.proportionalHeight
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
source: model.thumbnailUrl.replace("mxc://", "image://MxcImage/")
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
VideoOutput {
|
||||
anchors.fill: parent
|
||||
fillMode: VideoOutput.PreserveAspectFit
|
||||
source: media
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
Text {
|
||||
id: positionText
|
||||
text: "--:--:--"
|
||||
color: colors.text
|
||||
}
|
||||
Slider {
|
||||
Layout.fillWidth: true
|
||||
id: progress
|
||||
value: media.position
|
||||
from: 0
|
||||
to: media.duration
|
||||
|
||||
onMoved: media.seek(value)
|
||||
//indeterminate: true
|
||||
function updatePositionTexts() {
|
||||
function formatTime(date) {
|
||||
var hh = date.getUTCHours();
|
||||
var mm = date.getUTCMinutes();
|
||||
var ss = date.getSeconds();
|
||||
if (hh < 10) {hh = "0"+hh;}
|
||||
if (mm < 10) {mm = "0"+mm;}
|
||||
if (ss < 10) {ss = "0"+ss;}
|
||||
return hh+":"+mm+":"+ss;
|
||||
}
|
||||
positionText.text = formatTime(new Date(media.position))
|
||||
durationText.text = formatTime(new Date(media.duration))
|
||||
}
|
||||
onValueChanged: updatePositionTexts()
|
||||
}
|
||||
Text {
|
||||
id: durationText
|
||||
text: "--:--:--"
|
||||
color: colors.text
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
|
||||
spacing: 15
|
||||
|
||||
Rectangle {
|
||||
id: button
|
||||
color: colors.light
|
||||
radius: 22
|
||||
height: 44
|
||||
width: 44
|
||||
Image {
|
||||
id: img
|
||||
anchors.centerIn: parent
|
||||
|
||||
source: "qrc:/icons/icons/ui/arrow-pointing-down.png"
|
||||
fillMode: Image.Pad
|
||||
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
switch (button.state) {
|
||||
case "": timelineManager.cacheMedia(model.url, model.mimetype); break;
|
||||
case "stopped":
|
||||
media.play(); console.log("play");
|
||||
button.state = "playing"
|
||||
break
|
||||
case "playing":
|
||||
media.pause(); console.log("pause");
|
||||
button.state = "stopped"
|
||||
break
|
||||
}
|
||||
}
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
MediaPlayer {
|
||||
id: media
|
||||
onError: console.log(errorString)
|
||||
onStatusChanged: if(status == MediaPlayer.Loaded) progress.updatePositionTexts()
|
||||
onStopped: button.state = "stopped"
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: timelineManager
|
||||
onMediaCached: {
|
||||
if (mxcUrl == model.url) {
|
||||
media.source = "file://" + cacheUrl
|
||||
button.state = "stopped"
|
||||
console.log("media loaded: " + mxcUrl + " at " + cacheUrl)
|
||||
}
|
||||
console.log("media cached: " + mxcUrl + " at " + cacheUrl)
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "stopped"
|
||||
PropertyChanges { target: img; source: "qrc:/icons/icons/ui/play-sign.png" }
|
||||
},
|
||||
State {
|
||||
name: "playing"
|
||||
PropertyChanges { target: img; source: "qrc:/icons/icons/ui/pause-symbol.png" }
|
||||
}
|
||||
]
|
||||
}
|
||||
ColumnLayout {
|
||||
id: col
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: model.body
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
color: colors.text
|
||||
}
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: model.filesize
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
color: colors.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
6
resources/qml/delegates/TextMessage.qml
Normal file
6
resources/qml/delegates/TextMessage.qml
Normal file
|
@ -0,0 +1,6 @@
|
|||
import ".."
|
||||
|
||||
MatrixText {
|
||||
text: model.formattedBody.replace("<pre>", "<pre style='white-space: pre-wrap'>")
|
||||
width: parent ? parent.width : undefined
|
||||
}
|
|
@ -114,4 +114,21 @@
|
|||
<file>styles/nheko.qss</file>
|
||||
<file>styles/nheko-dark.qss</file>
|
||||
</qresource>
|
||||
<qresource prefix="/">
|
||||
<file>qml/TimelineView.qml</file>
|
||||
<file>qml/Avatar.qml</file>
|
||||
<file>qml/ImageButton.qml</file>
|
||||
<file>qml/MatrixText.qml</file>
|
||||
<file>qml/StatusIndicator.qml</file>
|
||||
<file>qml/EncryptionIndicator.qml</file>
|
||||
<file>qml/TimelineRow.qml</file>
|
||||
<file>qml/delegates/MessageDelegate.qml</file>
|
||||
<file>qml/delegates/TextMessage.qml</file>
|
||||
<file>qml/delegates/NoticeMessage.qml</file>
|
||||
<file>qml/delegates/ImageMessage.qml</file>
|
||||
<file>qml/delegates/PlayableMediaMessage.qml</file>
|
||||
<file>qml/delegates/FileMessage.qml</file>
|
||||
<file>qml/delegates/Pill.qml</file>
|
||||
<file>qml/delegates/Placeholder.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
@ -43,7 +43,6 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca
|
|||
|
||||
QPixmap pixmap;
|
||||
if (avatar_cache.find(cacheKey, &pixmap)) {
|
||||
nhlog::net()->info("cached pixmap {}", avatarUrl.toStdString());
|
||||
callback(pixmap);
|
||||
return;
|
||||
}
|
||||
|
@ -52,7 +51,6 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca
|
|||
if (!data.isNull()) {
|
||||
pixmap.loadFromData(data);
|
||||
avatar_cache.insert(cacheKey, pixmap);
|
||||
nhlog::net()->info("loaded pixmap from disk cache {}", avatarUrl.toStdString());
|
||||
callback(pixmap);
|
||||
return;
|
||||
}
|
||||
|
@ -69,8 +67,8 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca
|
|||
});
|
||||
|
||||
mtx::http::ThumbOpts opts;
|
||||
opts.width = 256;
|
||||
opts.height = 256;
|
||||
opts.width = size;
|
||||
opts.height = size;
|
||||
opts.mxc_url = avatarUrl.toStdString();
|
||||
|
||||
http::client()->get_thumbnail(
|
||||
|
@ -86,8 +84,6 @@ resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback ca
|
|||
|
||||
cache::client()->saveImage(opts.mxc_url, res);
|
||||
|
||||
nhlog::net()->info("downloaded pixmap {}", opts.mxc_url);
|
||||
|
||||
emit proxy->avatarDownloaded(QByteArray(res.data(), res.size()));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -91,7 +91,6 @@ from_json(const json &j, ReadReceiptKey &key)
|
|||
struct DescInfo
|
||||
{
|
||||
QString event_id;
|
||||
QString username;
|
||||
QString userid;
|
||||
QString body;
|
||||
QString timestamp;
|
||||
|
|
|
@ -113,12 +113,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||
view_manager_ = new TimelineViewManager(this);
|
||||
|
||||
contentLayout_->addWidget(top_bar_);
|
||||
contentLayout_->addWidget(view_manager_);
|
||||
|
||||
connect(this,
|
||||
&ChatPage::removeTimelineEvent,
|
||||
view_manager_,
|
||||
&TimelineViewManager::removeTimelineEvent);
|
||||
contentLayout_->addWidget(view_manager_->getWidget());
|
||||
|
||||
// Splitter
|
||||
splitter->addWidget(sideBar_);
|
||||
|
@ -566,7 +561,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||
connect(this,
|
||||
&ChatPage::initializeViews,
|
||||
view_manager_,
|
||||
[this](const mtx::responses::Rooms &rooms) { view_manager_->initialize(rooms); });
|
||||
[this](const mtx::responses::Rooms &rooms) { view_manager_->sync(rooms); });
|
||||
connect(this,
|
||||
&ChatPage::initializeEmptyViews,
|
||||
view_manager_,
|
||||
|
@ -582,7 +577,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||
nhlog::db()->error("failed to retrieve invites: {}", e.what());
|
||||
}
|
||||
|
||||
view_manager_->initialize(rooms);
|
||||
view_manager_->sync(rooms);
|
||||
removeLeftRooms(rooms.leave);
|
||||
|
||||
bool hasNotifications = false;
|
||||
|
|
|
@ -125,8 +125,6 @@ signals:
|
|||
void showUserSettingsPage();
|
||||
void showOverlayProgressBar();
|
||||
|
||||
void removeTimelineEvent(const QString &room_id, const QString &event_id);
|
||||
|
||||
void ownProfileOk();
|
||||
void setUserDisplayName(const QString &name);
|
||||
void setUserAvatar(const QString &avatar);
|
||||
|
|
30
src/ColorImageProvider.cpp
Normal file
30
src/ColorImageProvider.cpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#include "ColorImageProvider.h"
|
||||
|
||||
#include "Logging.h"
|
||||
#include <QPainter>
|
||||
|
||||
QPixmap
|
||||
ColorImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &)
|
||||
{
|
||||
auto args = id.split('?');
|
||||
|
||||
nhlog::ui()->info("Loading {}, source is {}", id.toStdString(), args[0].toStdString());
|
||||
|
||||
QPixmap source(args[0]);
|
||||
|
||||
if (size)
|
||||
*size = QSize(source.width(), source.height());
|
||||
|
||||
if (args.size() < 2)
|
||||
return source;
|
||||
|
||||
QColor color(args[1]);
|
||||
|
||||
QPixmap colorized = source;
|
||||
QPainter painter(&colorized);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
painter.fillRect(colorized.rect(), color);
|
||||
painter.end();
|
||||
|
||||
return colorized;
|
||||
}
|
11
src/ColorImageProvider.h
Normal file
11
src/ColorImageProvider.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
#include <QQuickImageProvider>
|
||||
|
||||
class ColorImageProvider : public QQuickImageProvider
|
||||
{
|
||||
public:
|
||||
ColorImageProvider()
|
||||
: QQuickImageProvider(QQuickImageProvider::Pixmap)
|
||||
{}
|
||||
|
||||
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
|
||||
};
|
|
@ -5,14 +5,43 @@
|
|||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
#include <iostream>
|
||||
|
||||
#include <QString>
|
||||
#include <QtGlobal>
|
||||
|
||||
namespace {
|
||||
std::shared_ptr<spdlog::logger> db_logger = nullptr;
|
||||
std::shared_ptr<spdlog::logger> net_logger = nullptr;
|
||||
std::shared_ptr<spdlog::logger> crypto_logger = nullptr;
|
||||
std::shared_ptr<spdlog::logger> ui_logger = nullptr;
|
||||
std::shared_ptr<spdlog::logger> qml_logger = nullptr;
|
||||
|
||||
constexpr auto MAX_FILE_SIZE = 1024 * 1024 * 6;
|
||||
constexpr auto MAX_LOG_FILES = 3;
|
||||
|
||||
void
|
||||
qmlMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||
{
|
||||
std::string localMsg = msg.toStdString();
|
||||
const char *file = context.file ? context.file : "";
|
||||
const char *function = context.function ? context.function : "";
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
nhlog::qml()->debug("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
nhlog::qml()->info("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
nhlog::qml()->warn("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace nhlog {
|
||||
|
@ -35,12 +64,15 @@ init(const std::string &file_path)
|
|||
db_logger = std::make_shared<spdlog::logger>("db", std::begin(sinks), std::end(sinks));
|
||||
crypto_logger =
|
||||
std::make_shared<spdlog::logger>("crypto", std::begin(sinks), std::end(sinks));
|
||||
qml_logger = std::make_shared<spdlog::logger>("qml", std::begin(sinks), std::end(sinks));
|
||||
|
||||
if (nheko::enable_debug_log) {
|
||||
db_logger->set_level(spdlog::level::trace);
|
||||
ui_logger->set_level(spdlog::level::trace);
|
||||
crypto_logger->set_level(spdlog::level::trace);
|
||||
}
|
||||
|
||||
qInstallMessageHandler(qmlMessageHandler);
|
||||
}
|
||||
|
||||
std::shared_ptr<spdlog::logger>
|
||||
|
@ -66,4 +98,10 @@ crypto()
|
|||
{
|
||||
return crypto_logger;
|
||||
}
|
||||
|
||||
std::shared_ptr<spdlog::logger>
|
||||
qml()
|
||||
{
|
||||
return qml_logger;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,5 +19,8 @@ db();
|
|||
std::shared_ptr<spdlog::logger>
|
||||
crypto();
|
||||
|
||||
std::shared_ptr<spdlog::logger>
|
||||
qml();
|
||||
|
||||
extern bool enable_debug_log_from_commandline;
|
||||
}
|
||||
|
|
|
@ -20,16 +20,6 @@ Q_DECLARE_METATYPE(nlohmann::json)
|
|||
Q_DECLARE_METATYPE(std::vector<std::string>)
|
||||
Q_DECLARE_METATYPE(std::vector<QString>)
|
||||
|
||||
class MediaProxy : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void imageDownloaded(const QPixmap &);
|
||||
void imageSaved(const QString &, const QByteArray &);
|
||||
void fileDownloaded(const QByteArray &);
|
||||
};
|
||||
|
||||
namespace http {
|
||||
mtx::http::Client *
|
||||
client();
|
||||
|
|
78
src/MxcImageProvider.cpp
Normal file
78
src/MxcImageProvider.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
#include "MxcImageProvider.h"
|
||||
|
||||
#include "Cache.h"
|
||||
|
||||
void
|
||||
MxcImageResponse::run()
|
||||
{
|
||||
if (m_requestedSize.isValid()) {
|
||||
QString fileName = QString("%1_%2x%3_crop")
|
||||
.arg(m_id)
|
||||
.arg(m_requestedSize.width())
|
||||
.arg(m_requestedSize.height());
|
||||
|
||||
auto data = cache::client()->image(fileName);
|
||||
if (!data.isNull() && m_image.loadFromData(data)) {
|
||||
m_image = m_image.scaled(m_requestedSize, Qt::KeepAspectRatio);
|
||||
m_image.setText("mxc url", "mxc://" + m_id);
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
mtx::http::ThumbOpts opts;
|
||||
opts.mxc_url = "mxc://" + m_id.toStdString();
|
||||
opts.width = m_requestedSize.width() > 0 ? m_requestedSize.width() : -1;
|
||||
opts.height = m_requestedSize.height() > 0 ? m_requestedSize.height() : -1;
|
||||
opts.method = "crop";
|
||||
http::client()->get_thumbnail(
|
||||
opts, [this, fileName](const std::string &res, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->error("Failed to download image {}",
|
||||
m_id.toStdString());
|
||||
m_error = "Failed download";
|
||||
emit finished();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = QByteArray(res.data(), res.size());
|
||||
cache::client()->saveImage(fileName, data);
|
||||
m_image.loadFromData(data);
|
||||
m_image.setText("mxc url", "mxc://" + m_id);
|
||||
|
||||
emit finished();
|
||||
});
|
||||
} else {
|
||||
auto data = cache::client()->image(m_id);
|
||||
if (!data.isNull() && m_image.loadFromData(data)) {
|
||||
m_image.setText("mxc url", "mxc://" + m_id);
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
http::client()->download(
|
||||
"mxc://" + m_id.toStdString(),
|
||||
[this](const std::string &res,
|
||||
const std::string &,
|
||||
const std::string &originalFilename,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->error("Failed to download image {}",
|
||||
m_id.toStdString());
|
||||
m_error = "Failed download";
|
||||
emit finished();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = QByteArray(res.data(), res.size());
|
||||
m_image.loadFromData(data);
|
||||
m_image.setText("original filename",
|
||||
QString::fromStdString(originalFilename));
|
||||
m_image.setText("mxc url", "mxc://" + m_id);
|
||||
cache::client()->saveImage(m_id, data);
|
||||
|
||||
emit finished();
|
||||
});
|
||||
}
|
||||
}
|
47
src/MxcImageProvider.h
Normal file
47
src/MxcImageProvider.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include <QQuickAsyncImageProvider>
|
||||
#include <QQuickImageResponse>
|
||||
|
||||
#include <QImage>
|
||||
#include <QThreadPool>
|
||||
|
||||
class MxcImageResponse
|
||||
: public QQuickImageResponse
|
||||
, public QRunnable
|
||||
{
|
||||
public:
|
||||
MxcImageResponse(const QString &id, const QSize &requestedSize)
|
||||
: m_id(id)
|
||||
, m_requestedSize(requestedSize)
|
||||
{
|
||||
setAutoDelete(false);
|
||||
}
|
||||
|
||||
QQuickTextureFactory *textureFactory() const override
|
||||
{
|
||||
return QQuickTextureFactory::textureFactoryForImage(m_image);
|
||||
}
|
||||
QString errorString() const override { return m_error; }
|
||||
|
||||
void run() override;
|
||||
|
||||
QString m_id, m_error;
|
||||
QSize m_requestedSize;
|
||||
QImage m_image;
|
||||
};
|
||||
|
||||
class MxcImageProvider : public QQuickAsyncImageProvider
|
||||
{
|
||||
public:
|
||||
QQuickImageResponse *requestImageResponse(const QString &id,
|
||||
const QSize &requestedSize) override
|
||||
{
|
||||
MxcImageResponse *response = new MxcImageResponse(id, requestedSize);
|
||||
pool.start(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private:
|
||||
QThreadPool pool;
|
||||
};
|
|
@ -118,7 +118,7 @@ RoomInfoListItem::RoomInfoListItem(QString room_id, RoomInfo info, QWidget *pare
|
|||
// so we can't use them for sorting.
|
||||
if (roomType_ == RoomType::Invited)
|
||||
lastMsgInfo_ = {
|
||||
emptyEventId, "-", "-", "-", "-", QDateTime::currentDateTime().addYears(10)};
|
||||
emptyEventId, "-", "-", "-", QDateTime::currentDateTime().addYears(10)};
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -142,7 +142,7 @@ RoomInfoListItem::resizeEvent(QResizeEvent *)
|
|||
void
|
||||
RoomInfoListItem::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
bool rounded = QSettings().value("user/avatar/circles", true).toBool();
|
||||
bool rounded = QSettings().value("user/avatar_circles", true).toBool();
|
||||
|
||||
Q_UNUSED(event);
|
||||
|
||||
|
@ -210,33 +210,11 @@ RoomInfoListItem::paintEvent(QPaintEvent *event)
|
|||
p.setFont(QFont{});
|
||||
p.setPen(subtitlePen);
|
||||
|
||||
// The limit is the space between the end of the avatar and the start of the
|
||||
// timestamp.
|
||||
int usernameLimit =
|
||||
std::max(0, width() - 3 * wm.padding - msgStampWidth - wm.iconSize - 20);
|
||||
auto userName =
|
||||
metrics.elidedText(lastMsgInfo_.username, Qt::ElideRight, usernameLimit);
|
||||
|
||||
p.setFont(QFont{});
|
||||
p.drawText(QPoint(2 * wm.padding + wm.iconSize, bottom_y), userName);
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
|
||||
int nameWidth = QFontMetrics(QFont{}).width(userName);
|
||||
#else
|
||||
int nameWidth = QFontMetrics(QFont{}).horizontalAdvance(userName);
|
||||
#endif
|
||||
p.setFont(QFont{});
|
||||
|
||||
// The limit is the space between the end of the username and the start of
|
||||
// the timestamp.
|
||||
int descriptionLimit =
|
||||
std::max(0,
|
||||
width() - 3 * wm.padding - bottomLineWidthLimit - wm.iconSize -
|
||||
nameWidth - 5);
|
||||
int descriptionLimit = std::max(
|
||||
0, width() - 3 * wm.padding - bottomLineWidthLimit - wm.iconSize);
|
||||
auto description =
|
||||
metrics.elidedText(lastMsgInfo_.body, Qt::ElideRight, descriptionLimit);
|
||||
p.drawText(QPoint(2 * wm.padding + wm.iconSize + nameWidth, bottom_y),
|
||||
description);
|
||||
p.drawText(QPoint(2 * wm.padding + wm.iconSize, bottom_y), description);
|
||||
|
||||
// We show the last message timestamp.
|
||||
p.save();
|
||||
|
|
|
@ -53,7 +53,7 @@ UserSettings::load()
|
|||
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();
|
||||
avatarCircles_ = settings.value("user/avatar_circles", true).toBool();
|
||||
emojiFont_ = settings.value("user/emoji_font_family", "default").toString();
|
||||
baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble();
|
||||
|
||||
|
@ -119,9 +119,7 @@ UserSettings::save()
|
|||
settings.setValue("start_in_tray", isStartInTrayEnabled_);
|
||||
settings.endGroup();
|
||||
|
||||
settings.beginGroup("avatar");
|
||||
settings.setValue("circles", avatarCircles_);
|
||||
settings.endGroup();
|
||||
settings.setValue("avatar_circles", avatarCircles_);
|
||||
|
||||
settings.setValue("font_size", baseFontSize_);
|
||||
settings.setValue("typing_notifications", isTypingNotificationsEnabled_);
|
||||
|
|
|
@ -40,9 +40,8 @@ utils::replaceEmoji(const QString &body)
|
|||
for (auto &code : utf32_string) {
|
||||
// TODO: Be more precise here.
|
||||
if (code > 9000)
|
||||
fmtBody +=
|
||||
QString("<span style=\"font-family: " + userFontFamily + ";\">") +
|
||||
QString::fromUcs4(&code, 1) + "</span>";
|
||||
fmtBody += QString("<font face=\"" + userFontFamily + "\">") +
|
||||
QString::fromUcs4(&code, 1) + "</font>";
|
||||
else
|
||||
fmtBody += QString::fromUcs4(&code, 1);
|
||||
}
|
||||
|
@ -147,11 +146,6 @@ utils::getMessageDescription(const TimelineEvent &event,
|
|||
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
|
||||
|
||||
DescInfo info;
|
||||
if (sender == localUser)
|
||||
info.username = QCoreApplication::translate("utils", "You");
|
||||
else
|
||||
info.username = username;
|
||||
|
||||
info.userid = sender;
|
||||
info.body = QString(" %1").arg(messageDescription<Encrypted>());
|
||||
info.timestamp = utils::descriptiveTime(ts);
|
||||
|
@ -324,19 +318,29 @@ utils::linkifyMessage(const QString &body)
|
|||
return doc;
|
||||
}
|
||||
|
||||
QByteArray escapeRawHtml(const QByteArray &data) {
|
||||
QByteArray buffer;
|
||||
const size_t length = data.size();
|
||||
buffer.reserve(length);
|
||||
for(size_t pos = 0; pos != length; ++pos) {
|
||||
switch(data.at(pos)) {
|
||||
case '&': buffer.append("&"); break;
|
||||
case '<': buffer.append("<"); break;
|
||||
case '>': buffer.append(">"); break;
|
||||
default: buffer.append(data.at(pos)); break;
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
QByteArray
|
||||
escapeRawHtml(const QByteArray &data)
|
||||
{
|
||||
QByteArray buffer;
|
||||
const size_t length = data.size();
|
||||
buffer.reserve(length);
|
||||
for (size_t pos = 0; pos != length; ++pos) {
|
||||
switch (data.at(pos)) {
|
||||
case '&':
|
||||
buffer.append("&");
|
||||
break;
|
||||
case '<':
|
||||
buffer.append("<");
|
||||
break;
|
||||
case '>':
|
||||
buffer.append(">");
|
||||
break;
|
||||
default:
|
||||
buffer.append(data.at(pos));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
QString
|
||||
|
@ -362,7 +366,7 @@ utils::getFormattedQuoteBody(const RelatedInfo &related, const QString &html)
|
|||
{
|
||||
return QString("<mx-reply><blockquote><a "
|
||||
"href=\"https://matrix.to/#/%1/%2\">In reply "
|
||||
"to</a>* <a href=\"https://matrix.to/#/%3\">%4</a><br "
|
||||
"to</a> <a href=\"https://matrix.to/#/%3\">%4</a><br"
|
||||
"/>%5</blockquote></mx-reply>")
|
||||
.arg(related.room,
|
||||
QString::fromStdString(related.related_event),
|
||||
|
@ -378,9 +382,6 @@ utils::getQuoteBody(const RelatedInfo &related)
|
|||
using MsgType = mtx::events::MessageType;
|
||||
|
||||
switch (related.type) {
|
||||
case MsgType::Text: {
|
||||
return markdownToHtml(related.quoted_body);
|
||||
}
|
||||
case MsgType::File: {
|
||||
return QString(QCoreApplication::translate("utils", "sent a file."));
|
||||
}
|
||||
|
|
116
src/Utils.h
116
src/Utils.h
|
@ -4,10 +4,6 @@
|
|||
|
||||
#include "Cache.h"
|
||||
#include "RoomInfoListItem.h"
|
||||
#include "timeline/widgets/AudioItem.h"
|
||||
#include "timeline/widgets/FileItem.h"
|
||||
#include "timeline/widgets/ImageItem.h"
|
||||
#include "timeline/widgets/VideoItem.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
|
@ -94,38 +90,72 @@ messageDescription(const QString &username = "",
|
|||
using Video = mtx::events::RoomEvent<mtx::events::msg::Video>;
|
||||
using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>;
|
||||
|
||||
// Sometimes the verb form of sent changes in some languages depending on the actor.
|
||||
auto remoteSent = QCoreApplication::translate(
|
||||
"message-description: ", "sent", "For when you are the sender");
|
||||
auto localSent = QCoreApplication::translate(
|
||||
"message-description:", "sent", "For when someone else is the sender");
|
||||
QString sentVerb = isLocal ? localSent : remoteSent;
|
||||
if (std::is_same<T, AudioItem>::value || std::is_same<T, Audio>::value) {
|
||||
return QCoreApplication::translate("message-description sent:", "%1 an audio clip")
|
||||
.arg(sentVerb);
|
||||
} else if (std::is_same<T, ImageItem>::value || std::is_same<T, Image>::value) {
|
||||
return QCoreApplication::translate("message-description sent:", "%1 an image")
|
||||
.arg(sentVerb);
|
||||
} else if (std::is_same<T, FileItem>::value || std::is_same<T, File>::value) {
|
||||
return QCoreApplication::translate("message-description sent:", "%1 a file")
|
||||
.arg(sentVerb);
|
||||
} else if (std::is_same<T, VideoItem>::value || std::is_same<T, Video>::value) {
|
||||
return QCoreApplication::translate("message-description sent:", "%1 a video clip")
|
||||
.arg(sentVerb);
|
||||
} else if (std::is_same<T, StickerItem>::value || std::is_same<T, Sticker>::value) {
|
||||
return QCoreApplication::translate("message-description sent:", "%1 a sticker")
|
||||
.arg(sentVerb);
|
||||
if (std::is_same<T, Audio>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent an audio clip");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent an audio clip")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Image>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent an image");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent an image")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, File>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent a file");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent a file")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Video>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent a video");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent a video")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Sticker>::value) {
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent a sticker");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent a sticker")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Notice>::value) {
|
||||
return QCoreApplication::translate("message-description sent:", "%1 a notification")
|
||||
.arg(sentVerb);
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent a notification");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent a notification")
|
||||
.arg(username);
|
||||
} else if (std::is_same<T, Text>::value) {
|
||||
return QString(": %1").arg(body);
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:", "You: %1")
|
||||
.arg(body);
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:", "%1: %2")
|
||||
.arg(username)
|
||||
.arg(body);
|
||||
} else if (std::is_same<T, Emote>::value) {
|
||||
return QString("* %1 %2").arg(username).arg(body);
|
||||
} else if (std::is_same<T, Encrypted>::value) {
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 an encrypted message")
|
||||
.arg(sentVerb);
|
||||
if (isLocal)
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"You sent an encrypted message");
|
||||
else
|
||||
return QCoreApplication::translate("message-description sent:",
|
||||
"%1 sent an encrypted message")
|
||||
.arg(username);
|
||||
} else {
|
||||
return QCoreApplication::translate("utils", "Unknown Message Type");
|
||||
}
|
||||
|
@ -135,29 +165,19 @@ template<class T, class Event>
|
|||
DescInfo
|
||||
createDescriptionInfo(const Event &event, const QString &localUser, const QString &room_id)
|
||||
{
|
||||
using Text = mtx::events::RoomEvent<mtx::events::msg::Text>;
|
||||
using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>;
|
||||
|
||||
const auto msg = boost::get<T>(event);
|
||||
const auto sender = QString::fromStdString(msg.sender);
|
||||
|
||||
const auto username = Cache::displayName(room_id, sender);
|
||||
const auto ts = QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts);
|
||||
|
||||
bool isText = std::is_same<T, Text>::value;
|
||||
bool isEmote = std::is_same<T, Emote>::value;
|
||||
|
||||
return DescInfo{
|
||||
QString::fromStdString(msg.event_id),
|
||||
isEmote ? ""
|
||||
: (sender == localUser ? QCoreApplication::translate("utils", "You") : username),
|
||||
sender,
|
||||
(isText || isEmote)
|
||||
? messageDescription<T>(
|
||||
username, QString::fromStdString(msg.content.body).trimmed(), sender == localUser)
|
||||
: QString(" %1").arg(messageDescription<T>()),
|
||||
utils::descriptiveTime(ts),
|
||||
ts};
|
||||
return DescInfo{QString::fromStdString(msg.event_id),
|
||||
sender,
|
||||
messageDescription<T>(username,
|
||||
QString::fromStdString(msg.content.body).trimmed(),
|
||||
sender == localUser),
|
||||
utils::descriptiveTime(ts),
|
||||
ts};
|
||||
}
|
||||
|
||||
//! Scale down an image to fit to the given width & height limitations.
|
||||
|
|
|
@ -41,7 +41,6 @@ ImageOverlay::ImageOverlay(QPixmap image, QWidget *parent)
|
|||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setWindowState(Qt::WindowFullScreen);
|
||||
|
||||
// Deprecated in 5.13: screen_ = QApplication::desktop()->availableGeometry();
|
||||
screen_ = QGuiApplication::primaryScreen()->availableGeometry();
|
||||
|
||||
move(QApplication::desktop()->mapToGlobal(screen_.topLeft()));
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <QAbstractSlider>
|
||||
#include <QLabel>
|
||||
#include <QListWidgetItem>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
|
|
|
@ -488,7 +488,7 @@ RoomSettings::retrieveRoomInfo()
|
|||
usesEncryption_ = cache::client()->isRoomEncrypted(room_id_.toStdString());
|
||||
info_ = cache::client()->singleRoomInfo(room_id_.toStdString());
|
||||
setAvatar();
|
||||
} catch (const lmdb::error &e) {
|
||||
} catch (const lmdb::error &) {
|
||||
nhlog::db()->warn("failed to retrieve room info from cache: {}",
|
||||
room_id_.toStdString());
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#include "ChatPage.h"
|
||||
#include "Logging.h"
|
||||
#include "UserMentions.h"
|
||||
#include "timeline/TimelineItem.h"
|
||||
//#include "timeline/TimelineItem.h"
|
||||
|
||||
using namespace popups;
|
||||
|
||||
|
@ -116,39 +116,46 @@ UserMentions::pushItem(const QString &event_id,
|
|||
const QString &room_id,
|
||||
const QString ¤t_room_id)
|
||||
{
|
||||
setUpdatesEnabled(false);
|
||||
|
||||
// Add to the 'all' section
|
||||
TimelineItem *view_item = new TimelineItem(
|
||||
mtx::events::MessageType::Text, user_id, body, true, room_id, all_scroll_widget_);
|
||||
view_item->setEventId(event_id);
|
||||
view_item->hide();
|
||||
|
||||
all_scroll_layout_->addWidget(view_item);
|
||||
QTimer::singleShot(0, this, [view_item, this]() {
|
||||
view_item->show();
|
||||
view_item->adjustSize();
|
||||
setUpdatesEnabled(true);
|
||||
});
|
||||
|
||||
// if it matches the current room... add it to the current room as well.
|
||||
if (QString::compare(room_id, current_room_id, Qt::CaseInsensitive) == 0) {
|
||||
// Add to the 'local' section
|
||||
TimelineItem *local_view_item = new TimelineItem(mtx::events::MessageType::Text,
|
||||
user_id,
|
||||
body,
|
||||
true,
|
||||
room_id,
|
||||
local_scroll_widget_);
|
||||
local_view_item->setEventId(event_id);
|
||||
local_view_item->hide();
|
||||
local_scroll_layout_->addWidget(local_view_item);
|
||||
|
||||
QTimer::singleShot(0, this, [local_view_item]() {
|
||||
local_view_item->show();
|
||||
local_view_item->adjustSize();
|
||||
});
|
||||
}
|
||||
(void)event_id;
|
||||
(void)user_id;
|
||||
(void)body;
|
||||
(void)room_id;
|
||||
(void)current_room_id;
|
||||
// setUpdatesEnabled(false);
|
||||
//
|
||||
// // Add to the 'all' section
|
||||
// TimelineItem *view_item = new TimelineItem(
|
||||
// mtx::events::MessageType::Text, user_id, body, true, room_id,
|
||||
// all_scroll_widget_);
|
||||
// view_item->setEventId(event_id);
|
||||
// view_item->hide();
|
||||
//
|
||||
// all_scroll_layout_->addWidget(view_item);
|
||||
// QTimer::singleShot(0, this, [view_item, this]() {
|
||||
// view_item->show();
|
||||
// view_item->adjustSize();
|
||||
// setUpdatesEnabled(true);
|
||||
// });
|
||||
//
|
||||
// // if it matches the current room... add it to the current room as well.
|
||||
// if (QString::compare(room_id, current_room_id, Qt::CaseInsensitive) == 0) {
|
||||
// // Add to the 'local' section
|
||||
// TimelineItem *local_view_item = new
|
||||
// TimelineItem(mtx::events::MessageType::Text,
|
||||
// user_id,
|
||||
// body,
|
||||
// true,
|
||||
// room_id,
|
||||
// local_scroll_widget_);
|
||||
// local_view_item->setEventId(event_id);
|
||||
// local_view_item->hide();
|
||||
// local_scroll_layout_->addWidget(local_view_item);
|
||||
//
|
||||
// QTimer::singleShot(0, this, [local_view_item]() {
|
||||
// local_view_item->show();
|
||||
// local_view_item->adjustSize();
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
void
|
||||
|
|
Binary file not shown.
138
src/timeline/DelegateChooser.cpp
Normal file
138
src/timeline/DelegateChooser.cpp
Normal file
|
@ -0,0 +1,138 @@
|
|||
#include "DelegateChooser.h"
|
||||
|
||||
#include "Logging.h"
|
||||
|
||||
// uses private API, which moved between versions
|
||||
#include <QQmlEngine>
|
||||
#include <QtGlobal>
|
||||
|
||||
QQmlComponent *
|
||||
DelegateChoice::delegate() const
|
||||
{
|
||||
return delegate_;
|
||||
}
|
||||
|
||||
void
|
||||
DelegateChoice::setDelegate(QQmlComponent *delegate)
|
||||
{
|
||||
if (delegate != delegate_) {
|
||||
delegate_ = delegate;
|
||||
emit delegateChanged();
|
||||
emit changed();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant
|
||||
DelegateChoice::roleValue() const
|
||||
{
|
||||
return roleValue_;
|
||||
}
|
||||
|
||||
void
|
||||
DelegateChoice::setRoleValue(const QVariant &value)
|
||||
{
|
||||
if (value != roleValue_) {
|
||||
roleValue_ = value;
|
||||
emit roleValueChanged();
|
||||
emit changed();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant
|
||||
DelegateChooser::roleValue() const
|
||||
{
|
||||
return roleValue_;
|
||||
}
|
||||
|
||||
void
|
||||
DelegateChooser::setRoleValue(const QVariant &value)
|
||||
{
|
||||
if (value != roleValue_) {
|
||||
roleValue_ = value;
|
||||
recalcChild();
|
||||
emit roleValueChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QQmlListProperty<DelegateChoice>
|
||||
DelegateChooser::choices()
|
||||
{
|
||||
return QQmlListProperty<DelegateChoice>(this,
|
||||
this,
|
||||
&DelegateChooser::appendChoice,
|
||||
&DelegateChooser::choiceCount,
|
||||
&DelegateChooser::choice,
|
||||
&DelegateChooser::clearChoices);
|
||||
}
|
||||
|
||||
void
|
||||
DelegateChooser::appendChoice(QQmlListProperty<DelegateChoice> *p, DelegateChoice *c)
|
||||
{
|
||||
DelegateChooser *dc = static_cast<DelegateChooser *>(p->object);
|
||||
dc->choices_.append(c);
|
||||
}
|
||||
|
||||
int
|
||||
DelegateChooser::choiceCount(QQmlListProperty<DelegateChoice> *p)
|
||||
{
|
||||
return static_cast<DelegateChooser *>(p->object)->choices_.count();
|
||||
}
|
||||
DelegateChoice *
|
||||
DelegateChooser::choice(QQmlListProperty<DelegateChoice> *p, int index)
|
||||
{
|
||||
return static_cast<DelegateChooser *>(p->object)->choices_.at(index);
|
||||
}
|
||||
void
|
||||
DelegateChooser::clearChoices(QQmlListProperty<DelegateChoice> *p)
|
||||
{
|
||||
static_cast<DelegateChooser *>(p->object)->choices_.clear();
|
||||
}
|
||||
|
||||
void
|
||||
DelegateChooser::recalcChild()
|
||||
{
|
||||
for (const auto choice : choices_) {
|
||||
auto choiceValue = choice->roleValue();
|
||||
if (!roleValue_.isValid() || !choiceValue.isValid() || choiceValue == roleValue_) {
|
||||
if (child) {
|
||||
child->setParentItem(nullptr);
|
||||
child = nullptr;
|
||||
}
|
||||
|
||||
choice->delegate()->create(incubator, QQmlEngine::contextForObject(this));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DelegateChooser::componentComplete()
|
||||
{
|
||||
QQuickItem::componentComplete();
|
||||
recalcChild();
|
||||
}
|
||||
|
||||
void
|
||||
DelegateChooser::DelegateIncubator::statusChanged(QQmlIncubator::Status status)
|
||||
{
|
||||
if (status == QQmlIncubator::Ready) {
|
||||
chooser.child = dynamic_cast<QQuickItem *>(object());
|
||||
if (chooser.child == nullptr) {
|
||||
nhlog::ui()->error("Delegate has to be derived of Item!");
|
||||
return;
|
||||
}
|
||||
|
||||
chooser.child->setParentItem(&chooser);
|
||||
connect(chooser.child, &QQuickItem::heightChanged, &chooser, [this]() {
|
||||
chooser.setHeight(chooser.child->height());
|
||||
});
|
||||
chooser.setHeight(chooser.child->height());
|
||||
QQmlEngine::setObjectOwnership(chooser.child,
|
||||
QQmlEngine::ObjectOwnership::JavaScriptOwnership);
|
||||
|
||||
} else if (status == QQmlIncubator::Error) {
|
||||
for (const auto &e : errors())
|
||||
nhlog::ui()->error("Error instantiating delegate: {}",
|
||||
e.toString().toStdString());
|
||||
}
|
||||
}
|
82
src/timeline/DelegateChooser.h
Normal file
82
src/timeline/DelegateChooser.h
Normal file
|
@ -0,0 +1,82 @@
|
|||
// A DelegateChooser like the one, that was added to Qt5.12 (in labs), but compatible with older Qt
|
||||
// versions see KDE/kquickitemviews see qtdeclarative/qqmldelagatecomponent
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QQmlComponent>
|
||||
#include <QQmlIncubator>
|
||||
#include <QQmlListProperty>
|
||||
#include <QQuickItem>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QVariant>
|
||||
|
||||
class QQmlAdaptorModel;
|
||||
|
||||
class DelegateChoice : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("DefaultProperty", "delegate")
|
||||
|
||||
public:
|
||||
Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
|
||||
Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
|
||||
|
||||
QQmlComponent *delegate() const;
|
||||
void setDelegate(QQmlComponent *delegate);
|
||||
|
||||
QVariant roleValue() const;
|
||||
void setRoleValue(const QVariant &value);
|
||||
|
||||
signals:
|
||||
void delegateChanged();
|
||||
void roleValueChanged();
|
||||
void changed();
|
||||
|
||||
private:
|
||||
QVariant roleValue_;
|
||||
QQmlComponent *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
class DelegateChooser : public QQuickItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("DefaultProperty", "choices")
|
||||
|
||||
public:
|
||||
Q_PROPERTY(QQmlListProperty<DelegateChoice> choices READ choices CONSTANT)
|
||||
Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
|
||||
|
||||
QQmlListProperty<DelegateChoice> choices();
|
||||
|
||||
QVariant roleValue() const;
|
||||
void setRoleValue(const QVariant &value);
|
||||
|
||||
void recalcChild();
|
||||
void componentComplete() override;
|
||||
|
||||
signals:
|
||||
void roleChanged();
|
||||
void roleValueChanged();
|
||||
|
||||
private:
|
||||
struct DelegateIncubator : public QQmlIncubator
|
||||
{
|
||||
DelegateIncubator(DelegateChooser &parent)
|
||||
: QQmlIncubator(QQmlIncubator::AsynchronousIfNested)
|
||||
, chooser(parent)
|
||||
{}
|
||||
void statusChanged(QQmlIncubator::Status status) override;
|
||||
|
||||
DelegateChooser &chooser;
|
||||
};
|
||||
|
||||
QVariant roleValue_;
|
||||
QList<DelegateChoice *> choices_;
|
||||
QQuickItem *child = nullptr;
|
||||
DelegateIncubator incubator{*this};
|
||||
|
||||
static void appendChoice(QQmlListProperty<DelegateChoice> *, DelegateChoice *);
|
||||
static int choiceCount(QQmlListProperty<DelegateChoice> *);
|
||||
static DelegateChoice *choice(QQmlListProperty<DelegateChoice> *, int index);
|
||||
static void clearChoices(QQmlListProperty<DelegateChoice> *);
|
||||
};
|
|
@ -1,960 +0,0 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <functional>
|
||||
|
||||
#include <QContextMenuEvent>
|
||||
#include <QDesktopServices>
|
||||
#include <QFontDatabase>
|
||||
#include <QMenu>
|
||||
#include <QTimer>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "ChatPage.h"
|
||||
#include "Config.h"
|
||||
#include "Logging.h"
|
||||
#include "MainWindow.h"
|
||||
#include "Olm.h"
|
||||
#include "ui/Avatar.h"
|
||||
#include "ui/Painter.h"
|
||||
#include "ui/TextLabel.h"
|
||||
|
||||
#include "timeline/TimelineItem.h"
|
||||
#include "timeline/widgets/AudioItem.h"
|
||||
#include "timeline/widgets/FileItem.h"
|
||||
#include "timeline/widgets/ImageItem.h"
|
||||
#include "timeline/widgets/VideoItem.h"
|
||||
|
||||
#include "dialogs/RawMessage.h"
|
||||
#include "mtx/identifiers.hpp"
|
||||
|
||||
constexpr int MSG_RIGHT_MARGIN = 7;
|
||||
constexpr int MSG_PADDING = 20;
|
||||
|
||||
StatusIndicator::StatusIndicator(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
lockIcon_.addFile(":/icons/icons/ui/lock.png");
|
||||
clockIcon_.addFile(":/icons/icons/ui/clock.png");
|
||||
checkmarkIcon_.addFile(":/icons/icons/ui/checkmark.png");
|
||||
doubleCheckmarkIcon_.addFile(":/icons/icons/ui/double-tick-indicator.png");
|
||||
}
|
||||
|
||||
void
|
||||
StatusIndicator::paintIcon(QPainter &p, QIcon &icon)
|
||||
{
|
||||
auto pixmap = icon.pixmap(width());
|
||||
|
||||
QPainter painter(&pixmap);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
painter.fillRect(pixmap.rect(), p.pen().color());
|
||||
|
||||
QIcon(pixmap).paint(&p, rect(), Qt::AlignCenter, QIcon::Normal);
|
||||
}
|
||||
|
||||
void
|
||||
StatusIndicator::paintEvent(QPaintEvent *)
|
||||
{
|
||||
if (state_ == StatusIndicatorState::Empty)
|
||||
return;
|
||||
|
||||
Painter p(this);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
|
||||
p.setPen(iconColor_);
|
||||
|
||||
switch (state_) {
|
||||
case StatusIndicatorState::Sent: {
|
||||
paintIcon(p, clockIcon_);
|
||||
break;
|
||||
}
|
||||
case StatusIndicatorState::Encrypted:
|
||||
paintIcon(p, lockIcon_);
|
||||
break;
|
||||
case StatusIndicatorState::Received: {
|
||||
paintIcon(p, checkmarkIcon_);
|
||||
break;
|
||||
}
|
||||
case StatusIndicatorState::Read: {
|
||||
paintIcon(p, doubleCheckmarkIcon_);
|
||||
break;
|
||||
}
|
||||
case StatusIndicatorState::Empty:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
StatusIndicator::setState(StatusIndicatorState state)
|
||||
{
|
||||
state_ = state;
|
||||
|
||||
switch (state) {
|
||||
case StatusIndicatorState::Encrypted:
|
||||
setToolTip(tr("Encrypted"));
|
||||
break;
|
||||
case StatusIndicatorState::Received:
|
||||
setToolTip(tr("Delivered"));
|
||||
break;
|
||||
case StatusIndicatorState::Read:
|
||||
setToolTip(tr("Seen"));
|
||||
break;
|
||||
case StatusIndicatorState::Sent:
|
||||
setToolTip(tr("Sent"));
|
||||
break;
|
||||
case StatusIndicatorState::Empty:
|
||||
setToolTip("");
|
||||
break;
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::adjustMessageLayoutForWidget()
|
||||
{
|
||||
messageLayout_->addLayout(widgetLayout_, 1);
|
||||
actionLayout_->addWidget(replyBtn_);
|
||||
actionLayout_->addWidget(contextBtn_);
|
||||
messageLayout_->addLayout(actionLayout_);
|
||||
messageLayout_->addWidget(statusIndicator_);
|
||||
messageLayout_->addWidget(timestamp_);
|
||||
|
||||
actionLayout_->setAlignment(replyBtn_, Qt::AlignTop | Qt::AlignRight);
|
||||
actionLayout_->setAlignment(contextBtn_, Qt::AlignTop | Qt::AlignRight);
|
||||
messageLayout_->setAlignment(statusIndicator_, Qt::AlignTop);
|
||||
messageLayout_->setAlignment(timestamp_, Qt::AlignTop);
|
||||
messageLayout_->setAlignment(actionLayout_, Qt::AlignTop);
|
||||
|
||||
mainLayout_->addLayout(messageLayout_);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::adjustMessageLayout()
|
||||
{
|
||||
messageLayout_->addWidget(body_, 1);
|
||||
actionLayout_->addWidget(replyBtn_);
|
||||
actionLayout_->addWidget(contextBtn_);
|
||||
messageLayout_->addLayout(actionLayout_);
|
||||
messageLayout_->addWidget(statusIndicator_);
|
||||
messageLayout_->addWidget(timestamp_);
|
||||
|
||||
actionLayout_->setAlignment(replyBtn_, Qt::AlignTop | Qt::AlignRight);
|
||||
actionLayout_->setAlignment(contextBtn_, Qt::AlignTop | Qt::AlignRight);
|
||||
messageLayout_->setAlignment(statusIndicator_, Qt::AlignTop);
|
||||
messageLayout_->setAlignment(timestamp_, Qt::AlignTop);
|
||||
messageLayout_->setAlignment(actionLayout_, Qt::AlignTop);
|
||||
|
||||
mainLayout_->addLayout(messageLayout_);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::init()
|
||||
{
|
||||
userAvatar_ = nullptr;
|
||||
timestamp_ = nullptr;
|
||||
userName_ = nullptr;
|
||||
body_ = nullptr;
|
||||
auto buttonSize_ = 32;
|
||||
|
||||
contextMenu_ = new QMenu(this);
|
||||
showReadReceipts_ = new QAction("Read receipts", this);
|
||||
markAsRead_ = new QAction("Mark as read", this);
|
||||
viewRawMessage_ = new QAction("View raw message", this);
|
||||
redactMsg_ = new QAction("Redact message", this);
|
||||
contextMenu_->addAction(showReadReceipts_);
|
||||
contextMenu_->addAction(viewRawMessage_);
|
||||
contextMenu_->addAction(markAsRead_);
|
||||
contextMenu_->addAction(redactMsg_);
|
||||
|
||||
connect(showReadReceipts_, &QAction::triggered, this, [this]() {
|
||||
if (!event_id_.isEmpty())
|
||||
MainWindow::instance()->openReadReceiptsDialog(event_id_);
|
||||
});
|
||||
|
||||
connect(this, &TimelineItem::eventRedacted, this, [this](const QString &event_id) {
|
||||
emit ChatPage::instance()->removeTimelineEvent(room_id_, event_id);
|
||||
});
|
||||
connect(this, &TimelineItem::redactionFailed, this, [](const QString &msg) {
|
||||
emit ChatPage::instance()->showNotification(msg);
|
||||
});
|
||||
connect(redactMsg_, &QAction::triggered, this, [this]() {
|
||||
if (!event_id_.isEmpty())
|
||||
http::client()->redact_event(
|
||||
room_id_.toStdString(),
|
||||
event_id_.toStdString(),
|
||||
[this](const mtx::responses::EventId &, mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
emit redactionFailed(tr("Message redaction failed: %1")
|
||||
.arg(QString::fromStdString(
|
||||
err->matrix_error.error)));
|
||||
return;
|
||||
}
|
||||
|
||||
emit eventRedacted(event_id_);
|
||||
});
|
||||
});
|
||||
connect(
|
||||
ChatPage::instance(), &ChatPage::themeChanged, this, &TimelineItem::refreshAuthorColor);
|
||||
connect(markAsRead_, &QAction::triggered, this, &TimelineItem::sendReadReceipt);
|
||||
connect(viewRawMessage_, &QAction::triggered, this, &TimelineItem::openRawMessageViewer);
|
||||
|
||||
colorGenerating_ = new QFutureWatcher<QString>(this);
|
||||
connect(colorGenerating_,
|
||||
&QFutureWatcher<QString>::finished,
|
||||
this,
|
||||
&TimelineItem::finishedGeneratingColor);
|
||||
|
||||
topLayout_ = new QHBoxLayout(this);
|
||||
mainLayout_ = new QVBoxLayout;
|
||||
messageLayout_ = new QHBoxLayout;
|
||||
actionLayout_ = new QHBoxLayout;
|
||||
messageLayout_->setContentsMargins(0, 0, MSG_RIGHT_MARGIN, 0);
|
||||
messageLayout_->setSpacing(MSG_PADDING);
|
||||
|
||||
actionLayout_->setContentsMargins(13, 1, 13, 0);
|
||||
actionLayout_->setSpacing(0);
|
||||
|
||||
topLayout_->setContentsMargins(
|
||||
conf::timeline::msgLeftMargin, conf::timeline::msgTopMargin, 0, 0);
|
||||
topLayout_->setSpacing(0);
|
||||
topLayout_->addLayout(mainLayout_);
|
||||
|
||||
mainLayout_->setContentsMargins(conf::timeline::headerLeftMargin, 0, 0, 0);
|
||||
mainLayout_->setSpacing(0);
|
||||
|
||||
replyBtn_ = new FlatButton(this);
|
||||
replyBtn_->setToolTip(tr("Reply"));
|
||||
replyBtn_->setFixedSize(buttonSize_, buttonSize_);
|
||||
replyBtn_->setCornerRadius(buttonSize_ / 2);
|
||||
|
||||
QIcon reply_icon;
|
||||
reply_icon.addFile(":/icons/icons/ui/mail-reply.png");
|
||||
replyBtn_->setIcon(reply_icon);
|
||||
replyBtn_->setIconSize(QSize(buttonSize_ / 2, buttonSize_ / 2));
|
||||
connect(replyBtn_, &FlatButton::clicked, this, &TimelineItem::replyAction);
|
||||
|
||||
contextBtn_ = new FlatButton(this);
|
||||
contextBtn_->setToolTip(tr("Options"));
|
||||
contextBtn_->setFixedSize(buttonSize_, buttonSize_);
|
||||
contextBtn_->setCornerRadius(buttonSize_ / 2);
|
||||
|
||||
QIcon context_icon;
|
||||
context_icon.addFile(":/icons/icons/ui/vertical-ellipsis.png");
|
||||
contextBtn_->setIcon(context_icon);
|
||||
contextBtn_->setIconSize(QSize(buttonSize_ / 2, buttonSize_ / 2));
|
||||
contextBtn_->setMenu(contextMenu_);
|
||||
|
||||
timestampFont_.setPointSizeF(timestampFont_.pointSizeF() * 0.9);
|
||||
timestampFont_.setFamily("Monospace");
|
||||
timestampFont_.setStyleHint(QFont::Monospace);
|
||||
|
||||
QFontMetrics tsFm(timestampFont_);
|
||||
|
||||
statusIndicator_ = new StatusIndicator(this);
|
||||
statusIndicator_->setFixedWidth(tsFm.height() - tsFm.leading());
|
||||
statusIndicator_->setFixedHeight(tsFm.height() - tsFm.leading());
|
||||
|
||||
parentWidget()->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
|
||||
}
|
||||
|
||||
/*
|
||||
* For messages created locally.
|
||||
*/
|
||||
TimelineItem::TimelineItem(mtx::events::MessageType ty,
|
||||
const QString &userid,
|
||||
QString body,
|
||||
bool withSender,
|
||||
const QString &room_id,
|
||||
QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, message_type_(ty)
|
||||
, room_id_{room_id}
|
||||
{
|
||||
init();
|
||||
addReplyAction();
|
||||
|
||||
auto displayName = Cache::displayName(room_id_, userid);
|
||||
auto timestamp = QDateTime::currentDateTime();
|
||||
|
||||
// Generate the html body to be rendered.
|
||||
auto formatted_body = utils::markdownToHtml(body);
|
||||
|
||||
// Escape html if the input is not formatted.
|
||||
if (formatted_body == body.trimmed().toHtmlEscaped())
|
||||
formatted_body = body.toHtmlEscaped();
|
||||
|
||||
QString emptyEventId;
|
||||
|
||||
if (ty == mtx::events::MessageType::Emote) {
|
||||
formatted_body = QString("<em>%1</em>").arg(formatted_body);
|
||||
descriptionMsg_ = {emptyEventId,
|
||||
"",
|
||||
userid,
|
||||
QString("* %1 %2").arg(displayName).arg(body),
|
||||
utils::descriptiveTime(timestamp),
|
||||
timestamp};
|
||||
} else {
|
||||
descriptionMsg_ = {emptyEventId,
|
||||
"You: ",
|
||||
userid,
|
||||
body,
|
||||
utils::descriptiveTime(timestamp),
|
||||
timestamp};
|
||||
}
|
||||
|
||||
formatted_body = utils::linkifyMessage(formatted_body);
|
||||
formatted_body.replace("mx-reply", "div");
|
||||
|
||||
generateTimestamp(timestamp);
|
||||
|
||||
if (withSender) {
|
||||
generateBody(userid, displayName, formatted_body);
|
||||
setupAvatarLayout(displayName);
|
||||
|
||||
setUserAvatar(userid);
|
||||
} else {
|
||||
generateBody(formatted_body);
|
||||
setupSimpleLayout();
|
||||
}
|
||||
|
||||
adjustMessageLayout();
|
||||
}
|
||||
|
||||
TimelineItem::TimelineItem(ImageItem *image,
|
||||
const QString &userid,
|
||||
bool withSender,
|
||||
const QString &room_id,
|
||||
QWidget *parent)
|
||||
: QWidget{parent}
|
||||
, message_type_(mtx::events::MessageType::Image)
|
||||
, room_id_{room_id}
|
||||
{
|
||||
init();
|
||||
|
||||
setupLocalWidgetLayout<ImageItem>(image, userid, withSender);
|
||||
|
||||
addSaveImageAction(image);
|
||||
}
|
||||
|
||||
TimelineItem::TimelineItem(FileItem *file,
|
||||
const QString &userid,
|
||||
bool withSender,
|
||||
const QString &room_id,
|
||||
QWidget *parent)
|
||||
: QWidget{parent}
|
||||
, message_type_(mtx::events::MessageType::File)
|
||||
, room_id_{room_id}
|
||||
{
|
||||
init();
|
||||
|
||||
setupLocalWidgetLayout<FileItem>(file, userid, withSender);
|
||||
}
|
||||
|
||||
TimelineItem::TimelineItem(AudioItem *audio,
|
||||
const QString &userid,
|
||||
bool withSender,
|
||||
const QString &room_id,
|
||||
QWidget *parent)
|
||||
: QWidget{parent}
|
||||
, message_type_(mtx::events::MessageType::Audio)
|
||||
, room_id_{room_id}
|
||||
{
|
||||
init();
|
||||
|
||||
setupLocalWidgetLayout<AudioItem>(audio, userid, withSender);
|
||||
}
|
||||
|
||||
TimelineItem::TimelineItem(VideoItem *video,
|
||||
const QString &userid,
|
||||
bool withSender,
|
||||
const QString &room_id,
|
||||
QWidget *parent)
|
||||
: QWidget{parent}
|
||||
, message_type_(mtx::events::MessageType::Video)
|
||||
, room_id_{room_id}
|
||||
{
|
||||
init();
|
||||
|
||||
setupLocalWidgetLayout<VideoItem>(video, userid, withSender);
|
||||
}
|
||||
|
||||
TimelineItem::TimelineItem(ImageItem *image,
|
||||
const mtx::events::RoomEvent<mtx::events::msg::Image> &event,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, message_type_(mtx::events::MessageType::Image)
|
||||
, room_id_{room_id}
|
||||
{
|
||||
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Image>, ImageItem>(
|
||||
image, event, with_sender);
|
||||
|
||||
markOwnMessagesAsReceived(event.sender);
|
||||
|
||||
addSaveImageAction(image);
|
||||
}
|
||||
|
||||
TimelineItem::TimelineItem(StickerItem *image,
|
||||
const mtx::events::Sticker &event,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, room_id_{room_id}
|
||||
{
|
||||
setupWidgetLayout<mtx::events::Sticker, StickerItem>(image, event, with_sender);
|
||||
|
||||
markOwnMessagesAsReceived(event.sender);
|
||||
|
||||
addSaveImageAction(image);
|
||||
}
|
||||
|
||||
TimelineItem::TimelineItem(FileItem *file,
|
||||
const mtx::events::RoomEvent<mtx::events::msg::File> &event,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, message_type_(mtx::events::MessageType::File)
|
||||
, room_id_{room_id}
|
||||
{
|
||||
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::File>, FileItem>(
|
||||
file, event, with_sender);
|
||||
|
||||
markOwnMessagesAsReceived(event.sender);
|
||||
}
|
||||
|
||||
TimelineItem::TimelineItem(AudioItem *audio,
|
||||
const mtx::events::RoomEvent<mtx::events::msg::Audio> &event,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, message_type_(mtx::events::MessageType::Audio)
|
||||
, room_id_{room_id}
|
||||
{
|
||||
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Audio>, AudioItem>(
|
||||
audio, event, with_sender);
|
||||
|
||||
markOwnMessagesAsReceived(event.sender);
|
||||
}
|
||||
|
||||
TimelineItem::TimelineItem(VideoItem *video,
|
||||
const mtx::events::RoomEvent<mtx::events::msg::Video> &event,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, message_type_(mtx::events::MessageType::Video)
|
||||
, room_id_{room_id}
|
||||
{
|
||||
setupWidgetLayout<mtx::events::RoomEvent<mtx::events::msg::Video>, VideoItem>(
|
||||
video, event, with_sender);
|
||||
|
||||
markOwnMessagesAsReceived(event.sender);
|
||||
}
|
||||
|
||||
/*
|
||||
* Used to display remote notice messages.
|
||||
*/
|
||||
TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice> &event,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, message_type_(mtx::events::MessageType::Notice)
|
||||
, room_id_{room_id}
|
||||
{
|
||||
init();
|
||||
addReplyAction();
|
||||
|
||||
markOwnMessagesAsReceived(event.sender);
|
||||
|
||||
event_id_ = QString::fromStdString(event.event_id);
|
||||
const auto sender = QString::fromStdString(event.sender);
|
||||
const auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
|
||||
|
||||
auto formatted_body = utils::linkifyMessage(utils::getMessageBody(event).trimmed());
|
||||
auto body = QString::fromStdString(event.content.body).trimmed().toHtmlEscaped();
|
||||
|
||||
descriptionMsg_ = {event_id_,
|
||||
Cache::displayName(room_id_, sender),
|
||||
sender,
|
||||
" sent a notification",
|
||||
utils::descriptiveTime(timestamp),
|
||||
timestamp};
|
||||
|
||||
generateTimestamp(timestamp);
|
||||
|
||||
if (with_sender) {
|
||||
auto displayName = Cache::displayName(room_id_, sender);
|
||||
|
||||
generateBody(sender, displayName, formatted_body);
|
||||
setupAvatarLayout(displayName);
|
||||
|
||||
setUserAvatar(sender);
|
||||
} else {
|
||||
generateBody(formatted_body);
|
||||
setupSimpleLayout();
|
||||
}
|
||||
|
||||
adjustMessageLayout();
|
||||
}
|
||||
|
||||
/*
|
||||
* Used to display remote emote messages.
|
||||
*/
|
||||
TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote> &event,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, message_type_(mtx::events::MessageType::Emote)
|
||||
, room_id_{room_id}
|
||||
{
|
||||
init();
|
||||
addReplyAction();
|
||||
|
||||
markOwnMessagesAsReceived(event.sender);
|
||||
|
||||
event_id_ = QString::fromStdString(event.event_id);
|
||||
const auto sender = QString::fromStdString(event.sender);
|
||||
|
||||
auto formatted_body = utils::linkifyMessage(utils::getMessageBody(event).trimmed());
|
||||
auto body = QString::fromStdString(event.content.body).trimmed().toHtmlEscaped();
|
||||
|
||||
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
|
||||
auto displayName = Cache::displayName(room_id_, sender);
|
||||
formatted_body = QString("<em>%1</em>").arg(formatted_body);
|
||||
|
||||
descriptionMsg_ = {event_id_,
|
||||
"",
|
||||
sender,
|
||||
QString("* %1 %2").arg(displayName).arg(body),
|
||||
utils::descriptiveTime(timestamp),
|
||||
timestamp};
|
||||
|
||||
generateTimestamp(timestamp);
|
||||
|
||||
if (with_sender) {
|
||||
generateBody(sender, displayName, formatted_body);
|
||||
setupAvatarLayout(displayName);
|
||||
|
||||
setUserAvatar(sender);
|
||||
} else {
|
||||
generateBody(formatted_body);
|
||||
setupSimpleLayout();
|
||||
}
|
||||
|
||||
adjustMessageLayout();
|
||||
}
|
||||
|
||||
/*
|
||||
* Used to display remote text messages.
|
||||
*/
|
||||
TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text> &event,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, message_type_(mtx::events::MessageType::Text)
|
||||
, room_id_{room_id}
|
||||
{
|
||||
init();
|
||||
addReplyAction();
|
||||
|
||||
markOwnMessagesAsReceived(event.sender);
|
||||
|
||||
event_id_ = QString::fromStdString(event.event_id);
|
||||
const auto sender = QString::fromStdString(event.sender);
|
||||
|
||||
auto formatted_body = utils::linkifyMessage(utils::getMessageBody(event).trimmed());
|
||||
auto body = QString::fromStdString(event.content.body).trimmed().toHtmlEscaped();
|
||||
|
||||
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
|
||||
auto displayName = Cache::displayName(room_id_, sender);
|
||||
|
||||
QSettings settings;
|
||||
descriptionMsg_ = {event_id_,
|
||||
sender == settings.value("auth/user_id") ? "You" : displayName,
|
||||
sender,
|
||||
QString(": %1").arg(body),
|
||||
utils::descriptiveTime(timestamp),
|
||||
timestamp};
|
||||
|
||||
generateTimestamp(timestamp);
|
||||
|
||||
if (with_sender) {
|
||||
generateBody(sender, displayName, formatted_body);
|
||||
setupAvatarLayout(displayName);
|
||||
|
||||
setUserAvatar(sender);
|
||||
} else {
|
||||
generateBody(formatted_body);
|
||||
setupSimpleLayout();
|
||||
}
|
||||
|
||||
adjustMessageLayout();
|
||||
}
|
||||
|
||||
TimelineItem::~TimelineItem()
|
||||
{
|
||||
colorGenerating_->cancel();
|
||||
colorGenerating_->waitForFinished();
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::markSent()
|
||||
{
|
||||
statusIndicator_->setState(StatusIndicatorState::Sent);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::markOwnMessagesAsReceived(const std::string &sender)
|
||||
{
|
||||
QSettings settings;
|
||||
if (sender == settings.value("auth/user_id").toString().toStdString())
|
||||
statusIndicator_->setState(StatusIndicatorState::Received);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::markRead()
|
||||
{
|
||||
if (statusIndicator_->state() != StatusIndicatorState::Encrypted)
|
||||
statusIndicator_->setState(StatusIndicatorState::Read);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::markReceived(bool isEncrypted)
|
||||
{
|
||||
isReceived_ = true;
|
||||
|
||||
if (isEncrypted)
|
||||
statusIndicator_->setState(StatusIndicatorState::Encrypted);
|
||||
else
|
||||
statusIndicator_->setState(StatusIndicatorState::Received);
|
||||
|
||||
sendReadReceipt();
|
||||
}
|
||||
|
||||
// Only the body is displayed.
|
||||
void
|
||||
TimelineItem::generateBody(const QString &body)
|
||||
{
|
||||
body_ = new TextLabel(utils::replaceEmoji(body), this);
|
||||
body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
|
||||
|
||||
connect(body_, &TextLabel::userProfileTriggered, this, [](const QString &user_id) {
|
||||
MainWindow::instance()->openUserProfile(user_id,
|
||||
ChatPage::instance()->currentRoom());
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::refreshAuthorColor()
|
||||
{
|
||||
// Cancel and wait if we are already generating the color.
|
||||
if (colorGenerating_->isRunning()) {
|
||||
colorGenerating_->cancel();
|
||||
colorGenerating_->waitForFinished();
|
||||
}
|
||||
if (userName_) {
|
||||
// generate user's unique color.
|
||||
std::function<QString()> generate = [this]() {
|
||||
QString userColor = utils::generateContrastingHexColor(
|
||||
userName_->toolTip(), backgroundColor().name());
|
||||
return userColor;
|
||||
};
|
||||
|
||||
QString userColor = Cache::userColor(userName_->toolTip());
|
||||
|
||||
// If the color is empty, then generate it asynchronously
|
||||
if (userColor.isEmpty()) {
|
||||
colorGenerating_->setFuture(QtConcurrent::run(generate));
|
||||
} else {
|
||||
userName_->setStyleSheet("QLabel { color : " + userColor + "; }");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::finishedGeneratingColor()
|
||||
{
|
||||
nhlog::ui()->debug("finishedGeneratingColor for: {}", userName_->toolTip().toStdString());
|
||||
QString userColor = colorGenerating_->result();
|
||||
|
||||
if (!userColor.isEmpty()) {
|
||||
// another TimelineItem might have inserted in the meantime.
|
||||
if (Cache::userColor(userName_->toolTip()).isEmpty()) {
|
||||
Cache::insertUserColor(userName_->toolTip(), userColor);
|
||||
}
|
||||
userName_->setStyleSheet("QLabel { color : " + userColor + "; }");
|
||||
}
|
||||
}
|
||||
// The username/timestamp is displayed along with the message body.
|
||||
void
|
||||
TimelineItem::generateBody(const QString &user_id, const QString &displayname, const QString &body)
|
||||
{
|
||||
generateUserName(user_id, displayname);
|
||||
generateBody(body);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::generateUserName(const QString &user_id, const QString &displayname)
|
||||
{
|
||||
auto sender = displayname;
|
||||
|
||||
if (displayname.startsWith("@")) {
|
||||
// TODO: Fix this by using a UserId type.
|
||||
if (displayname.split(":")[0].split("@").size() > 1)
|
||||
sender = displayname.split(":")[0].split("@")[1];
|
||||
}
|
||||
|
||||
QFont usernameFont;
|
||||
usernameFont.setPointSizeF(usernameFont.pointSizeF() * 1.1);
|
||||
usernameFont.setWeight(QFont::Medium);
|
||||
|
||||
QFontMetrics fm(usernameFont);
|
||||
|
||||
userName_ = new QLabel(this);
|
||||
userName_->setFont(usernameFont);
|
||||
userName_->setText(utils::replaceEmoji(fm.elidedText(sender, Qt::ElideRight, 500)));
|
||||
userName_->setToolTip(user_id);
|
||||
userName_->setToolTipDuration(1500);
|
||||
userName_->setAttribute(Qt::WA_Hover);
|
||||
userName_->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
|
||||
// width deprecated in 5.13:
|
||||
userName_->setFixedWidth(QFontMetrics(userName_->font()).width(userName_->text()));
|
||||
#else
|
||||
userName_->setFixedWidth(
|
||||
QFontMetrics(userName_->font()).horizontalAdvance(userName_->text()));
|
||||
#endif
|
||||
// Set the user color asynchronously if it hasn't been generated yet,
|
||||
// otherwise this will just set it.
|
||||
refreshAuthorColor();
|
||||
|
||||
auto filter = new UserProfileFilter(user_id, userName_);
|
||||
userName_->installEventFilter(filter);
|
||||
userName_->setCursor(Qt::PointingHandCursor);
|
||||
|
||||
connect(filter, &UserProfileFilter::hoverOn, this, [this]() {
|
||||
QFont f = userName_->font();
|
||||
f.setUnderline(true);
|
||||
userName_->setFont(f);
|
||||
});
|
||||
|
||||
connect(filter, &UserProfileFilter::hoverOff, this, [this]() {
|
||||
QFont f = userName_->font();
|
||||
f.setUnderline(false);
|
||||
userName_->setFont(f);
|
||||
});
|
||||
|
||||
connect(filter, &UserProfileFilter::clicked, this, [this, user_id]() {
|
||||
MainWindow::instance()->openUserProfile(user_id, room_id_);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::generateTimestamp(const QDateTime &time)
|
||||
{
|
||||
timestamp_ = new QLabel(this);
|
||||
timestamp_->setFont(timestampFont_);
|
||||
timestamp_->setText(
|
||||
QString("<span style=\"color: #999\"> %1 </span>").arg(time.toString("HH:mm")));
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::setupAvatarLayout(const QString &userName)
|
||||
{
|
||||
topLayout_->setContentsMargins(
|
||||
conf::timeline::msgLeftMargin, conf::timeline::msgAvatarTopMargin, 0, 0);
|
||||
|
||||
QFont f;
|
||||
f.setPointSizeF(f.pointSizeF());
|
||||
|
||||
userAvatar_ = new Avatar(this, QFontMetrics(f).height() * 2);
|
||||
userAvatar_->setLetter(QChar(userName[0]).toUpper());
|
||||
|
||||
// TODO: The provided user name should be a UserId class
|
||||
if (userName[0] == '@' && userName.size() > 1)
|
||||
userAvatar_->setLetter(QChar(userName[1]).toUpper());
|
||||
|
||||
topLayout_->insertWidget(0, userAvatar_);
|
||||
topLayout_->setAlignment(userAvatar_, Qt::AlignTop | Qt::AlignLeft);
|
||||
|
||||
if (userName_)
|
||||
mainLayout_->insertWidget(0, userName_, Qt::AlignTop | Qt::AlignLeft);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::setupSimpleLayout()
|
||||
{
|
||||
QFont f;
|
||||
f.setPointSizeF(f.pointSizeF());
|
||||
|
||||
topLayout_->setContentsMargins(conf::timeline::msgLeftMargin +
|
||||
QFontMetrics(f).height() * 2 + 2,
|
||||
conf::timeline::msgTopMargin,
|
||||
0,
|
||||
0);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::setUserAvatar(const QString &userid)
|
||||
{
|
||||
if (userAvatar_ == nullptr)
|
||||
return;
|
||||
|
||||
userAvatar_->setImage(room_id_, userid);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::contextMenuEvent(QContextMenuEvent *event)
|
||||
{
|
||||
if (contextMenu_)
|
||||
contextMenu_->exec(event->globalPos());
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::addSaveImageAction(ImageItem *image)
|
||||
{
|
||||
if (contextMenu_) {
|
||||
auto saveImage = new QAction("Save image", this);
|
||||
contextMenu_->addAction(saveImage);
|
||||
|
||||
connect(saveImage, &QAction::triggered, image, &ImageItem::saveAs);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::addReplyAction()
|
||||
{
|
||||
if (contextMenu_) {
|
||||
auto replyAction = new QAction("Reply", this);
|
||||
contextMenu_->addAction(replyAction);
|
||||
|
||||
connect(replyAction, &QAction::triggered, this, &TimelineItem::replyAction);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::replyAction()
|
||||
{
|
||||
if (!body_)
|
||||
return;
|
||||
|
||||
RelatedInfo related;
|
||||
related.type = message_type_;
|
||||
related.quoted_body = body_->toPlainText();
|
||||
related.quoted_user = descriptionMsg_.userid;
|
||||
related.related_event = eventId().toStdString();
|
||||
related.room = room_id_;
|
||||
|
||||
emit ChatPage::instance()->messageReply(related);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::addKeyRequestAction()
|
||||
{
|
||||
if (contextMenu_) {
|
||||
auto requestKeys = new QAction("Request encryption keys", this);
|
||||
contextMenu_->addAction(requestKeys);
|
||||
|
||||
connect(requestKeys, &QAction::triggered, this, [this]() {
|
||||
olm::request_keys(room_id_.toStdString(), event_id_.toStdString());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::addAvatar()
|
||||
{
|
||||
if (userAvatar_)
|
||||
return;
|
||||
|
||||
// TODO: should be replaced with the proper event struct.
|
||||
auto userid = descriptionMsg_.userid;
|
||||
auto displayName = Cache::displayName(room_id_, userid);
|
||||
|
||||
generateUserName(userid, displayName);
|
||||
|
||||
setupAvatarLayout(displayName);
|
||||
|
||||
setUserAvatar(userid);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::sendReadReceipt() const
|
||||
{
|
||||
if (!event_id_.isEmpty())
|
||||
http::client()->read_event(room_id_.toStdString(),
|
||||
event_id_.toStdString(),
|
||||
[this](mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn(
|
||||
"failed to read_event ({}, {})",
|
||||
room_id_.toStdString(),
|
||||
event_id_.toStdString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimelineItem::openRawMessageViewer() const
|
||||
{
|
||||
const auto event_id = event_id_.toStdString();
|
||||
const auto room_id = room_id_.toStdString();
|
||||
|
||||
auto proxy = std::make_shared<EventProxy>();
|
||||
connect(proxy.get(), &EventProxy::eventRetrieved, this, [](const nlohmann::json &obj) {
|
||||
auto dialog = new dialogs::RawMessage{QString::fromStdString(obj.dump(4))};
|
||||
Q_UNUSED(dialog);
|
||||
});
|
||||
|
||||
http::client()->get_event(
|
||||
room_id,
|
||||
event_id,
|
||||
[event_id, room_id, proxy = std::move(proxy)](
|
||||
const mtx::events::collections::TimelineEvents &res, mtx::http::RequestErr err) {
|
||||
using namespace mtx::events;
|
||||
|
||||
if (err) {
|
||||
nhlog::net()->warn(
|
||||
"failed to retrieve event {} from {}", event_id, room_id);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
emit proxy->eventRetrieved(utils::serialize_event(res));
|
||||
} catch (const nlohmann::json::exception &e) {
|
||||
nhlog::net()->warn(
|
||||
"failed to serialize event ({}, {})", room_id, event_id);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,389 +0,0 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDateTime>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLayout>
|
||||
#include <QPainter>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "mtx/events.hpp"
|
||||
|
||||
#include "AvatarProvider.h"
|
||||
#include "RoomInfoListItem.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include "Cache.h"
|
||||
#include "MatrixClient.h"
|
||||
|
||||
#include "ui/FlatButton.h"
|
||||
|
||||
class ImageItem;
|
||||
class StickerItem;
|
||||
class AudioItem;
|
||||
class VideoItem;
|
||||
class FileItem;
|
||||
class Avatar;
|
||||
class TextLabel;
|
||||
|
||||
enum class StatusIndicatorState
|
||||
{
|
||||
//! The encrypted message was received by the server.
|
||||
Encrypted,
|
||||
//! The plaintext message was received by the server.
|
||||
Received,
|
||||
//! At least one of the participants has read the message.
|
||||
Read,
|
||||
//! The client sent the message. Not yet received.
|
||||
Sent,
|
||||
//! When the message is loaded from cache or backfill.
|
||||
Empty,
|
||||
};
|
||||
|
||||
//!
|
||||
//! Used to notify the user about the status of a message.
|
||||
//!
|
||||
class StatusIndicator : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StatusIndicator(QWidget *parent);
|
||||
void setState(StatusIndicatorState state);
|
||||
StatusIndicatorState state() const { return state_; }
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
void paintIcon(QPainter &p, QIcon &icon);
|
||||
|
||||
QIcon lockIcon_;
|
||||
QIcon clockIcon_;
|
||||
QIcon checkmarkIcon_;
|
||||
QIcon doubleCheckmarkIcon_;
|
||||
|
||||
QColor iconColor_ = QColor("#999");
|
||||
|
||||
StatusIndicatorState state_ = StatusIndicatorState::Empty;
|
||||
|
||||
static constexpr int MaxWidth = 24;
|
||||
};
|
||||
|
||||
class EventProxy : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void eventRetrieved(const nlohmann::json &);
|
||||
};
|
||||
|
||||
class UserProfileFilter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit UserProfileFilter(const QString &user_id, QLabel *parent)
|
||||
: QObject(parent)
|
||||
, user_id_{user_id}
|
||||
{}
|
||||
|
||||
signals:
|
||||
void hoverOff();
|
||||
void hoverOn();
|
||||
void clicked();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::MouseButtonRelease) {
|
||||
emit clicked();
|
||||
return true;
|
||||
} else if (event->type() == QEvent::HoverLeave) {
|
||||
emit hoverOff();
|
||||
return true;
|
||||
} else if (event->type() == QEvent::HoverEnter) {
|
||||
emit hoverOn();
|
||||
return true;
|
||||
}
|
||||
|
||||
return QObject::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
private:
|
||||
QString user_id_;
|
||||
};
|
||||
|
||||
class TimelineItem : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor)
|
||||
|
||||
public:
|
||||
TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice> &e,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent = 0);
|
||||
TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text> &e,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent = 0);
|
||||
TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote> &e,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent = 0);
|
||||
|
||||
// For local messages.
|
||||
// m.text & m.emote
|
||||
TimelineItem(mtx::events::MessageType ty,
|
||||
const QString &userid,
|
||||
QString body,
|
||||
bool withSender,
|
||||
const QString &room_id,
|
||||
QWidget *parent = 0);
|
||||
// m.image
|
||||
TimelineItem(ImageItem *item,
|
||||
const QString &userid,
|
||||
bool withSender,
|
||||
const QString &room_id,
|
||||
QWidget *parent = 0);
|
||||
TimelineItem(FileItem *item,
|
||||
const QString &userid,
|
||||
bool withSender,
|
||||
const QString &room_id,
|
||||
QWidget *parent = 0);
|
||||
TimelineItem(AudioItem *item,
|
||||
const QString &userid,
|
||||
bool withSender,
|
||||
const QString &room_id,
|
||||
QWidget *parent = 0);
|
||||
TimelineItem(VideoItem *item,
|
||||
const QString &userid,
|
||||
bool withSender,
|
||||
const QString &room_id,
|
||||
QWidget *parent = 0);
|
||||
|
||||
TimelineItem(ImageItem *img,
|
||||
const mtx::events::RoomEvent<mtx::events::msg::Image> &e,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent);
|
||||
TimelineItem(StickerItem *img,
|
||||
const mtx::events::Sticker &e,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent);
|
||||
TimelineItem(FileItem *file,
|
||||
const mtx::events::RoomEvent<mtx::events::msg::File> &e,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent);
|
||||
TimelineItem(AudioItem *audio,
|
||||
const mtx::events::RoomEvent<mtx::events::msg::Audio> &e,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent);
|
||||
TimelineItem(VideoItem *video,
|
||||
const mtx::events::RoomEvent<mtx::events::msg::Video> &e,
|
||||
bool with_sender,
|
||||
const QString &room_id,
|
||||
QWidget *parent);
|
||||
|
||||
~TimelineItem();
|
||||
|
||||
void setBackgroundColor(const QColor &color) { backgroundColor_ = color; }
|
||||
QColor backgroundColor() const { return backgroundColor_; }
|
||||
|
||||
void setUserAvatar(const QString &userid);
|
||||
DescInfo descriptionMessage() const { return descriptionMsg_; }
|
||||
QString eventId() const { return event_id_; }
|
||||
void setEventId(const QString &event_id) { event_id_ = event_id; }
|
||||
void markReceived(bool isEncrypted);
|
||||
void markRead();
|
||||
void markSent();
|
||||
bool isReceived() { return isReceived_; };
|
||||
void setRoomId(QString room_id) { room_id_ = room_id; }
|
||||
void sendReadReceipt() const;
|
||||
void openRawMessageViewer() const;
|
||||
void replyAction();
|
||||
|
||||
//! Add a user avatar for this event.
|
||||
void addAvatar();
|
||||
void addKeyRequestAction();
|
||||
|
||||
signals:
|
||||
void eventRedacted(const QString &event_id);
|
||||
void redactionFailed(const QString &msg);
|
||||
|
||||
public slots:
|
||||
void refreshAuthorColor();
|
||||
void finishedGeneratingColor();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void contextMenuEvent(QContextMenuEvent *event) override;
|
||||
|
||||
private:
|
||||
//! If we are the sender of the message the event wil be marked as received by the server.
|
||||
void markOwnMessagesAsReceived(const std::string &sender);
|
||||
void init();
|
||||
//! Add a context menu option to save the image of the timeline item.
|
||||
void addSaveImageAction(ImageItem *image);
|
||||
//! Add the reply action in the context menu for widgets that support it.
|
||||
void addReplyAction();
|
||||
|
||||
template<class Widget>
|
||||
void setupLocalWidgetLayout(Widget *widget, const QString &userid, bool withSender);
|
||||
|
||||
template<class Event, class Widget>
|
||||
void setupWidgetLayout(Widget *widget, const Event &event, bool withSender);
|
||||
|
||||
void generateBody(const QString &body);
|
||||
void generateBody(const QString &user_id, const QString &displayname, const QString &body);
|
||||
void generateTimestamp(const QDateTime &time);
|
||||
void generateUserName(const QString &userid, const QString &displayname);
|
||||
|
||||
void setupAvatarLayout(const QString &userName);
|
||||
void setupSimpleLayout();
|
||||
|
||||
void adjustMessageLayout();
|
||||
void adjustMessageLayoutForWidget();
|
||||
|
||||
//! Whether or not the event associated with the widget
|
||||
//! has been acknowledged by the server.
|
||||
bool isReceived_ = false;
|
||||
|
||||
QFutureWatcher<QString> *colorGenerating_;
|
||||
|
||||
QString event_id_;
|
||||
mtx::events::MessageType message_type_ = mtx::events::MessageType::Unknown;
|
||||
QString room_id_;
|
||||
|
||||
DescInfo descriptionMsg_;
|
||||
|
||||
QMenu *contextMenu_;
|
||||
QAction *showReadReceipts_;
|
||||
QAction *markAsRead_;
|
||||
QAction *redactMsg_;
|
||||
QAction *viewRawMessage_;
|
||||
QAction *replyMsg_;
|
||||
|
||||
QHBoxLayout *topLayout_ = nullptr;
|
||||
QHBoxLayout *messageLayout_ = nullptr;
|
||||
QHBoxLayout *actionLayout_ = nullptr;
|
||||
QVBoxLayout *mainLayout_ = nullptr;
|
||||
QHBoxLayout *widgetLayout_ = nullptr;
|
||||
|
||||
Avatar *userAvatar_;
|
||||
|
||||
QFont timestampFont_;
|
||||
|
||||
StatusIndicator *statusIndicator_;
|
||||
|
||||
QLabel *timestamp_;
|
||||
QLabel *userName_;
|
||||
TextLabel *body_;
|
||||
|
||||
QColor backgroundColor_;
|
||||
|
||||
FlatButton *replyBtn_;
|
||||
FlatButton *contextBtn_;
|
||||
};
|
||||
|
||||
template<class Widget>
|
||||
void
|
||||
TimelineItem::setupLocalWidgetLayout(Widget *widget, const QString &userid, bool withSender)
|
||||
{
|
||||
auto displayName = Cache::displayName(room_id_, userid);
|
||||
auto timestamp = QDateTime::currentDateTime();
|
||||
|
||||
descriptionMsg_ = {"", // No event_id up until this point.
|
||||
"You",
|
||||
userid,
|
||||
QString(" %1").arg(utils::messageDescription<Widget>()),
|
||||
utils::descriptiveTime(timestamp),
|
||||
timestamp};
|
||||
|
||||
generateTimestamp(timestamp);
|
||||
|
||||
widgetLayout_ = new QHBoxLayout;
|
||||
widgetLayout_->setContentsMargins(0, 2, 0, 2);
|
||||
widgetLayout_->addWidget(widget);
|
||||
widgetLayout_->addStretch(1);
|
||||
|
||||
if (withSender) {
|
||||
generateBody(userid, displayName, "");
|
||||
setupAvatarLayout(displayName);
|
||||
|
||||
setUserAvatar(userid);
|
||||
} else {
|
||||
setupSimpleLayout();
|
||||
}
|
||||
|
||||
adjustMessageLayoutForWidget();
|
||||
}
|
||||
|
||||
template<class Event, class Widget>
|
||||
void
|
||||
TimelineItem::setupWidgetLayout(Widget *widget, const Event &event, bool withSender)
|
||||
{
|
||||
init();
|
||||
|
||||
// if (event.type == mtx::events::EventType::RoomMessage) {
|
||||
// message_type_ = mtx::events::getMessageType(event.content.msgtype);
|
||||
//}
|
||||
// TODO: Fix this.
|
||||
message_type_ = mtx::events::MessageType::Unknown;
|
||||
event_id_ = QString::fromStdString(event.event_id);
|
||||
const auto sender = QString::fromStdString(event.sender);
|
||||
|
||||
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.origin_server_ts);
|
||||
auto displayName = Cache::displayName(room_id_, sender);
|
||||
|
||||
QSettings settings;
|
||||
descriptionMsg_ = {event_id_,
|
||||
sender == settings.value("auth/user_id") ? "You" : displayName,
|
||||
sender,
|
||||
QString(" %1").arg(utils::messageDescription<Widget>()),
|
||||
utils::descriptiveTime(timestamp),
|
||||
timestamp};
|
||||
|
||||
generateTimestamp(timestamp);
|
||||
|
||||
widgetLayout_ = new QHBoxLayout();
|
||||
widgetLayout_->setContentsMargins(0, 2, 0, 2);
|
||||
widgetLayout_->addWidget(widget);
|
||||
widgetLayout_->addStretch(1);
|
||||
|
||||
if (withSender) {
|
||||
generateBody(sender, displayName, "");
|
||||
setupAvatarLayout(displayName);
|
||||
|
||||
setUserAvatar(sender);
|
||||
} else {
|
||||
setupSimpleLayout();
|
||||
}
|
||||
|
||||
adjustMessageLayoutForWidget();
|
||||
}
|
1344
src/timeline/TimelineModel.cpp
Normal file
1344
src/timeline/TimelineModel.cpp
Normal file
File diff suppressed because it is too large
Load diff
237
src/timeline/TimelineModel.h
Normal file
237
src/timeline/TimelineModel.h
Normal file
|
@ -0,0 +1,237 @@
|
|||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QColor>
|
||||
#include <QDate>
|
||||
#include <QHash>
|
||||
#include <QSet>
|
||||
|
||||
#include <mtx/responses.hpp>
|
||||
|
||||
#include "Cache.h"
|
||||
#include "Logging.h"
|
||||
#include "MatrixClient.h"
|
||||
|
||||
namespace qml_mtx_events {
|
||||
Q_NAMESPACE
|
||||
|
||||
enum EventType
|
||||
{
|
||||
// Unsupported event
|
||||
Unsupported,
|
||||
/// m.room_key_request
|
||||
KeyRequest,
|
||||
/// m.room.aliases
|
||||
Aliases,
|
||||
/// m.room.avatar
|
||||
Avatar,
|
||||
/// m.room.canonical_alias
|
||||
CanonicalAlias,
|
||||
/// m.room.create
|
||||
Create,
|
||||
/// m.room.encrypted.
|
||||
Encrypted,
|
||||
/// m.room.encryption.
|
||||
Encryption,
|
||||
/// m.room.guest_access
|
||||
GuestAccess,
|
||||
/// m.room.history_visibility
|
||||
HistoryVisibility,
|
||||
/// m.room.join_rules
|
||||
JoinRules,
|
||||
/// m.room.member
|
||||
Member,
|
||||
/// m.room.name
|
||||
Name,
|
||||
/// m.room.power_levels
|
||||
PowerLevels,
|
||||
/// m.room.tombstone
|
||||
Tombstone,
|
||||
/// m.room.topic
|
||||
Topic,
|
||||
/// m.room.redaction
|
||||
Redaction,
|
||||
/// m.room.pinned_events
|
||||
PinnedEvents,
|
||||
// m.sticker
|
||||
Sticker,
|
||||
// m.tag
|
||||
Tag,
|
||||
/// m.room.message
|
||||
AudioMessage,
|
||||
EmoteMessage,
|
||||
FileMessage,
|
||||
ImageMessage,
|
||||
LocationMessage,
|
||||
NoticeMessage,
|
||||
TextMessage,
|
||||
VideoMessage,
|
||||
Redacted,
|
||||
UnknownMessage,
|
||||
};
|
||||
Q_ENUM_NS(EventType)
|
||||
|
||||
enum EventState
|
||||
{
|
||||
//! The plaintext message was received by the server.
|
||||
Received,
|
||||
//! At least one of the participants has read the message.
|
||||
Read,
|
||||
//! The client sent the message. Not yet received.
|
||||
Sent,
|
||||
//! When the message is loaded from cache or backfill.
|
||||
Empty,
|
||||
//! When the message failed to send
|
||||
Failed,
|
||||
};
|
||||
Q_ENUM_NS(EventState)
|
||||
}
|
||||
|
||||
class StateKeeper
|
||||
{
|
||||
public:
|
||||
StateKeeper(std::function<void()> &&fn)
|
||||
: fn_(std::move(fn))
|
||||
{}
|
||||
|
||||
~StateKeeper() { fn_(); }
|
||||
|
||||
private:
|
||||
std::function<void()> fn_;
|
||||
};
|
||||
|
||||
struct DecryptionResult
|
||||
{
|
||||
//! The decrypted content as a normal plaintext event.
|
||||
mtx::events::collections::TimelineEvents event;
|
||||
//! Whether or not the decryption was successful.
|
||||
bool isDecrypted = false;
|
||||
};
|
||||
|
||||
class TimelineViewManager;
|
||||
|
||||
class TimelineModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(
|
||||
int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
|
||||
|
||||
public:
|
||||
explicit TimelineModel(TimelineViewManager *manager, QString room_id, QObject *parent = 0);
|
||||
|
||||
enum Roles
|
||||
{
|
||||
Section,
|
||||
Type,
|
||||
Body,
|
||||
FormattedBody,
|
||||
UserId,
|
||||
UserName,
|
||||
Timestamp,
|
||||
Url,
|
||||
ThumbnailUrl,
|
||||
Filename,
|
||||
Filesize,
|
||||
MimeType,
|
||||
Height,
|
||||
Width,
|
||||
ProportionalHeight,
|
||||
Id,
|
||||
State,
|
||||
IsEncrypted,
|
||||
ReplyTo,
|
||||
};
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
Q_INVOKABLE QColor userColor(QString id, QColor background);
|
||||
Q_INVOKABLE QString displayName(QString id) const;
|
||||
Q_INVOKABLE QString avatarUrl(QString id) const;
|
||||
Q_INVOKABLE QString formatDateSeparator(QDate date) const;
|
||||
|
||||
Q_INVOKABLE QString escapeEmoji(QString str) const;
|
||||
Q_INVOKABLE void viewRawMessage(QString id) const;
|
||||
Q_INVOKABLE void openUserProfile(QString userid) const;
|
||||
Q_INVOKABLE void replyAction(QString id);
|
||||
Q_INVOKABLE void readReceiptsAction(QString id) const;
|
||||
Q_INVOKABLE void redactEvent(QString id);
|
||||
Q_INVOKABLE int idToIndex(QString id) const;
|
||||
Q_INVOKABLE QString indexToId(int index) const;
|
||||
|
||||
void addEvents(const mtx::responses::Timeline &events);
|
||||
template<class T>
|
||||
void sendMessage(const T &msg);
|
||||
|
||||
public slots:
|
||||
void fetchHistory();
|
||||
void setCurrentIndex(int index);
|
||||
int currentIndex() const { return idToIndex(currentId); }
|
||||
void markEventsAsRead(const std::vector<QString> &event_ids);
|
||||
|
||||
private slots:
|
||||
// Add old events at the top of the timeline.
|
||||
void addBackwardsEvents(const mtx::responses::Messages &msgs);
|
||||
void processOnePendingMessage();
|
||||
void addPendingMessage(mtx::events::collections::TimelineEvents event);
|
||||
|
||||
signals:
|
||||
void oldMessagesRetrieved(const mtx::responses::Messages &res);
|
||||
void messageFailed(QString txn_id);
|
||||
void messageSent(QString txn_id, QString event_id);
|
||||
void currentIndexChanged(int index);
|
||||
void redactionFailed(QString id);
|
||||
void eventRedacted(QString id);
|
||||
void nextPendingMessage();
|
||||
void newMessageToSend(mtx::events::collections::TimelineEvents event);
|
||||
|
||||
private:
|
||||
DecryptionResult decryptEvent(
|
||||
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e) const;
|
||||
std::vector<QString> internalAddEvents(
|
||||
const std::vector<mtx::events::collections::TimelineEvents> &timeline);
|
||||
void sendEncryptedMessage(const std::string &txn_id, nlohmann::json content);
|
||||
void handleClaimedKeys(std::shared_ptr<StateKeeper> keeper,
|
||||
const std::map<std::string, std::string> &room_key,
|
||||
const std::map<std::string, DevicePublicKeys> &pks,
|
||||
const std::string &user_id,
|
||||
const mtx::responses::ClaimKeys &res,
|
||||
mtx::http::RequestErr err);
|
||||
void updateLastMessage();
|
||||
void readEvent(const std::string &id);
|
||||
|
||||
QHash<QString, mtx::events::collections::TimelineEvents> events;
|
||||
QSet<QString> failed, read;
|
||||
QList<QString> pending;
|
||||
std::vector<QString> eventOrder;
|
||||
|
||||
QString room_id_;
|
||||
QString prev_batch_token_;
|
||||
|
||||
bool isInitialSync = true;
|
||||
bool paginationInProgress = false;
|
||||
bool isProcessingPending = false;
|
||||
|
||||
QHash<QString, QColor> userColors;
|
||||
QString currentId;
|
||||
|
||||
TimelineViewManager *manager_;
|
||||
|
||||
friend struct SendMessageVisitor;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
void
|
||||
TimelineModel::sendMessage(const T &msg)
|
||||
{
|
||||
auto txn_id = http::client()->generate_txn_id();
|
||||
mtx::events::RoomEvent<T> msgCopy = {};
|
||||
msgCopy.content = msg;
|
||||
msgCopy.type = mtx::events::EventType::RoomMessage;
|
||||
msgCopy.event_id = txn_id;
|
||||
msgCopy.sender = http::client()->user_id().to_string();
|
||||
msgCopy.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
emit newMessageToSend(msgCopy);
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,449 +0,0 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QApplication>
|
||||
#include <QLayout>
|
||||
#include <QList>
|
||||
#include <QQueue>
|
||||
#include <QScrollArea>
|
||||
#include <QScrollBar>
|
||||
#include <QStyle>
|
||||
#include <QStyleOption>
|
||||
#include <QTimer>
|
||||
|
||||
#include <mtx/events.hpp>
|
||||
#include <mtx/responses/messages.hpp>
|
||||
|
||||
#include "../Utils.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "timeline/TimelineItem.h"
|
||||
|
||||
class StateKeeper
|
||||
{
|
||||
public:
|
||||
StateKeeper(std::function<void()> &&fn)
|
||||
: fn_(std::move(fn))
|
||||
{}
|
||||
|
||||
~StateKeeper() { fn_(); }
|
||||
|
||||
private:
|
||||
std::function<void()> fn_;
|
||||
};
|
||||
|
||||
struct DecryptionResult
|
||||
{
|
||||
//! The decrypted content as a normal plaintext event.
|
||||
utils::TimelineEvent event;
|
||||
//! Whether or not the decryption was successful.
|
||||
bool isDecrypted = false;
|
||||
};
|
||||
|
||||
class FloatingButton;
|
||||
struct DescInfo;
|
||||
|
||||
// Contains info about a message shown in the history view
|
||||
// but not yet confirmed by the homeserver through sync.
|
||||
struct PendingMessage
|
||||
{
|
||||
mtx::events::MessageType ty;
|
||||
std::string txn_id;
|
||||
RelatedInfo related;
|
||||
QString body;
|
||||
QString filename;
|
||||
QString mime;
|
||||
uint64_t media_size;
|
||||
QString event_id;
|
||||
TimelineItem *widget;
|
||||
QSize dimensions;
|
||||
bool is_encrypted = false;
|
||||
};
|
||||
|
||||
template<class MessageT>
|
||||
MessageT
|
||||
toRoomMessage(const PendingMessage &) = delete;
|
||||
|
||||
template<>
|
||||
mtx::events::msg::Audio
|
||||
toRoomMessage<mtx::events::msg::Audio>(const PendingMessage &m);
|
||||
|
||||
template<>
|
||||
mtx::events::msg::Emote
|
||||
toRoomMessage<mtx::events::msg::Emote>(const PendingMessage &m);
|
||||
|
||||
template<>
|
||||
mtx::events::msg::File
|
||||
toRoomMessage<mtx::events::msg::File>(const PendingMessage &);
|
||||
|
||||
template<>
|
||||
mtx::events::msg::Image
|
||||
toRoomMessage<mtx::events::msg::Image>(const PendingMessage &m);
|
||||
|
||||
template<>
|
||||
mtx::events::msg::Text
|
||||
toRoomMessage<mtx::events::msg::Text>(const PendingMessage &);
|
||||
|
||||
template<>
|
||||
mtx::events::msg::Video
|
||||
toRoomMessage<mtx::events::msg::Video>(const PendingMessage &m);
|
||||
|
||||
// In which place new TimelineItems should be inserted.
|
||||
enum class TimelineDirection
|
||||
{
|
||||
Top,
|
||||
Bottom,
|
||||
};
|
||||
|
||||
class TimelineView : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
TimelineView(const mtx::responses::Timeline &timeline,
|
||||
const QString &room_id,
|
||||
QWidget *parent = 0);
|
||||
TimelineView(const QString &room_id, QWidget *parent = 0);
|
||||
|
||||
// Add new events at the end of the timeline.
|
||||
void addEvents(const mtx::responses::Timeline &timeline);
|
||||
void addUserMessage(mtx::events::MessageType ty,
|
||||
const QString &body,
|
||||
const RelatedInfo &related);
|
||||
void addUserMessage(mtx::events::MessageType ty, const QString &msg);
|
||||
|
||||
template<class Widget, mtx::events::MessageType MsgType>
|
||||
void addUserMessage(const QString &url,
|
||||
const QString &filename,
|
||||
const QString &mime,
|
||||
uint64_t size,
|
||||
const QSize &dimensions = QSize());
|
||||
void updatePendingMessage(const std::string &txn_id, const QString &event_id);
|
||||
void scrollDown();
|
||||
|
||||
//! Remove an item from the timeline with the given Event ID.
|
||||
void removeEvent(const QString &event_id);
|
||||
void setPrevBatchToken(const QString &token) { prev_batch_token_ = token; }
|
||||
|
||||
public slots:
|
||||
void sliderRangeChanged(int min, int max);
|
||||
void sliderMoved(int position);
|
||||
void fetchHistory();
|
||||
|
||||
// Add old events at the top of the timeline.
|
||||
void addBackwardsEvents(const mtx::responses::Messages &msgs);
|
||||
|
||||
// Whether or not the initial batch has been loaded.
|
||||
bool hasLoaded() { return scroll_layout_->count() > 0 || isTimelineFinished; }
|
||||
|
||||
void handleFailedMessage(const std::string &txn_id);
|
||||
|
||||
private slots:
|
||||
void sendNextPendingMessage();
|
||||
|
||||
signals:
|
||||
void updateLastTimelineMessage(const QString &user, const DescInfo &info);
|
||||
void messagesRetrieved(const mtx::responses::Messages &res);
|
||||
void messageFailed(const std::string &txn_id);
|
||||
void messageSent(const std::string &txn_id, const QString &event_id);
|
||||
void markReadEvents(const std::vector<QString> &event_ids);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
private:
|
||||
using TimelineEvent = mtx::events::collections::TimelineEvents;
|
||||
|
||||
//! Mark our own widgets as read if they have more than one receipt.
|
||||
void displayReadReceipts(std::vector<TimelineEvent> events);
|
||||
//! Determine if the start of the timeline is reached from the response of /messages.
|
||||
bool isStartOfTimeline(const mtx::responses::Messages &msgs);
|
||||
|
||||
QWidget *relativeWidget(QWidget *item, int dt) const;
|
||||
|
||||
DecryptionResult parseEncryptedEvent(
|
||||
const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e);
|
||||
|
||||
void handleClaimedKeys(std::shared_ptr<StateKeeper> keeper,
|
||||
const std::map<std::string, std::string> &room_key,
|
||||
const std::map<std::string, DevicePublicKeys> &pks,
|
||||
const std::string &user_id,
|
||||
const mtx::responses::ClaimKeys &res,
|
||||
mtx::http::RequestErr err);
|
||||
|
||||
//! Callback for all message sending.
|
||||
void sendRoomMessageHandler(const std::string &txn_id,
|
||||
const mtx::responses::EventId &res,
|
||||
mtx::http::RequestErr err);
|
||||
void prepareEncryptedMessage(const PendingMessage &msg);
|
||||
|
||||
//! Call the /messages endpoint to fill the timeline.
|
||||
void getMessages();
|
||||
//! HACK: Fixing layout flickering when adding to the bottom
|
||||
//! of the timeline.
|
||||
void pushTimelineItem(QWidget *item, TimelineDirection dir)
|
||||
{
|
||||
setUpdatesEnabled(false);
|
||||
item->hide();
|
||||
|
||||
if (dir == TimelineDirection::Top)
|
||||
scroll_layout_->insertWidget(0, item);
|
||||
else
|
||||
scroll_layout_->addWidget(item);
|
||||
|
||||
QTimer::singleShot(0, this, [item, this]() {
|
||||
item->show();
|
||||
item->adjustSize();
|
||||
setUpdatesEnabled(true);
|
||||
});
|
||||
}
|
||||
|
||||
//! Decides whether or not to show or hide the scroll down button.
|
||||
void toggleScrollDownButton();
|
||||
void init();
|
||||
void addTimelineItem(QWidget *item,
|
||||
TimelineDirection direction = TimelineDirection::Bottom);
|
||||
void updateLastSender(const QString &user_id, TimelineDirection direction);
|
||||
void notifyForLastEvent();
|
||||
void notifyForLastEvent(const TimelineEvent &event);
|
||||
//! Keep track of the sender and the timestamp of the current message.
|
||||
void saveLastMessageInfo(const QString &sender, const QDateTime &datetime)
|
||||
{
|
||||
lastSender_ = sender;
|
||||
lastMsgTimestamp_ = datetime;
|
||||
}
|
||||
void saveFirstMessageInfo(const QString &sender, const QDateTime &datetime)
|
||||
{
|
||||
firstSender_ = sender;
|
||||
firstMsgTimestamp_ = datetime;
|
||||
}
|
||||
//! Keep track of the sender and the timestamp of the current message.
|
||||
void saveMessageInfo(const QString &sender,
|
||||
uint64_t origin_server_ts,
|
||||
TimelineDirection direction);
|
||||
|
||||
TimelineEvent findFirstViewableEvent(const std::vector<TimelineEvent> &events);
|
||||
TimelineEvent findLastViewableEvent(const std::vector<TimelineEvent> &events);
|
||||
|
||||
//! Mark the last event as read.
|
||||
void readLastEvent() const;
|
||||
//! Whether or not the scrollbar is visible (non-zero height).
|
||||
bool isScrollbarActivated() { return scroll_area_->verticalScrollBar()->value() != 0; }
|
||||
//! Retrieve the event id of the last item.
|
||||
QString getLastEventId() const;
|
||||
|
||||
template<class Event, class Widget>
|
||||
TimelineItem *processMessageEvent(const Event &event, TimelineDirection direction);
|
||||
|
||||
// TODO: Remove this eventually.
|
||||
template<class Event>
|
||||
TimelineItem *processMessageEvent(const Event &event, TimelineDirection direction);
|
||||
|
||||
// For events with custom display widgets.
|
||||
template<class Event, class Widget>
|
||||
TimelineItem *createTimelineItem(const Event &event, bool withSender);
|
||||
|
||||
// For events without custom display widgets.
|
||||
// TODO: All events should have custom widgets.
|
||||
template<class Event>
|
||||
TimelineItem *createTimelineItem(const Event &event, bool withSender);
|
||||
|
||||
// Used to determine whether or not we should prefix a message with the
|
||||
// sender's name.
|
||||
bool isSenderRendered(const QString &user_id,
|
||||
uint64_t origin_server_ts,
|
||||
TimelineDirection direction);
|
||||
|
||||
bool isPendingMessage(const std::string &txn_id,
|
||||
const QString &sender,
|
||||
const QString &userid);
|
||||
void removePendingMessage(const std::string &txn_id);
|
||||
|
||||
bool isDuplicate(const QString &event_id) { return eventIds_.contains(event_id); }
|
||||
|
||||
void handleNewUserMessage(PendingMessage msg);
|
||||
bool isDateDifference(const QDateTime &first,
|
||||
const QDateTime &second = QDateTime::currentDateTime()) const;
|
||||
|
||||
// Return nullptr if the event couldn't be parsed.
|
||||
QWidget *parseMessageEvent(const mtx::events::collections::TimelineEvents &event,
|
||||
TimelineDirection direction);
|
||||
|
||||
//! Store the event id associated with the given widget.
|
||||
void saveEventId(QWidget *widget);
|
||||
//! Remove all widgets from the timeline layout.
|
||||
void clearTimeline();
|
||||
|
||||
QVBoxLayout *top_layout_;
|
||||
QVBoxLayout *scroll_layout_;
|
||||
|
||||
QScrollArea *scroll_area_;
|
||||
QWidget *scroll_widget_;
|
||||
|
||||
QString firstSender_;
|
||||
QDateTime firstMsgTimestamp_;
|
||||
QString lastSender_;
|
||||
QDateTime lastMsgTimestamp_;
|
||||
|
||||
QString room_id_;
|
||||
QString prev_batch_token_;
|
||||
QString local_user_;
|
||||
|
||||
bool isPaginationInProgress_ = false;
|
||||
|
||||
// Keeps track whether or not the user has visited the view.
|
||||
bool isInitialized = false;
|
||||
bool isTimelineFinished = false;
|
||||
bool isInitialSync = true;
|
||||
|
||||
const int SCROLL_BAR_GAP = 200;
|
||||
|
||||
QTimer *paginationTimer_;
|
||||
|
||||
int scroll_height_ = 0;
|
||||
int previous_max_height_ = 0;
|
||||
|
||||
int oldPosition_;
|
||||
int oldHeight_;
|
||||
|
||||
FloatingButton *scrollDownBtn_;
|
||||
|
||||
TimelineDirection lastMessageDirection_;
|
||||
|
||||
//! Messages received by sync not added to the timeline.
|
||||
std::vector<TimelineEvent> bottomMessages_;
|
||||
//! Messages received by /messages not added to the timeline.
|
||||
std::vector<TimelineEvent> topMessages_;
|
||||
|
||||
//! Render the given timeline events to the bottom of the timeline.
|
||||
void renderBottomEvents(const std::vector<TimelineEvent> &events);
|
||||
//! Render the given timeline events to the top of the timeline.
|
||||
void renderTopEvents(const std::vector<TimelineEvent> &events);
|
||||
|
||||
// The events currently rendered. Used for duplicate detection.
|
||||
QMap<QString, QWidget *> eventIds_;
|
||||
QQueue<PendingMessage> pending_msgs_;
|
||||
QList<PendingMessage> pending_sent_msgs_;
|
||||
};
|
||||
|
||||
template<class Widget, mtx::events::MessageType MsgType>
|
||||
void
|
||||
TimelineView::addUserMessage(const QString &url,
|
||||
const QString &filename,
|
||||
const QString &mime,
|
||||
uint64_t size,
|
||||
const QSize &dimensions)
|
||||
{
|
||||
auto with_sender = (lastSender_ != local_user_) || isDateDifference(lastMsgTimestamp_);
|
||||
auto trimmed = QFileInfo{filename}.fileName(); // Trim file path.
|
||||
|
||||
auto widget = new Widget(url, trimmed, size, this);
|
||||
|
||||
TimelineItem *view_item =
|
||||
new TimelineItem(widget, local_user_, with_sender, room_id_, scroll_widget_);
|
||||
|
||||
addTimelineItem(view_item);
|
||||
|
||||
lastMessageDirection_ = TimelineDirection::Bottom;
|
||||
|
||||
// Keep track of the sender and the timestamp of the current message.
|
||||
saveLastMessageInfo(local_user_, QDateTime::currentDateTime());
|
||||
|
||||
PendingMessage message;
|
||||
message.ty = MsgType;
|
||||
message.txn_id = http::client()->generate_txn_id();
|
||||
message.body = url;
|
||||
message.filename = trimmed;
|
||||
message.mime = mime;
|
||||
message.media_size = size;
|
||||
message.widget = view_item;
|
||||
message.dimensions = dimensions;
|
||||
|
||||
handleNewUserMessage(message);
|
||||
}
|
||||
|
||||
template<class Event>
|
||||
TimelineItem *
|
||||
TimelineView::createTimelineItem(const Event &event, bool withSender)
|
||||
{
|
||||
TimelineItem *item = new TimelineItem(event, withSender, room_id_, scroll_widget_);
|
||||
return item;
|
||||
}
|
||||
|
||||
template<class Event, class Widget>
|
||||
TimelineItem *
|
||||
TimelineView::createTimelineItem(const Event &event, bool withSender)
|
||||
{
|
||||
auto eventWidget = new Widget(event);
|
||||
auto item = new TimelineItem(eventWidget, event, withSender, room_id_, scroll_widget_);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
template<class Event>
|
||||
TimelineItem *
|
||||
TimelineView::processMessageEvent(const Event &event, TimelineDirection direction)
|
||||
{
|
||||
const auto event_id = QString::fromStdString(event.event_id);
|
||||
const auto sender = QString::fromStdString(event.sender);
|
||||
|
||||
const auto txn_id = event.unsigned_data.transaction_id;
|
||||
if ((!txn_id.empty() && isPendingMessage(txn_id, sender, local_user_)) ||
|
||||
isDuplicate(event_id)) {
|
||||
removePendingMessage(txn_id);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto with_sender = isSenderRendered(sender, event.origin_server_ts, direction);
|
||||
|
||||
saveMessageInfo(sender, event.origin_server_ts, direction);
|
||||
|
||||
auto item = createTimelineItem<Event>(event, with_sender);
|
||||
|
||||
eventIds_[event_id] = item;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
template<class Event, class Widget>
|
||||
TimelineItem *
|
||||
TimelineView::processMessageEvent(const Event &event, TimelineDirection direction)
|
||||
{
|
||||
const auto event_id = QString::fromStdString(event.event_id);
|
||||
const auto sender = QString::fromStdString(event.sender);
|
||||
|
||||
const auto txn_id = event.unsigned_data.transaction_id;
|
||||
if ((!txn_id.empty() && isPendingMessage(txn_id, sender, local_user_)) ||
|
||||
isDuplicate(event_id)) {
|
||||
removePendingMessage(txn_id);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto with_sender = isSenderRendered(sender, event.origin_server_ts, direction);
|
||||
|
||||
saveMessageInfo(sender, event.origin_server_ts, direction);
|
||||
|
||||
auto item = createTimelineItem<Event, Widget>(event, with_sender);
|
||||
|
||||
eventIds_[event_id] = item;
|
||||
|
||||
return item;
|
||||
}
|
|
@ -1,94 +1,342 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "TimelineViewManager.h"
|
||||
|
||||
#include <random>
|
||||
#include <QFileDialog>
|
||||
#include <QMetaType>
|
||||
#include <QMimeDatabase>
|
||||
#include <QPalette>
|
||||
#include <QQmlContext>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QSettings>
|
||||
|
||||
#include "Cache.h"
|
||||
#include "ChatPage.h"
|
||||
#include "ColorImageProvider.h"
|
||||
#include "DelegateChooser.h"
|
||||
#include "Logging.h"
|
||||
#include "Utils.h"
|
||||
#include "timeline/TimelineView.h"
|
||||
#include "timeline/TimelineViewManager.h"
|
||||
#include "timeline/widgets/AudioItem.h"
|
||||
#include "timeline/widgets/FileItem.h"
|
||||
#include "timeline/widgets/ImageItem.h"
|
||||
#include "timeline/widgets/VideoItem.h"
|
||||
#include "MxcImageProvider.h"
|
||||
#include "UserSettingsPage.h"
|
||||
#include "dialogs/ImageOverlay.h"
|
||||
|
||||
void
|
||||
TimelineViewManager::updateColorPalette()
|
||||
{
|
||||
UserSettings settings;
|
||||
if (settings.theme() == "light") {
|
||||
QPalette lightActive(/*windowText*/ QColor("#333"),
|
||||
/*button*/ QColor("#333"),
|
||||
/*light*/ QColor(),
|
||||
/*dark*/ QColor(220, 220, 220, 120),
|
||||
/*mid*/ QColor(),
|
||||
/*text*/ QColor("#333"),
|
||||
/*bright_text*/ QColor(),
|
||||
/*base*/ QColor("white"),
|
||||
/*window*/ QColor("white"));
|
||||
view->rootContext()->setContextProperty("currentActivePalette", lightActive);
|
||||
view->rootContext()->setContextProperty("currentInactivePalette", lightActive);
|
||||
} else if (settings.theme() == "dark") {
|
||||
QPalette darkActive(/*windowText*/ QColor("#caccd1"),
|
||||
/*button*/ QColor("#caccd1"),
|
||||
/*light*/ QColor(),
|
||||
/*dark*/ QColor(45, 49, 57, 120),
|
||||
/*mid*/ QColor(),
|
||||
/*text*/ QColor("#caccd1"),
|
||||
/*bright_text*/ QColor(),
|
||||
/*base*/ QColor("#202228"),
|
||||
/*window*/ QColor("#202228"));
|
||||
darkActive.setColor(QPalette::Highlight, QColor("#e7e7e9"));
|
||||
view->rootContext()->setContextProperty("currentActivePalette", darkActive);
|
||||
view->rootContext()->setContextProperty("currentInactivePalette", darkActive);
|
||||
} else {
|
||||
view->rootContext()->setContextProperty("currentActivePalette", QPalette());
|
||||
view->rootContext()->setContextProperty("currentInactivePalette", nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
TimelineViewManager::TimelineViewManager(QWidget *parent)
|
||||
: QStackedWidget(parent)
|
||||
{}
|
||||
: imgProvider(new MxcImageProvider())
|
||||
, colorImgProvider(new ColorImageProvider())
|
||||
{
|
||||
qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
|
||||
"com.github.nheko",
|
||||
1,
|
||||
0,
|
||||
"MtxEvent",
|
||||
"Can't instantiate enum!");
|
||||
qmlRegisterType<DelegateChoice>("com.github.nheko", 1, 0, "DelegateChoice");
|
||||
qmlRegisterType<DelegateChooser>("com.github.nheko", 1, 0, "DelegateChooser");
|
||||
|
||||
#ifdef USE_QUICK_VIEW
|
||||
view = new QQuickView();
|
||||
container = QWidget::createWindowContainer(view, parent);
|
||||
#else
|
||||
view = new QQuickWidget(parent);
|
||||
container = view;
|
||||
view->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
||||
container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
connect(view, &QQuickWidget::statusChanged, this, [](QQuickWidget::Status status) {
|
||||
nhlog::ui()->debug("Status changed to {}", status);
|
||||
});
|
||||
#endif
|
||||
container->setMinimumSize(200, 200);
|
||||
view->rootContext()->setContextProperty("timelineManager", this);
|
||||
updateColorPalette();
|
||||
view->engine()->addImageProvider("MxcImage", imgProvider);
|
||||
view->engine()->addImageProvider("colorimage", colorImgProvider);
|
||||
view->setSource(QUrl("qrc:///qml/TimelineView.qml"));
|
||||
|
||||
connect(dynamic_cast<ChatPage *>(parent),
|
||||
&ChatPage::themeChanged,
|
||||
this,
|
||||
&TimelineViewManager::updateColorPalette);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::sync(const mtx::responses::Rooms &rooms)
|
||||
{
|
||||
for (auto it = rooms.join.cbegin(); it != rooms.join.cend(); ++it) {
|
||||
// addRoom will only add the room, if it doesn't exist
|
||||
addRoom(QString::fromStdString(it->first));
|
||||
models.value(QString::fromStdString(it->first))->addEvents(it->second.timeline);
|
||||
}
|
||||
|
||||
this->isInitialSync_ = false;
|
||||
emit initialSyncChanged(false);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::addRoom(const QString &room_id)
|
||||
{
|
||||
if (!models.contains(room_id))
|
||||
models.insert(room_id,
|
||||
QSharedPointer<TimelineModel>(new TimelineModel(this, room_id)));
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::setHistoryView(const QString &room_id)
|
||||
{
|
||||
nhlog::ui()->info("Trying to activate room {}", room_id.toStdString());
|
||||
|
||||
auto room = models.find(room_id);
|
||||
if (room != models.end()) {
|
||||
timeline_ = room.value().data();
|
||||
emit activeTimelineChanged(timeline_);
|
||||
nhlog::ui()->info("Activated room {}", room_id.toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::openImageOverlay(QString mxcUrl,
|
||||
QString originalFilename,
|
||||
QString mimeType,
|
||||
qml_mtx_events::EventType eventType) const
|
||||
{
|
||||
QQuickImageResponse *imgResponse =
|
||||
imgProvider->requestImageResponse(mxcUrl.remove("mxc://"), QSize());
|
||||
connect(imgResponse,
|
||||
&QQuickImageResponse::finished,
|
||||
this,
|
||||
[this, mxcUrl, originalFilename, mimeType, eventType, imgResponse]() {
|
||||
if (!imgResponse->errorString().isEmpty()) {
|
||||
nhlog::ui()->error("Error when retrieving image for overlay: {}",
|
||||
imgResponse->errorString().toStdString());
|
||||
return;
|
||||
}
|
||||
auto pixmap = QPixmap::fromImage(imgResponse->textureFactory()->image());
|
||||
|
||||
auto imgDialog = new dialogs::ImageOverlay(pixmap);
|
||||
imgDialog->show();
|
||||
connect(imgDialog,
|
||||
&dialogs::ImageOverlay::saving,
|
||||
this,
|
||||
[this, mxcUrl, originalFilename, mimeType, eventType]() {
|
||||
saveMedia(mxcUrl, originalFilename, mimeType, eventType);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::saveMedia(QString mxcUrl,
|
||||
QString originalFilename,
|
||||
QString mimeType,
|
||||
qml_mtx_events::EventType eventType) const
|
||||
{
|
||||
QString dialogTitle;
|
||||
if (eventType == qml_mtx_events::EventType::ImageMessage) {
|
||||
dialogTitle = tr("Save image");
|
||||
} else if (eventType == qml_mtx_events::EventType::VideoMessage) {
|
||||
dialogTitle = tr("Save video");
|
||||
} else if (eventType == qml_mtx_events::EventType::AudioMessage) {
|
||||
dialogTitle = tr("Save audio");
|
||||
} else {
|
||||
dialogTitle = tr("Save file");
|
||||
}
|
||||
|
||||
QString filterString = QMimeDatabase().mimeTypeForName(mimeType).filterString();
|
||||
|
||||
auto filename =
|
||||
QFileDialog::getSaveFileName(container, dialogTitle, originalFilename, filterString);
|
||||
|
||||
if (filename.isEmpty())
|
||||
return;
|
||||
|
||||
const auto url = mxcUrl.toStdString();
|
||||
|
||||
http::client()->download(
|
||||
url,
|
||||
[filename, url](const std::string &data,
|
||||
const std::string &,
|
||||
const std::string &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn("failed to retrieve image {}: {} {}",
|
||||
url,
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
QFile file(filename);
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
return;
|
||||
|
||||
file.write(QByteArray(data.data(), (int)data.size()));
|
||||
file.close();
|
||||
} catch (const std::exception &e) {
|
||||
nhlog::ui()->warn("Error while saving file to: {}", e.what());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::cacheMedia(QString mxcUrl, QString mimeType)
|
||||
{
|
||||
// If the message is a link to a non mxcUrl, don't download it
|
||||
if (!mxcUrl.startsWith("mxc://")) {
|
||||
emit mediaCached(mxcUrl, mxcUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
QString suffix = QMimeDatabase().mimeTypeForName(mimeType).preferredSuffix();
|
||||
|
||||
const auto url = mxcUrl.toStdString();
|
||||
QFileInfo filename(QString("%1/media_cache/%2.%3")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
|
||||
.arg(QString(mxcUrl).remove("mxc://"))
|
||||
.arg(suffix));
|
||||
if (QDir::cleanPath(filename.path()) != filename.path()) {
|
||||
nhlog::net()->warn("mxcUrl '{}' is not safe, not downloading file", url);
|
||||
return;
|
||||
}
|
||||
|
||||
QDir().mkpath(filename.path());
|
||||
|
||||
if (filename.isReadable()) {
|
||||
emit mediaCached(mxcUrl, filename.filePath());
|
||||
return;
|
||||
}
|
||||
|
||||
http::client()->download(
|
||||
url,
|
||||
[this, mxcUrl, filename, url](const std::string &data,
|
||||
const std::string &,
|
||||
const std::string &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn("failed to retrieve image {}: {} {}",
|
||||
url,
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
QFile file(filename.filePath());
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
return;
|
||||
|
||||
file.write(QByteArray(data.data(), data.size()));
|
||||
file.close();
|
||||
} catch (const std::exception &e) {
|
||||
nhlog::ui()->warn("Error while saving file to: {}", e.what());
|
||||
}
|
||||
|
||||
emit mediaCached(mxcUrl, filename.filePath());
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::updateReadReceipts(const QString &room_id,
|
||||
const std::vector<QString> &event_ids)
|
||||
{
|
||||
if (timelineViewExists(room_id)) {
|
||||
auto view = views_[room_id];
|
||||
if (view)
|
||||
emit view->markReadEvents(event_ids);
|
||||
auto room = models.find(room_id);
|
||||
if (room != models.end()) {
|
||||
room.value()->markEventsAsRead(event_ids);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::removeTimelineEvent(const QString &room_id, const QString &event_id)
|
||||
TimelineViewManager::initWithMessages(const std::map<QString, mtx::responses::Timeline> &msgs)
|
||||
{
|
||||
auto view = views_[room_id];
|
||||
for (const auto &e : msgs) {
|
||||
addRoom(e.first);
|
||||
|
||||
if (view)
|
||||
view->removeEvent(event_id);
|
||||
models.value(e.first)->addEvents(e.second);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::queueTextMessage(const QString &msg)
|
||||
{
|
||||
if (active_room_.isEmpty())
|
||||
return;
|
||||
mtx::events::msg::Text text = {};
|
||||
text.body = msg.trimmed().toStdString();
|
||||
text.format = "org.matrix.custom.html";
|
||||
text.formatted_body = utils::markdownToHtml(msg).toStdString();
|
||||
|
||||
auto room_id = active_room_;
|
||||
auto view = views_[room_id];
|
||||
|
||||
view->addUserMessage(mtx::events::MessageType::Text, msg);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::queueEmoteMessage(const QString &msg)
|
||||
{
|
||||
if (active_room_.isEmpty())
|
||||
return;
|
||||
|
||||
auto room_id = active_room_;
|
||||
auto view = views_[room_id];
|
||||
|
||||
view->addUserMessage(mtx::events::MessageType::Emote, msg);
|
||||
if (timeline_)
|
||||
timeline_->sendMessage(text);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::queueReplyMessage(const QString &reply, const RelatedInfo &related)
|
||||
{
|
||||
if (active_room_.isEmpty())
|
||||
return;
|
||||
mtx::events::msg::Text text = {};
|
||||
|
||||
auto room_id = active_room_;
|
||||
auto view = views_[room_id];
|
||||
QString body;
|
||||
bool firstLine = true;
|
||||
for (const auto &line : related.quoted_body.split("\n")) {
|
||||
if (firstLine) {
|
||||
firstLine = false;
|
||||
body = QString("> <%1> %2\n").arg(related.quoted_user).arg(line);
|
||||
} else {
|
||||
body = QString("%1\n> %2\n").arg(body).arg(line);
|
||||
}
|
||||
}
|
||||
|
||||
view->addUserMessage(mtx::events::MessageType::Text, reply, related);
|
||||
text.body = QString("%1\n%2").arg(body).arg(reply).toStdString();
|
||||
text.format = "org.matrix.custom.html";
|
||||
text.formatted_body =
|
||||
utils::getFormattedQuoteBody(related, utils::markdownToHtml(reply)).toStdString();
|
||||
text.relates_to.in_reply_to.event_id = related.related_event;
|
||||
|
||||
if (timeline_)
|
||||
timeline_->sendMessage(text);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::queueEmoteMessage(const QString &msg)
|
||||
{
|
||||
auto html = utils::markdownToHtml(msg);
|
||||
|
||||
mtx::events::msg::Emote emote;
|
||||
emote.body = msg.trimmed().toStdString();
|
||||
|
||||
if (html != msg.trimmed().toHtmlEscaped())
|
||||
emote.formatted_body = html.toStdString();
|
||||
|
||||
if (timeline_)
|
||||
timeline_->sendMessage(emote);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -96,18 +344,17 @@ TimelineViewManager::queueImageMessage(const QString &roomid,
|
|||
const QString &filename,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
uint64_t size,
|
||||
uint64_t dsize,
|
||||
const QSize &dimensions)
|
||||
{
|
||||
if (!timelineViewExists(roomid)) {
|
||||
nhlog::ui()->warn("Cannot send m.image message to a non-managed view");
|
||||
return;
|
||||
}
|
||||
|
||||
auto view = views_[roomid];
|
||||
|
||||
view->addUserMessage<ImageItem, mtx::events::MessageType::Image>(
|
||||
url, filename, mime, size, dimensions);
|
||||
mtx::events::msg::Image image;
|
||||
image.info.mimetype = mime.toStdString();
|
||||
image.info.size = dsize;
|
||||
image.body = filename.toStdString();
|
||||
image.url = url.toStdString();
|
||||
image.info.h = dimensions.height();
|
||||
image.info.w = dimensions.width();
|
||||
models.value(roomid)->sendMessage(image);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -115,16 +362,14 @@ TimelineViewManager::queueFileMessage(const QString &roomid,
|
|||
const QString &filename,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
uint64_t size)
|
||||
uint64_t dsize)
|
||||
{
|
||||
if (!timelineViewExists(roomid)) {
|
||||
nhlog::ui()->warn("cannot send m.file message to a non-managed view");
|
||||
return;
|
||||
}
|
||||
|
||||
auto view = views_[roomid];
|
||||
|
||||
view->addUserMessage<FileItem, mtx::events::MessageType::File>(url, filename, mime, size);
|
||||
mtx::events::msg::File file;
|
||||
file.info.mimetype = mime.toStdString();
|
||||
file.info.size = dsize;
|
||||
file.body = filename.toStdString();
|
||||
file.url = url.toStdString();
|
||||
models.value(roomid)->sendMessage(file);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -132,16 +377,14 @@ TimelineViewManager::queueAudioMessage(const QString &roomid,
|
|||
const QString &filename,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
uint64_t size)
|
||||
uint64_t dsize)
|
||||
{
|
||||
if (!timelineViewExists(roomid)) {
|
||||
nhlog::ui()->warn("cannot send m.audio message to a non-managed view");
|
||||
return;
|
||||
}
|
||||
|
||||
auto view = views_[roomid];
|
||||
|
||||
view->addUserMessage<AudioItem, mtx::events::MessageType::Audio>(url, filename, mime, size);
|
||||
mtx::events::msg::Audio audio;
|
||||
audio.info.mimetype = mime.toStdString();
|
||||
audio.info.size = dsize;
|
||||
audio.body = filename.toStdString();
|
||||
audio.url = url.toStdString();
|
||||
models.value(roomid)->sendMessage(audio);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -149,192 +392,12 @@ TimelineViewManager::queueVideoMessage(const QString &roomid,
|
|||
const QString &filename,
|
||||
const QString &url,
|
||||
const QString &mime,
|
||||
uint64_t size)
|
||||
uint64_t dsize)
|
||||
{
|
||||
if (!timelineViewExists(roomid)) {
|
||||
nhlog::ui()->warn("cannot send m.video message to a non-managed view");
|
||||
return;
|
||||
}
|
||||
|
||||
auto view = views_[roomid];
|
||||
|
||||
view->addUserMessage<VideoItem, mtx::events::MessageType::Video>(url, filename, mime, size);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::initialize(const mtx::responses::Rooms &rooms)
|
||||
{
|
||||
for (auto it = rooms.join.cbegin(); it != rooms.join.cend(); ++it) {
|
||||
addRoom(it->second, QString::fromStdString(it->first));
|
||||
}
|
||||
|
||||
sync(rooms);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::initWithMessages(const std::map<QString, mtx::responses::Timeline> &msgs)
|
||||
{
|
||||
for (auto it = msgs.cbegin(); it != msgs.cend(); ++it) {
|
||||
if (timelineViewExists(it->first))
|
||||
return;
|
||||
|
||||
// Create a history view with the room events.
|
||||
TimelineView *view = new TimelineView(it->second, it->first);
|
||||
views_.emplace(it->first, QSharedPointer<TimelineView>(view));
|
||||
|
||||
connect(view,
|
||||
&TimelineView::updateLastTimelineMessage,
|
||||
this,
|
||||
&TimelineViewManager::updateRoomsLastMessage);
|
||||
|
||||
// Add the view in the widget stack.
|
||||
addWidget(view);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::initialize(const std::vector<std::string> &rooms)
|
||||
{
|
||||
for (const auto &roomid : rooms)
|
||||
addRoom(QString::fromStdString(roomid));
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::addRoom(const mtx::responses::JoinedRoom &room, const QString &room_id)
|
||||
{
|
||||
if (timelineViewExists(room_id))
|
||||
return;
|
||||
|
||||
// Create a history view with the room events.
|
||||
TimelineView *view = new TimelineView(room.timeline, room_id);
|
||||
views_.emplace(room_id, QSharedPointer<TimelineView>(view));
|
||||
|
||||
connect(view,
|
||||
&TimelineView::updateLastTimelineMessage,
|
||||
this,
|
||||
&TimelineViewManager::updateRoomsLastMessage);
|
||||
|
||||
// Add the view in the widget stack.
|
||||
addWidget(view);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::addRoom(const QString &room_id)
|
||||
{
|
||||
if (timelineViewExists(room_id))
|
||||
return;
|
||||
|
||||
// Create a history view without any events.
|
||||
TimelineView *view = new TimelineView(room_id);
|
||||
views_.emplace(room_id, QSharedPointer<TimelineView>(view));
|
||||
|
||||
connect(view,
|
||||
&TimelineView::updateLastTimelineMessage,
|
||||
this,
|
||||
&TimelineViewManager::updateRoomsLastMessage);
|
||||
|
||||
// Add the view in the widget stack.
|
||||
addWidget(view);
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::sync(const mtx::responses::Rooms &rooms)
|
||||
{
|
||||
for (const auto &room : rooms.join) {
|
||||
auto roomid = QString::fromStdString(room.first);
|
||||
|
||||
if (!timelineViewExists(roomid)) {
|
||||
nhlog::ui()->warn("ignoring event from unknown room: {}",
|
||||
roomid.toStdString());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto view = views_.at(roomid);
|
||||
|
||||
view->addEvents(room.second.timeline);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::setHistoryView(const QString &room_id)
|
||||
{
|
||||
if (!timelineViewExists(room_id)) {
|
||||
nhlog::ui()->warn("room from RoomList is not present in ViewManager: {}",
|
||||
room_id.toStdString());
|
||||
return;
|
||||
}
|
||||
|
||||
active_room_ = room_id;
|
||||
auto view = views_.at(room_id);
|
||||
|
||||
setCurrentWidget(view.data());
|
||||
|
||||
view->fetchHistory();
|
||||
view->scrollDown();
|
||||
}
|
||||
|
||||
QString
|
||||
TimelineViewManager::chooseRandomColor()
|
||||
{
|
||||
std::random_device random_device;
|
||||
std::mt19937 engine{random_device()};
|
||||
std::uniform_real_distribution<float> dist(0, 1);
|
||||
|
||||
float hue = dist(engine);
|
||||
float saturation = 0.9;
|
||||
float value = 0.7;
|
||||
|
||||
int hue_i = hue * 6;
|
||||
|
||||
float f = hue * 6 - hue_i;
|
||||
|
||||
float p = value * (1 - saturation);
|
||||
float q = value * (1 - f * saturation);
|
||||
float t = value * (1 - (1 - f) * saturation);
|
||||
|
||||
float r = 0;
|
||||
float g = 0;
|
||||
float b = 0;
|
||||
|
||||
if (hue_i == 0) {
|
||||
r = value;
|
||||
g = t;
|
||||
b = p;
|
||||
} else if (hue_i == 1) {
|
||||
r = q;
|
||||
g = value;
|
||||
b = p;
|
||||
} else if (hue_i == 2) {
|
||||
r = p;
|
||||
g = value;
|
||||
b = t;
|
||||
} else if (hue_i == 3) {
|
||||
r = p;
|
||||
g = q;
|
||||
b = value;
|
||||
} else if (hue_i == 4) {
|
||||
r = t;
|
||||
g = p;
|
||||
b = value;
|
||||
} else if (hue_i == 5) {
|
||||
r = value;
|
||||
g = p;
|
||||
b = q;
|
||||
}
|
||||
|
||||
int ri = r * 256;
|
||||
int gi = g * 256;
|
||||
int bi = b * 256;
|
||||
|
||||
QColor color(ri, gi, bi);
|
||||
|
||||
return color.name();
|
||||
}
|
||||
|
||||
bool
|
||||
TimelineViewManager::hasLoaded() const
|
||||
{
|
||||
return std::all_of(views_.cbegin(), views_.cend(), [](const auto &view) {
|
||||
return view.second->hasLoaded();
|
||||
});
|
||||
mtx::events::msg::Video video;
|
||||
video.info.mimetype = mime.toStdString();
|
||||
video.info.size = dsize;
|
||||
video.body = filename.toStdString();
|
||||
video.url = url.toStdString();
|
||||
models.value(roomid)->sendMessage(video);
|
||||
}
|
||||
|
|
|
@ -1,69 +1,80 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QQuickView>
|
||||
#include <QQuickWidget>
|
||||
#include <QSharedPointer>
|
||||
#include <QStackedWidget>
|
||||
#include <QWidget>
|
||||
|
||||
#include <mtx.hpp>
|
||||
#include <mtx/responses.hpp>
|
||||
|
||||
#include "Cache.h"
|
||||
#include "Logging.h"
|
||||
#include "TimelineModel.h"
|
||||
#include "Utils.h"
|
||||
|
||||
class QFile;
|
||||
class MxcImageProvider;
|
||||
class ColorImageProvider;
|
||||
|
||||
class RoomInfoListItem;
|
||||
class TimelineView;
|
||||
struct DescInfo;
|
||||
struct SavedMessages;
|
||||
|
||||
class TimelineViewManager : public QStackedWidget
|
||||
class TimelineViewManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(
|
||||
TimelineModel *timeline MEMBER timeline_ READ activeTimeline NOTIFY activeTimelineChanged)
|
||||
Q_PROPERTY(
|
||||
bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
|
||||
|
||||
public:
|
||||
TimelineViewManager(QWidget *parent);
|
||||
|
||||
// Initialize with timeline events.
|
||||
void initialize(const mtx::responses::Rooms &rooms);
|
||||
// Empty initialization.
|
||||
void initialize(const std::vector<std::string> &rooms);
|
||||
|
||||
void addRoom(const mtx::responses::JoinedRoom &room, const QString &room_id);
|
||||
void addRoom(const QString &room_id);
|
||||
TimelineViewManager(QWidget *parent = 0);
|
||||
QWidget *getWidget() const { return container; }
|
||||
|
||||
void sync(const mtx::responses::Rooms &rooms);
|
||||
void clearAll() { views_.clear(); }
|
||||
void addRoom(const QString &room_id);
|
||||
|
||||
// Check if all the timelines have been loaded.
|
||||
bool hasLoaded() const;
|
||||
void clearAll() { models.clear(); }
|
||||
|
||||
static QString chooseRandomColor();
|
||||
Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; }
|
||||
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
|
||||
void openImageOverlay(QString mxcUrl,
|
||||
QString originalFilename,
|
||||
QString mimeType,
|
||||
qml_mtx_events::EventType eventType) const;
|
||||
void saveMedia(QString mxcUrl,
|
||||
QString originalFilename,
|
||||
QString mimeType,
|
||||
qml_mtx_events::EventType eventType) const;
|
||||
Q_INVOKABLE void cacheMedia(QString mxcUrl, QString mimeType);
|
||||
// Qml can only pass enum as int
|
||||
Q_INVOKABLE void openImageOverlay(QString mxcUrl,
|
||||
QString originalFilename,
|
||||
QString mimeType,
|
||||
int eventType) const
|
||||
{
|
||||
openImageOverlay(
|
||||
mxcUrl, originalFilename, mimeType, (qml_mtx_events::EventType)eventType);
|
||||
}
|
||||
Q_INVOKABLE void saveMedia(QString mxcUrl,
|
||||
QString originalFilename,
|
||||
QString mimeType,
|
||||
int eventType) const
|
||||
{
|
||||
saveMedia(mxcUrl, originalFilename, mimeType, (qml_mtx_events::EventType)eventType);
|
||||
}
|
||||
|
||||
signals:
|
||||
void clearRoomMessageCount(QString roomid);
|
||||
void updateRoomsLastMessage(const QString &user, const DescInfo &info);
|
||||
void updateRoomsLastMessage(QString roomid, const DescInfo &info);
|
||||
void activeTimelineChanged(TimelineModel *timeline);
|
||||
void initialSyncChanged(bool isInitialSync);
|
||||
void mediaCached(QString mxcUrl, QString cacheUrl);
|
||||
|
||||
public slots:
|
||||
void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|
||||
void removeTimelineEvent(const QString &room_id, const QString &event_id);
|
||||
void initWithMessages(const std::map<QString, mtx::responses::Timeline> &msgs);
|
||||
|
||||
void setHistoryView(const QString &room_id);
|
||||
void updateColorPalette();
|
||||
|
||||
void queueTextMessage(const QString &msg);
|
||||
void queueReplyMessage(const QString &reply, const RelatedInfo &related);
|
||||
void queueEmoteMessage(const QString &msg);
|
||||
|
@ -90,9 +101,17 @@ public slots:
|
|||
uint64_t dsize);
|
||||
|
||||
private:
|
||||
//! Check if the given room id is managed by a TimelineView.
|
||||
bool timelineViewExists(const QString &id) { return views_.find(id) != views_.end(); }
|
||||
#ifdef USE_QUICK_VIEW
|
||||
QQuickView *view;
|
||||
#else
|
||||
QQuickWidget *view;
|
||||
#endif
|
||||
QWidget *container;
|
||||
|
||||
QString active_room_;
|
||||
std::map<QString, QSharedPointer<TimelineView>> views_;
|
||||
MxcImageProvider *imgProvider;
|
||||
ColorImageProvider *colorImgProvider;
|
||||
|
||||
QHash<QString, QSharedPointer<TimelineModel>> models;
|
||||
TimelineModel *timeline_ = nullptr;
|
||||
bool isInitialSync_ = true;
|
||||
};
|
||||
|
|
|
@ -1,236 +0,0 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QBrush>
|
||||
#include <QDesktopServices>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "Logging.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include "timeline/widgets/AudioItem.h"
|
||||
|
||||
constexpr int MaxWidth = 400;
|
||||
constexpr int Height = 70;
|
||||
constexpr int IconRadius = 22;
|
||||
constexpr int IconDiameter = IconRadius * 2;
|
||||
constexpr int HorizontalPadding = 12;
|
||||
constexpr int TextPadding = 15;
|
||||
constexpr int ActionIconRadius = IconRadius - 4;
|
||||
|
||||
constexpr double VerticalPadding = Height - 2 * IconRadius;
|
||||
constexpr double IconYCenter = Height / 2;
|
||||
constexpr double IconXCenter = HorizontalPadding + IconRadius;
|
||||
|
||||
void
|
||||
AudioItem::init()
|
||||
{
|
||||
setMouseTracking(true);
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
setAttribute(Qt::WA_Hover, true);
|
||||
|
||||
playIcon_.addFile(":/icons/icons/ui/play-sign.png");
|
||||
pauseIcon_.addFile(":/icons/icons/ui/pause-symbol.png");
|
||||
|
||||
player_ = new QMediaPlayer;
|
||||
player_->setMedia(QUrl(url_));
|
||||
player_->setVolume(100);
|
||||
player_->setNotifyInterval(1000);
|
||||
|
||||
connect(player_, &QMediaPlayer::stateChanged, this, [this](QMediaPlayer::State state) {
|
||||
if (state == QMediaPlayer::StoppedState) {
|
||||
state_ = AudioState::Play;
|
||||
player_->setMedia(QUrl(url_));
|
||||
update();
|
||||
}
|
||||
});
|
||||
|
||||
setFixedHeight(Height);
|
||||
}
|
||||
|
||||
AudioItem::AudioItem(const mtx::events::RoomEvent<mtx::events::msg::Audio> &event, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, url_{QUrl(QString::fromStdString(event.content.url))}
|
||||
, text_{QString::fromStdString(event.content.body)}
|
||||
, event_{event}
|
||||
{
|
||||
readableFileSize_ = utils::humanReadableFileSize(event.content.info.size);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
AudioItem::AudioItem(const QString &url, const QString &filename, uint64_t size, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, url_{url}
|
||||
, text_{filename}
|
||||
{
|
||||
readableFileSize_ = utils::humanReadableFileSize(size);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
QSize
|
||||
AudioItem::sizeHint() const
|
||||
{
|
||||
return QSize(MaxWidth, Height);
|
||||
}
|
||||
|
||||
void
|
||||
AudioItem::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() != Qt::LeftButton)
|
||||
return;
|
||||
|
||||
auto point = event->pos();
|
||||
|
||||
// Click on the download icon.
|
||||
if (QRect(HorizontalPadding, VerticalPadding / 2, IconDiameter, IconDiameter)
|
||||
.contains(point)) {
|
||||
if (state_ == AudioState::Play) {
|
||||
state_ = AudioState::Pause;
|
||||
player_->play();
|
||||
} else {
|
||||
state_ = AudioState::Play;
|
||||
player_->pause();
|
||||
}
|
||||
|
||||
update();
|
||||
} else {
|
||||
filenameToSave_ = QFileDialog::getSaveFileName(this, tr("Save File"), text_);
|
||||
|
||||
if (filenameToSave_.isEmpty())
|
||||
return;
|
||||
|
||||
auto proxy = std::make_shared<MediaProxy>();
|
||||
connect(proxy.get(), &MediaProxy::fileDownloaded, this, &AudioItem::fileDownloaded);
|
||||
|
||||
http::client()->download(
|
||||
url_.toString().toStdString(),
|
||||
[proxy = std::move(proxy), url = url_](const std::string &data,
|
||||
const std::string &,
|
||||
const std::string &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->info("failed to retrieve m.audio content: {}",
|
||||
url.toString().toStdString());
|
||||
return;
|
||||
}
|
||||
|
||||
emit proxy->fileDownloaded(QByteArray(data.data(), data.size()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioItem::fileDownloaded(const QByteArray &data)
|
||||
{
|
||||
try {
|
||||
QFile file(filenameToSave_);
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
return;
|
||||
|
||||
file.write(data);
|
||||
file.close();
|
||||
} catch (const std::exception &e) {
|
||||
nhlog::ui()->warn("error while saving file: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AudioItem::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QFont font;
|
||||
font.setWeight(QFont::Medium);
|
||||
|
||||
QFontMetrics fm(font);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
|
||||
const int computedWidth = std::min(
|
||||
fm.width(text_) + 2 * IconRadius + VerticalPadding * 2 + TextPadding, (double)MaxWidth);
|
||||
#else
|
||||
const int computedWidth =
|
||||
std::min(fm.horizontalAdvance(text_) + 2 * IconRadius + VerticalPadding * 2 + TextPadding,
|
||||
(double)MaxWidth);
|
||||
#endif
|
||||
resize(computedWidth, Height);
|
||||
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void
|
||||
AudioItem::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
QFont font;
|
||||
font.setWeight(QFont::Medium);
|
||||
|
||||
QFontMetrics fm(font);
|
||||
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(QRectF(0, 0, width(), height()), 10, 10);
|
||||
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.fillPath(path, backgroundColor_);
|
||||
painter.drawPath(path);
|
||||
|
||||
QPainterPath circle;
|
||||
circle.addEllipse(QPoint(IconXCenter, IconYCenter), IconRadius, IconRadius);
|
||||
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.fillPath(circle, iconColor_);
|
||||
painter.drawPath(circle);
|
||||
|
||||
QIcon icon_;
|
||||
if (state_ == AudioState::Play)
|
||||
icon_ = playIcon_;
|
||||
else
|
||||
icon_ = pauseIcon_;
|
||||
|
||||
icon_.paint(&painter,
|
||||
QRect(IconXCenter - ActionIconRadius / 2,
|
||||
IconYCenter - ActionIconRadius / 2,
|
||||
ActionIconRadius,
|
||||
ActionIconRadius),
|
||||
Qt::AlignCenter,
|
||||
QIcon::Normal);
|
||||
|
||||
const int textStartX = HorizontalPadding + 2 * IconRadius + TextPadding;
|
||||
const int textStartY = VerticalPadding + fm.ascent() / 2;
|
||||
|
||||
// Draw the filename.
|
||||
QString elidedText = fm.elidedText(
|
||||
text_, Qt::ElideRight, width() - HorizontalPadding * 2 - TextPadding - 2 * IconRadius);
|
||||
|
||||
painter.setFont(font);
|
||||
painter.setPen(QPen(textColor_));
|
||||
painter.drawText(QPoint(textStartX, textStartY), elidedText);
|
||||
|
||||
// Draw the filesize.
|
||||
font.setWeight(QFont::Normal);
|
||||
painter.setFont(font);
|
||||
painter.setPen(QPen(textColor_));
|
||||
painter.drawText(QPoint(textStartX, textStartY + 1.5 * fm.ascent()), readableFileSize_);
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QEvent>
|
||||
#include <QIcon>
|
||||
#include <QMediaPlayer>
|
||||
#include <QMouseEvent>
|
||||
#include <QSharedPointer>
|
||||
#include <QWidget>
|
||||
|
||||
#include <mtx.hpp>
|
||||
|
||||
class AudioItem : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
|
||||
Q_PROPERTY(QColor iconColor WRITE setIconColor READ iconColor)
|
||||
Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
|
||||
|
||||
Q_PROPERTY(QColor durationBackgroundColor WRITE setDurationBackgroundColor READ
|
||||
durationBackgroundColor)
|
||||
Q_PROPERTY(QColor durationForegroundColor WRITE setDurationForegroundColor READ
|
||||
durationForegroundColor)
|
||||
|
||||
public:
|
||||
AudioItem(const mtx::events::RoomEvent<mtx::events::msg::Audio> &event,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
AudioItem(const QString &url,
|
||||
const QString &filename,
|
||||
uint64_t size,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
void setTextColor(const QColor &color) { textColor_ = color; }
|
||||
void setIconColor(const QColor &color) { iconColor_ = color; }
|
||||
void setBackgroundColor(const QColor &color) { backgroundColor_ = color; }
|
||||
|
||||
void setDurationBackgroundColor(const QColor &color) { durationBgColor_ = color; }
|
||||
void setDurationForegroundColor(const QColor &color) { durationFgColor_ = color; }
|
||||
|
||||
QColor textColor() const { return textColor_; }
|
||||
QColor iconColor() const { return iconColor_; }
|
||||
QColor backgroundColor() const { return backgroundColor_; }
|
||||
|
||||
QColor durationBackgroundColor() const { return durationBgColor_; }
|
||||
QColor durationForegroundColor() const { return durationFgColor_; }
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void fileDownloaded(const QByteArray &data);
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
||||
enum class AudioState
|
||||
{
|
||||
Play,
|
||||
Pause,
|
||||
};
|
||||
|
||||
AudioState state_ = AudioState::Play;
|
||||
|
||||
QUrl url_;
|
||||
QString text_;
|
||||
QString readableFileSize_;
|
||||
QString filenameToSave_;
|
||||
|
||||
mtx::events::RoomEvent<mtx::events::msg::Audio> event_;
|
||||
|
||||
QMediaPlayer *player_;
|
||||
|
||||
QIcon playIcon_;
|
||||
QIcon pauseIcon_;
|
||||
|
||||
QColor textColor_ = QColor("white");
|
||||
QColor iconColor_ = QColor("#38A3D8");
|
||||
QColor backgroundColor_ = QColor("#333");
|
||||
|
||||
QColor durationBgColor_ = QColor("black");
|
||||
QColor durationFgColor_ = QColor("blue");
|
||||
};
|
|
@ -1,221 +0,0 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QBrush>
|
||||
#include <QDesktopServices>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "Logging.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include "timeline/widgets/FileItem.h"
|
||||
|
||||
constexpr int MaxWidth = 400;
|
||||
constexpr int Height = 70;
|
||||
constexpr int IconRadius = 22;
|
||||
constexpr int IconDiameter = IconRadius * 2;
|
||||
constexpr int HorizontalPadding = 12;
|
||||
constexpr int TextPadding = 15;
|
||||
constexpr int DownloadIconRadius = IconRadius - 4;
|
||||
|
||||
constexpr double VerticalPadding = Height - 2 * IconRadius;
|
||||
constexpr double IconYCenter = Height / 2;
|
||||
constexpr double IconXCenter = HorizontalPadding + IconRadius;
|
||||
|
||||
void
|
||||
FileItem::init()
|
||||
{
|
||||
setMouseTracking(true);
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
setAttribute(Qt::WA_Hover, true);
|
||||
|
||||
icon_.addFile(":/icons/icons/ui/arrow-pointing-down.png");
|
||||
|
||||
setFixedHeight(Height);
|
||||
}
|
||||
|
||||
FileItem::FileItem(const mtx::events::RoomEvent<mtx::events::msg::File> &event, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, url_{QString::fromStdString(event.content.url)}
|
||||
, text_{QString::fromStdString(event.content.body)}
|
||||
, event_{event}
|
||||
{
|
||||
readableFileSize_ = utils::humanReadableFileSize(event.content.info.size);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
FileItem::FileItem(const QString &url, const QString &filename, uint64_t size, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, url_{url}
|
||||
, text_{filename}
|
||||
{
|
||||
readableFileSize_ = utils::humanReadableFileSize(size);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
void
|
||||
FileItem::openUrl()
|
||||
{
|
||||
if (url_.toString().isEmpty())
|
||||
return;
|
||||
|
||||
auto urlToOpen = utils::mxcToHttp(
|
||||
url_, QString::fromStdString(http::client()->server()), http::client()->port());
|
||||
|
||||
if (!QDesktopServices::openUrl(urlToOpen))
|
||||
nhlog::ui()->warn("Could not open url: {}", urlToOpen.toStdString());
|
||||
}
|
||||
|
||||
QSize
|
||||
FileItem::sizeHint() const
|
||||
{
|
||||
return QSize(MaxWidth, Height);
|
||||
}
|
||||
|
||||
void
|
||||
FileItem::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() != Qt::LeftButton)
|
||||
return;
|
||||
|
||||
auto point = event->pos();
|
||||
|
||||
// Click on the download icon.
|
||||
if (QRect(HorizontalPadding, VerticalPadding / 2, IconDiameter, IconDiameter)
|
||||
.contains(point)) {
|
||||
filenameToSave_ = QFileDialog::getSaveFileName(this, tr("Save File"), text_);
|
||||
|
||||
if (filenameToSave_.isEmpty())
|
||||
return;
|
||||
|
||||
auto proxy = std::make_shared<MediaProxy>();
|
||||
connect(proxy.get(), &MediaProxy::fileDownloaded, this, &FileItem::fileDownloaded);
|
||||
|
||||
http::client()->download(
|
||||
url_.toString().toStdString(),
|
||||
[proxy = std::move(proxy), url = url_](const std::string &data,
|
||||
const std::string &,
|
||||
const std::string &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::ui()->warn("failed to retrieve m.file content: {}",
|
||||
url.toString().toStdString());
|
||||
return;
|
||||
}
|
||||
|
||||
emit proxy->fileDownloaded(QByteArray(data.data(), data.size()));
|
||||
});
|
||||
} else {
|
||||
openUrl();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FileItem::fileDownloaded(const QByteArray &data)
|
||||
{
|
||||
try {
|
||||
QFile file(filenameToSave_);
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
return;
|
||||
|
||||
file.write(data);
|
||||
file.close();
|
||||
} catch (const std::exception &e) {
|
||||
nhlog::ui()->warn("Error while saving file to: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FileItem::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QFont font;
|
||||
font.setWeight(QFont::Medium);
|
||||
|
||||
QFontMetrics fm(font);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
|
||||
const int computedWidth = std::min(
|
||||
fm.width(text_) + 2 * IconRadius + VerticalPadding * 2 + TextPadding, (double)MaxWidth);
|
||||
#else
|
||||
const int computedWidth =
|
||||
std::min(fm.horizontalAdvance(text_) + 2 * IconRadius + VerticalPadding * 2 + TextPadding,
|
||||
(double)MaxWidth);
|
||||
#endif
|
||||
resize(computedWidth, Height);
|
||||
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void
|
||||
FileItem::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
QFont font;
|
||||
font.setWeight(QFont::Medium);
|
||||
|
||||
QFontMetrics fm(font);
|
||||
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(QRectF(0, 0, width(), height()), 10, 10);
|
||||
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.fillPath(path, backgroundColor_);
|
||||
painter.drawPath(path);
|
||||
|
||||
QPainterPath circle;
|
||||
circle.addEllipse(QPoint(IconXCenter, IconYCenter), IconRadius, IconRadius);
|
||||
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.fillPath(circle, iconColor_);
|
||||
painter.drawPath(circle);
|
||||
|
||||
icon_.paint(&painter,
|
||||
QRect(IconXCenter - DownloadIconRadius / 2,
|
||||
IconYCenter - DownloadIconRadius / 2,
|
||||
DownloadIconRadius,
|
||||
DownloadIconRadius),
|
||||
Qt::AlignCenter,
|
||||
QIcon::Normal);
|
||||
|
||||
const int textStartX = HorizontalPadding + 2 * IconRadius + TextPadding;
|
||||
const int textStartY = VerticalPadding + fm.ascent() / 2;
|
||||
|
||||
// Draw the filename.
|
||||
QString elidedText = fm.elidedText(
|
||||
text_, Qt::ElideRight, width() - HorizontalPadding * 2 - TextPadding - 2 * IconRadius);
|
||||
|
||||
painter.setFont(font);
|
||||
painter.setPen(QPen(textColor_));
|
||||
painter.drawText(QPoint(textStartX, textStartY), elidedText);
|
||||
|
||||
// Draw the filesize.
|
||||
font.setWeight(QFont::Normal);
|
||||
painter.setFont(font);
|
||||
painter.setPen(QPen(textColor_));
|
||||
painter.drawText(QPoint(textStartX, textStartY + 1.5 * fm.ascent()), readableFileSize_);
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QEvent>
|
||||
#include <QIcon>
|
||||
#include <QMouseEvent>
|
||||
#include <QSharedPointer>
|
||||
#include <QWidget>
|
||||
|
||||
#include <mtx.hpp>
|
||||
|
||||
class FileItem : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor)
|
||||
Q_PROPERTY(QColor iconColor WRITE setIconColor READ iconColor)
|
||||
Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor)
|
||||
|
||||
public:
|
||||
FileItem(const mtx::events::RoomEvent<mtx::events::msg::File> &event,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
FileItem(const QString &url,
|
||||
const QString &filename,
|
||||
uint64_t size,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
void setTextColor(const QColor &color) { textColor_ = color; }
|
||||
void setIconColor(const QColor &color) { iconColor_ = color; }
|
||||
void setBackgroundColor(const QColor &color) { backgroundColor_ = color; }
|
||||
|
||||
QColor textColor() const { return textColor_; }
|
||||
QColor iconColor() const { return iconColor_; }
|
||||
QColor backgroundColor() const { return backgroundColor_; }
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void fileDownloaded(const QByteArray &data);
|
||||
|
||||
private:
|
||||
void openUrl();
|
||||
void init();
|
||||
|
||||
QUrl url_;
|
||||
QString text_;
|
||||
QString readableFileSize_;
|
||||
QString filenameToSave_;
|
||||
|
||||
mtx::events::RoomEvent<mtx::events::msg::File> event_;
|
||||
|
||||
QIcon icon_;
|
||||
|
||||
QColor textColor_ = QColor("white");
|
||||
QColor iconColor_ = QColor("#38A3D8");
|
||||
QColor backgroundColor_ = QColor("#333");
|
||||
};
|
|
@ -1,267 +0,0 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QBrush>
|
||||
#include <QDesktopServices>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
#include <QUuid>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "Config.h"
|
||||
#include "ImageItem.h"
|
||||
#include "Logging.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "Utils.h"
|
||||
#include "dialogs/ImageOverlay.h"
|
||||
|
||||
void
|
||||
ImageItem::downloadMedia(const QUrl &url)
|
||||
{
|
||||
auto proxy = std::make_shared<MediaProxy>();
|
||||
connect(proxy.get(), &MediaProxy::imageDownloaded, this, &ImageItem::setImage);
|
||||
|
||||
http::client()->download(url.toString().toStdString(),
|
||||
[proxy = std::move(proxy), url](const std::string &data,
|
||||
const std::string &,
|
||||
const std::string &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn(
|
||||
"failed to retrieve image {}: {} {}",
|
||||
url.toString().toStdString(),
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
QPixmap img;
|
||||
img.loadFromData(QByteArray(data.data(), data.size()));
|
||||
|
||||
emit proxy->imageDownloaded(img);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
ImageItem::saveImage(const QString &filename, const QByteArray &data)
|
||||
{
|
||||
try {
|
||||
QFile file(filename);
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly))
|
||||
return;
|
||||
|
||||
file.write(data);
|
||||
file.close();
|
||||
} catch (const std::exception &e) {
|
||||
nhlog::ui()->warn("Error while saving file to: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ImageItem::init()
|
||||
{
|
||||
setMouseTracking(true);
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
setAttribute(Qt::WA_Hover, true);
|
||||
|
||||
downloadMedia(url_);
|
||||
}
|
||||
|
||||
ImageItem::ImageItem(const mtx::events::RoomEvent<mtx::events::msg::Image> &event, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, event_{event}
|
||||
{
|
||||
url_ = QString::fromStdString(event.content.url);
|
||||
text_ = QString::fromStdString(event.content.body);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
ImageItem::ImageItem(const QString &url, const QString &filename, uint64_t size, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, url_{url}
|
||||
, text_{filename}
|
||||
{
|
||||
Q_UNUSED(size);
|
||||
init();
|
||||
}
|
||||
|
||||
void
|
||||
ImageItem::openUrl()
|
||||
{
|
||||
if (url_.toString().isEmpty())
|
||||
return;
|
||||
|
||||
auto urlToOpen = utils::mxcToHttp(
|
||||
url_, QString::fromStdString(http::client()->server()), http::client()->port());
|
||||
|
||||
if (!QDesktopServices::openUrl(urlToOpen))
|
||||
nhlog::ui()->warn("could not open url: {}", urlToOpen.toStdString());
|
||||
}
|
||||
|
||||
QSize
|
||||
ImageItem::sizeHint() const
|
||||
{
|
||||
if (image_.isNull())
|
||||
return QSize(max_width_, bottom_height_);
|
||||
|
||||
return QSize(width_, height_);
|
||||
}
|
||||
|
||||
void
|
||||
ImageItem::setImage(const QPixmap &image)
|
||||
{
|
||||
image_ = image;
|
||||
scaled_image_ = utils::scaleDown(max_width_, max_height_, image_);
|
||||
|
||||
width_ = scaled_image_.width();
|
||||
height_ = scaled_image_.height();
|
||||
|
||||
setFixedSize(width_, height_);
|
||||
update();
|
||||
}
|
||||
|
||||
void
|
||||
ImageItem::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!isInteractive_) {
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->button() != Qt::LeftButton)
|
||||
return;
|
||||
|
||||
if (image_.isNull()) {
|
||||
openUrl();
|
||||
return;
|
||||
}
|
||||
|
||||
if (textRegion_.contains(event->pos())) {
|
||||
openUrl();
|
||||
} else {
|
||||
auto imgDialog = new dialogs::ImageOverlay(image_);
|
||||
imgDialog->show();
|
||||
connect(imgDialog, &dialogs::ImageOverlay::saving, this, &ImageItem::saveAs);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ImageItem::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
if (!image_)
|
||||
return QWidget::resizeEvent(event);
|
||||
|
||||
scaled_image_ = utils::scaleDown(max_width_, max_height_, image_);
|
||||
|
||||
width_ = scaled_image_.width();
|
||||
height_ = scaled_image_.height();
|
||||
|
||||
setFixedSize(width_, height_);
|
||||
}
|
||||
|
||||
void
|
||||
ImageItem::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
QFont font;
|
||||
|
||||
QFontMetrics metrics(font);
|
||||
const int fontHeight = metrics.height() + metrics.ascent();
|
||||
|
||||
if (image_.isNull()) {
|
||||
QString elidedText = metrics.elidedText(text_, Qt::ElideRight, max_width_ - 10);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
|
||||
setFixedSize(metrics.width(elidedText), fontHeight);
|
||||
#else
|
||||
setFixedSize(metrics.horizontalAdvance(elidedText), fontHeight);
|
||||
#endif
|
||||
painter.setFont(font);
|
||||
painter.setPen(QPen(QColor(66, 133, 244)));
|
||||
painter.drawText(QPoint(0, fontHeight / 2), elidedText);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
imageRegion_ = QRectF(0, 0, width_, height_);
|
||||
|
||||
QPainterPath path;
|
||||
path.addRoundedRect(imageRegion_, 5, 5);
|
||||
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.fillPath(path, scaled_image_);
|
||||
painter.drawPath(path);
|
||||
|
||||
// Bottom text section
|
||||
if (isInteractive_ && underMouse()) {
|
||||
const int textBoxHeight = fontHeight / 2 + 6;
|
||||
|
||||
textRegion_ = QRectF(0, height_ - textBoxHeight, width_, textBoxHeight);
|
||||
|
||||
QPainterPath textPath;
|
||||
textPath.addRoundedRect(textRegion_, 0, 0);
|
||||
|
||||
painter.fillPath(textPath, QColor(40, 40, 40, 140));
|
||||
|
||||
QString elidedText = metrics.elidedText(text_, Qt::ElideRight, width_ - 10);
|
||||
|
||||
font.setWeight(QFont::Medium);
|
||||
painter.setFont(font);
|
||||
painter.setPen(QPen(QColor(Qt::white)));
|
||||
|
||||
textRegion_.adjust(5, 0, 5, 0);
|
||||
painter.drawText(textRegion_, Qt::AlignVCenter, elidedText);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ImageItem::saveAs()
|
||||
{
|
||||
auto filename = QFileDialog::getSaveFileName(this, tr("Save image"), text_);
|
||||
|
||||
if (filename.isEmpty())
|
||||
return;
|
||||
|
||||
const auto url = url_.toString().toStdString();
|
||||
|
||||
auto proxy = std::make_shared<MediaProxy>();
|
||||
connect(proxy.get(), &MediaProxy::imageSaved, this, &ImageItem::saveImage);
|
||||
|
||||
http::client()->download(
|
||||
url,
|
||||
[proxy = std::move(proxy), filename, url](const std::string &data,
|
||||
const std::string &,
|
||||
const std::string &,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
nhlog::net()->warn("failed to retrieve image {}: {} {}",
|
||||
url,
|
||||
err->matrix_error.error,
|
||||
static_cast<int>(err->status_code));
|
||||
return;
|
||||
}
|
||||
|
||||
emit proxy->imageSaved(filename, QByteArray(data.data(), data.size()));
|
||||
});
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QSharedPointer>
|
||||
#include <QWidget>
|
||||
|
||||
#include <mtx.hpp>
|
||||
|
||||
namespace dialogs {
|
||||
class ImageOverlay;
|
||||
}
|
||||
|
||||
class ImageItem : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ImageItem(const mtx::events::RoomEvent<mtx::events::msg::Image> &event,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
ImageItem(const QString &url,
|
||||
const QString &filename,
|
||||
uint64_t size,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
public slots:
|
||||
//! Show a save as dialog for the image.
|
||||
void saveAs();
|
||||
void setImage(const QPixmap &image);
|
||||
void saveImage(const QString &filename, const QByteArray &data);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
//! Whether the user can interact with the displayed image.
|
||||
bool isInteractive_ = true;
|
||||
|
||||
private:
|
||||
void init();
|
||||
void openUrl();
|
||||
void downloadMedia(const QUrl &url);
|
||||
|
||||
int max_width_ = 500;
|
||||
int max_height_ = 300;
|
||||
|
||||
int width_;
|
||||
int height_;
|
||||
|
||||
QPixmap scaled_image_;
|
||||
QPixmap image_;
|
||||
|
||||
QUrl url_;
|
||||
QString text_;
|
||||
|
||||
int bottom_height_ = 30;
|
||||
|
||||
QRectF textRegion_;
|
||||
QRectF imageRegion_;
|
||||
|
||||
mtx::events::RoomEvent<mtx::events::msg::Image> event_;
|
||||
};
|
||||
|
||||
class StickerItem : public ImageItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
StickerItem(const mtx::events::Sticker &event, QWidget *parent = nullptr)
|
||||
: ImageItem{QString::fromStdString(event.content.url),
|
||||
QString::fromStdString(event.content.body),
|
||||
event.content.info.size,
|
||||
parent}
|
||||
, event_{event}
|
||||
{
|
||||
isInteractive_ = false;
|
||||
setCursor(Qt::ArrowCursor);
|
||||
setMouseTracking(false);
|
||||
setAttribute(Qt::WA_Hover, false);
|
||||
}
|
||||
|
||||
private:
|
||||
mtx::events::Sticker event_;
|
||||
};
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Config.h"
|
||||
#include "MatrixClient.h"
|
||||
#include "Utils.h"
|
||||
#include "timeline/widgets/VideoItem.h"
|
||||
|
||||
void
|
||||
VideoItem::init()
|
||||
{
|
||||
url_ = utils::mxcToHttp(
|
||||
url_, QString::fromStdString(http::client()->server()), http::client()->port());
|
||||
}
|
||||
|
||||
VideoItem::VideoItem(const mtx::events::RoomEvent<mtx::events::msg::Video> &event, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, url_{QString::fromStdString(event.content.url)}
|
||||
, text_{QString::fromStdString(event.content.body)}
|
||||
, event_{event}
|
||||
{
|
||||
readableFileSize_ = utils::humanReadableFileSize(event.content.info.size);
|
||||
|
||||
init();
|
||||
|
||||
auto layout = new QVBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
layout->setSpacing(0);
|
||||
|
||||
QString link = QString("<a href=%1>%2</a>").arg(url_.toString()).arg(text_);
|
||||
|
||||
label_ = new QLabel(link, this);
|
||||
label_->setMargin(0);
|
||||
label_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
|
||||
label_->setOpenExternalLinks(true);
|
||||
|
||||
layout->addWidget(label_);
|
||||
}
|
||||
|
||||
VideoItem::VideoItem(const QString &url, const QString &filename, uint64_t size, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, url_{url}
|
||||
, text_{filename}
|
||||
{
|
||||
readableFileSize_ = utils::humanReadableFileSize(size);
|
||||
|
||||
init();
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QEvent>
|
||||
#include <QLabel>
|
||||
#include <QSharedPointer>
|
||||
#include <QUrl>
|
||||
#include <QWidget>
|
||||
|
||||
#include <mtx.hpp>
|
||||
|
||||
class VideoItem : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
VideoItem(const mtx::events::RoomEvent<mtx::events::msg::Video> &event,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
VideoItem(const QString &url,
|
||||
const QString &filename,
|
||||
uint64_t size,
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
private:
|
||||
void init();
|
||||
|
||||
QUrl url_;
|
||||
QString text_;
|
||||
QString readableFileSize_;
|
||||
|
||||
QLabel *label_;
|
||||
|
||||
mtx::events::RoomEvent<mtx::events::msg::Video> event_;
|
||||
};
|
|
@ -101,7 +101,7 @@ Avatar::setIcon(const QIcon &icon)
|
|||
void
|
||||
Avatar::paintEvent(QPaintEvent *)
|
||||
{
|
||||
bool rounded = QSettings().value("user/avatar/circles", true).toBool();
|
||||
bool rounded = QSettings().value("user/avatar_circles", true).toBool();
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
|
Loading…
Reference in a new issue