Compare commits
82 Commits
work/tobia
...
work/jz/fl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae155805e9 | ||
|
|
70bff21632 | ||
|
|
f58c390a47 | ||
|
|
089a9abcb4 | ||
|
|
bf1c76d0a6 | ||
|
|
879da627b1 | ||
|
|
9b93eb44d5 | ||
|
|
b30220eca9 | ||
|
|
d270d4e5e1 | ||
|
|
21da6cb0f4 | ||
|
|
6ac75df935 | ||
|
|
f29781349c | ||
|
|
bb776d5c2b | ||
|
|
6cfab9e3ea | ||
|
|
6373186c15 | ||
|
|
e342de3bc1 | ||
|
|
4cd7b69ea5 | ||
|
|
988e8529da | ||
|
|
6a32d1e961 | ||
|
|
0552c798fb | ||
|
|
a53ad41879 | ||
|
|
92351edcd0 | ||
|
|
878eb48cb0 | ||
|
|
053ca6bed8 | ||
|
|
78ae14ab2f | ||
|
|
5fdc2ad765 | ||
|
|
b75dbe8d5c | ||
|
|
eaf4663c84 | ||
|
|
64b8cd5bcc | ||
|
|
482d61ee47 | ||
|
|
276dcce95e | ||
|
|
217f9e2e02 | ||
|
|
f40a0a6f5f | ||
|
|
9bd67acc2f | ||
|
|
e87da0feb0 | ||
|
|
2608d879fa | ||
|
|
6ab61fd41f | ||
|
|
30dd6297ee | ||
|
|
ce02183f82 | ||
|
|
7c74a6cbe1 | ||
|
|
e6a11b2ad8 | ||
|
|
158942d1b5 | ||
|
|
aaa97ec029 | ||
|
|
882ead5715 | ||
|
|
ab4519dedd | ||
|
|
c3fd2428a2 | ||
|
|
fbb4b962fa | ||
|
|
9bf65de649 | ||
|
|
75f069cb7d | ||
|
|
87d50125ab | ||
|
|
dc2cf21cb8 | ||
|
|
bae14ecd35 | ||
|
|
b48c1c3b80 | ||
|
|
0f9eb4beeb | ||
|
|
0ab8624d79 | ||
|
|
e872c934c3 | ||
|
|
c3d5d18aae | ||
|
|
ff5853a850 | ||
|
|
f772906324 | ||
|
|
07eabb2dc1 | ||
|
|
b3c88763a4 | ||
|
|
f7081f8829 | ||
|
|
ceef2167fd | ||
|
|
1dcfd94328 | ||
|
|
a1aa0804e2 | ||
|
|
77176478eb | ||
|
|
bf4ebfa7a8 | ||
|
|
6e7d622b41 | ||
|
|
8398b7d24d | ||
|
|
aef9b7375a | ||
|
|
ba45318b56 | ||
|
|
7d4f8780ad | ||
|
|
b504c990f8 | ||
|
|
e07b876677 | ||
|
|
a0bfd34951 | ||
|
|
b173714bbe | ||
|
|
db4021b601 | ||
|
|
2f46fd1d2c | ||
|
|
1f85f848e2 | ||
|
|
d7e0954e86 | ||
|
|
1671e05d12 | ||
|
|
33c55d1563 |
@@ -2,7 +2,7 @@
|
||||
"id": "org.kde.neochat",
|
||||
"branch": "master",
|
||||
"runtime": "org.kde.Platform",
|
||||
"runtime-version": "6.6",
|
||||
"runtime-version": "6.7",
|
||||
"sdk": "org.kde.Sdk",
|
||||
"command": "neochat",
|
||||
"tags": [
|
||||
|
||||
@@ -85,6 +85,7 @@ if(ANDROID)
|
||||
else()
|
||||
find_package(Qt6 ${QT_MIN_VERSION} COMPONENTS Widgets)
|
||||
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle KIO WindowSystem StatusNotifierItem)
|
||||
find_package(KF6SyntaxHighlighting ${KF_MIN_VERSION} REQUIRED)
|
||||
set_package_properties(KF6QQC2DesktopStyle PROPERTIES
|
||||
TYPE RUNTIME
|
||||
)
|
||||
|
||||
@@ -529,6 +529,19 @@ void TextHandlerTest::componentOutput_data()
|
||||
QTest::newRow("inline code single block") << QStringLiteral("<code>https://kde.org</code>")
|
||||
<< QList<MessageComponent>{
|
||||
MessageComponent{MessageComponentType::Text, QStringLiteral("<code>https://kde.org</code>"), {}}};
|
||||
QTest::newRow("long start tag")
|
||||
<< QStringLiteral(
|
||||
"Ah, you mean something like<br/><pre data-md=\"```\"><code class=\"language-qml\"># main.qml\nimport CustomQml\n...\nControls.TextField { id: "
|
||||
"someField }\nCustomQml {\n someTextProperty: someField.text\n}\n</code></pre>Sure you can, it's still local to the same file where you "
|
||||
"defined the id")
|
||||
<< QList<MessageComponent>{
|
||||
MessageComponent{MessageComponentType::Text, QStringLiteral("Ah, you mean something like"), {}},
|
||||
MessageComponent{
|
||||
MessageComponentType::Code,
|
||||
QStringLiteral(
|
||||
"# main.qml\nimport CustomQml\n...\nControls.TextField { id: someField }\nCustomQml {\n someTextProperty: someField.text\n}"),
|
||||
QVariantMap{{QStringLiteral("class"), QStringLiteral("qml")}}},
|
||||
MessageComponent{MessageComponentType::Text, QStringLiteral("Sure you can, it's still local to the same file where you defined the id"), {}}};
|
||||
}
|
||||
|
||||
void TextHandlerTest::componentOutput()
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<name xml:lang="it">NeoChat</name>
|
||||
<name xml:lang="ka">NeoChat</name>
|
||||
<name xml:lang="ko">NeoChat</name>
|
||||
<name xml:lang="lv">NeoChat</name>
|
||||
<name xml:lang="nl">NeoChat</name>
|
||||
<name xml:lang="nn">NeoChat</name>
|
||||
<name xml:lang="pa">ਨਿਓ-ਚੈਟ</name>
|
||||
@@ -64,6 +65,7 @@
|
||||
<summary xml:lang="it">Conversa con i tuoi contatti su matrix</summary>
|
||||
<summary xml:lang="ka">ესაუბრეთ მეგობრებს Matrix-ზე</summary>
|
||||
<summary xml:lang="ko">Matrix를 사용하여 친구들과 대화하기</summary>
|
||||
<summary xml:lang="lv">Tērzējiet ar saviem draugiem „Matrix“ tīklā</summary>
|
||||
<summary xml:lang="nl">Met uw vrienden chatten op matrix</summary>
|
||||
<summary xml:lang="nn">Prat med vennar på Matrix</summary>
|
||||
<summary xml:lang="pl">Rozmawiaj ze swoimi znajomymi w Matriksie</summary>
|
||||
@@ -87,6 +89,7 @@
|
||||
<p xml:lang="ia">NeoChat es un app de conversation que te permitte prender avantage plen del rete Matrix. Il te forni un modo secur de inviar messages de texto, videos e files audio a tui familia, collegas e amicos.</p>
|
||||
<p xml:lang="it">NeoChat è un'applicazione di chat che ti consente di sfruttare appieno la rete Matrix. Ti fornisce un modo sicuro per inviare messaggi di testo, video e file audio a familiari, colleghi e amici.</p>
|
||||
<p xml:lang="ka">NeoChat ჩატის აპია, რომელიც საშუალება გაძლევთ, Matrix-ის ქსელის საშუალებები ბოლომდე გამოიყენოთ. ის გაძლევთ უსაფრთხო გზას, გააგზავნოთ ტექსტური შეტყობინებები, ვიდეოებ და აუდიოფაილები თქვენს ოჯახთან, კოლეგებთან და მეგობრებთან.</p>
|
||||
<p xml:lang="lv">„NeoChat“ ir tērzēšanas programma, kas ļauj pilnvērtīgi izmantot „Matrix“ tīklu. Tā sniedz drošu veidu teksta ziņu, video un audio sūtīšanai ģimenes locekļiem, kolēģiem un draugiem.</p>
|
||||
<p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p>
|
||||
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
|
||||
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
|
||||
@@ -110,6 +113,7 @@
|
||||
<p xml:lang="it">NeoChat mira ad essere un'applicazione completa per le specifiche Matrix. Pertanto, sono supportati tutti gli elementi dell'attuale specifica stabile con le notevoli eccezioni di VoIP, conversazioni e alcuni aspetti della cifratura end-to-end. Ci sono alcune altre piccole omissioni dovute al fatto che le specifiche Matrix sono in continua evoluzione, ma l'obiettivo rimane quello di fornire un eventuale supporto per l'intera specifica.</p>
|
||||
<p xml:lang="ka">NeoChat მიზნად ისახავს Matrix სპეციფიკაციის სრული განხორციელება ჰქონდეს. როგორც ასეთი, ყველაფერი მიმდინარე სპეციფიკაციიდან, VoIP-ის, ძაფებისა და გამჭოლი დაშიფვრის ზოგიერთი ასპექტის გარდა, მხარდაჭერილია. შეძლება ასევე იყოს მცირე ლაფსუსებიც იმის გამო, რომ Matrix-ის სპეციფიკაცია მუდმივად ვითარდება, მაგრამ ჩვენი მიზანი მისი სრული მხარდაჭერაა.</p>
|
||||
<p xml:lang="ko">NeoChat은 Matrix 표준을 따르는 프로그램을 목표로 합니다. 현재 안정 버전의 표준에서 제공하는 기능의 대부분을 지원하며, VoIP, 스레드, 일부 종단간 암호화와 같은 기능은 아직 지원하지 않습니다. Matrix 표준은 계속하여 진화 중이기 때문에 일부 기능이 빠져 있을 수도 있지만 장기적으로는 전체 표준을 지원하는 것이 목표입니다.</p>
|
||||
<p xml:lang="lv">„NeoChat“ mērķis ir piedāvāt plašas iespējas atbilstoši „Matrix“ specifikācijai. Līdz ar to programma atbalsta visu pašreizējā stabilajā specifikācijā, izņemot VoIP, pavedienus un dažos aspektos galšifrēšanu. Pastāv citas atsevišķas sīkas neieviestas daļas, jo „Matrix“ specifikācija nepārtraukti attīstās, tomēr mērķis ir ar laiku nodrošināt atbalstu pilnai specifikācijai.</p>
|
||||
<p xml:lang="nl">NeoChat richt zich op het volledig bieden van alle mogelijkheden van de Matrix-specificatie. Alles in de huidige stabiele specificatie met merkbare uitzondering van VoIP, gekoppelde discussies en sommige aspecten van eind-tot-eind versleuteling worden ondersteund. Er zijn een paar andere kleinere omissies vanwege het feit dat de Matrix specificatie constant evolueert maar het doel blijft het eventueel bieden van ondersteuning van de gehele specificatie.</p>
|
||||
<p xml:lang="nn">NeoChat har som mål å støtta all funksjonalitet i Matrix-spesifikasjonen. Førebels er alt i den gjeldande stabile spesifikasjonen støtta, med unntak av VoIP, trådar og nokre delar av ende-til-kryptering. Det finst òg andre småting som ikkje er støtta, sidan Matrix-spesifikasjon er i stadig endring, men målet er altså støtte for alt.</p>
|
||||
<p xml:lang="pl">NeoChat w zamyśle ma być pełnowartościową aplikacją wg wytycznych Matriksa. Z tego powodu, wszystko, co jest obecnie w stabilnych wytycznych z pominięciem VoIP, wątków i niektórych części szyfrowania Użytkownik-do-Użytkownika są obecnie obsługiwane. Pominięto też kilka mniejszych rzeczy ze względu na ciągły rozwój wytycznych Matriksa, lecz celem nadal jest zapewnienie obsługi wszystkich wytycznych.</p>
|
||||
@@ -136,6 +140,7 @@
|
||||
<p xml:lang="it">A causa della natura dello sviluppo delle specifiche Matrix, NeoChat supporta anche numerose funzionalità instabili. Attualmente queste sono:</p>
|
||||
<p xml:lang="ka">Matrix-ის სპეციფიკაციის განვითარების ბუნების გამო NeoChat-ს ასევე აქვს უამრავი არასტაბილური ფუნქციაც. ახლა ისინია:</p>
|
||||
<p xml:lang="ko">Matrix 표준 개발의 특징으로 인하여 NeoChat은 일부 실험적인 기능을 지원합니다. 현재 지원하는 기능은 다음과 같습니다.</p>
|
||||
<p xml:lang="lv">„Matrix“ specifikācijas veida dēļ „NeoChat“ attīstība atbalsta arī vairākas nestabilas iespējas, šobrīd šādas ir:</p>
|
||||
<p xml:lang="nl">Vanwege de aard van de ontwikkeling van de Matrix specificatie ondersteunt NeoChat ook talloze onstabiele mogelijkheden. Dit zijn nu:</p>
|
||||
<p xml:lang="nn">På grunn av måten Matrix-spesifikasjonen vert utvikla på, støttar NeoChat òg nokre uferdige funksjonar:</p>
|
||||
<p xml:lang="pl">Ze względu na sposób rozwoju Matriksa, NeoChat obsługuje także kilka niestabilnych możliwości. Obecnie są to:</p>
|
||||
@@ -164,6 +169,7 @@
|
||||
<li xml:lang="it">Sondaggi - MSC3381</li>
|
||||
<li xml:lang="ka">Polls - MSC3381</li>
|
||||
<li xml:lang="ko">투표 - MSC3381</li>
|
||||
<li xml:lang="lv">Aptaujas — MSC3381</li>
|
||||
<li xml:lang="nl">Polls - MSC3381</li>
|
||||
<li xml:lang="nn">Avstemmingar – MSC3381</li>
|
||||
<li xml:lang="pl">Ankiety - MSC3381</li>
|
||||
@@ -191,6 +197,7 @@
|
||||
<li xml:lang="it">Pacchetti di adesivi - MSC2545</li>
|
||||
<li xml:lang="ka">სტიკერების პაკეტები - MSC2545</li>
|
||||
<li xml:lang="ko">스티커 팩 - MSC2545</li>
|
||||
<li xml:lang="lv">Uzlīmju pakas — MSC2545</li>
|
||||
<li xml:lang="nl">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="nn">Klistremerke-pakkar – MSC2545</li>
|
||||
<li xml:lang="pl">Paczki naklejek - MSC2545</li>
|
||||
@@ -218,6 +225,7 @@
|
||||
<li xml:lang="it">Località eventi - MSC3488</li>
|
||||
<li xml:lang="ka">მდებარეობის მოვლენები - MSC3488</li>
|
||||
<li xml:lang="ko">위치 이벤트 - MSC3488</li>
|
||||
<li xml:lang="lv">Atrašanās vietas notikumi — MSC3488</li>
|
||||
<li xml:lang="nl">Locatie gebeurtenissen - MSC3488</li>
|
||||
<li xml:lang="nn">Posisjonshendingar – MSC3488</li>
|
||||
<li xml:lang="pl">Wydarzenia w miejscach - MSC3488</li>
|
||||
@@ -278,6 +286,7 @@
|
||||
<caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption>
|
||||
<caption xml:lang="ka">მთავარი ხედი სურათების სიით, ჩატით და ოთახის ინფორმაციით</caption>
|
||||
<caption xml:lang="ko">대화방 목록, 채팅, 대화방 정보가 표시된 주 보기</caption>
|
||||
<caption xml:lang="lv">Pamata skats ar istabu sarakstu, tērzēšanu un istabas informāciju</caption>
|
||||
<caption xml:lang="nl">Hoofdweergave met lijst met rooms, chat en roominformatie</caption>
|
||||
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
|
||||
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
|
||||
@@ -303,6 +312,7 @@
|
||||
<caption xml:lang="ia">Discoperi nove communitate con Matrix Spaces (Spatios de Matrix)</caption>
|
||||
<caption xml:lang="it">Scopri nuove comunità con Matrix Spaces</caption>
|
||||
<caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption>
|
||||
<caption xml:lang="lv">Atklājiet jaunas kopienas ar „Matrix“ telpām</caption>
|
||||
<caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption>
|
||||
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
|
||||
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
|
||||
@@ -335,6 +345,7 @@
|
||||
<caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption>
|
||||
<caption xml:lang="ka">მთავარი ხედი სურათების სიით, ჩატით და ოთახის ინფორმაციით</caption>
|
||||
<caption xml:lang="ko">대화방 목록, 채팅, 대화방 정보가 표시된 주 보기</caption>
|
||||
<caption xml:lang="lv">Pamata skats ar istabu sarakstu, tērzēšanu un istabas informāciju</caption>
|
||||
<caption xml:lang="nl">Hoofdweergave met lijst met rooms, chat en roominformatie</caption>
|
||||
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
|
||||
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
|
||||
@@ -365,6 +376,7 @@
|
||||
<caption xml:lang="it">Schermata di accesso</caption>
|
||||
<caption xml:lang="ka">შესვლის ეკრანი</caption>
|
||||
<caption xml:lang="ko">로그인 화면</caption>
|
||||
<caption xml:lang="lv">Ierakstīšanās logs</caption>
|
||||
<caption xml:lang="nl">Aanmeldscherm</caption>
|
||||
<caption xml:lang="nn">Innloggingsbilete</caption>
|
||||
<caption xml:lang="pl">Ekran logowania</caption>
|
||||
|
||||
@@ -26,6 +26,7 @@ Name[it]=NeoChat
|
||||
Name[ka]=NeoChat
|
||||
Name[ko]=NeoChat
|
||||
Name[lt]=NeoChat
|
||||
Name[lv]=NeoChat
|
||||
Name[nl]=NeoChat
|
||||
Name[nn]=NeoChat
|
||||
Name[pa]=ਨਿਓ-ਚੈਟ
|
||||
@@ -66,6 +67,7 @@ GenericName[it]=Client Matrix
|
||||
GenericName[ka]=Matrix -ის კლიენტი
|
||||
GenericName[ko]=Matrix 클라이언트
|
||||
GenericName[lt]=Matrix kliento programa
|
||||
GenericName[lv]=„Matrix“ klients
|
||||
GenericName[nl]=Matrix-client
|
||||
GenericName[nn]=Matrix-klient
|
||||
GenericName[pa]=ਮੈਟਰਿਕਸ ਕਲਾਈਂਟ
|
||||
@@ -105,6 +107,7 @@ Comment[it]=Client per il protocollo Matrix
|
||||
Comment[ka]=კლიენტი Matrix-ის პროტოკოლისთვის
|
||||
Comment[ko]=Matrix 프로토콜용 클라이언트
|
||||
Comment[lt]=Matrix protokolo kliento programa
|
||||
Comment[lv]=Klients „Matrix“ protokolam
|
||||
Comment[nl]=Client voor het Matrix-protocol
|
||||
Comment[nn]=Klient for Matrix-protokollen
|
||||
Comment[pa]=ਮੈਟਰਿਕਸ ਪਰੋਟੋਕਾਲ ਲਈ ਕਲਾਈਂਟ ਹੈ
|
||||
|
||||
3598
po/ar/neochat.po
3598
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
3397
po/ast/neochat.po
3397
po/ast/neochat.po
File diff suppressed because it is too large
Load Diff
3780
po/az/neochat.po
3780
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
3534
po/ca/neochat.po
3534
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3431
po/cs/neochat.po
3431
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
3543
po/da/neochat.po
3543
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
3661
po/de/neochat.po
3661
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
3678
po/el/neochat.po
3678
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
3610
po/en_GB/neochat.po
3610
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
3465
po/eo/neochat.po
3465
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
3533
po/es/neochat.po
3533
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
3504
po/eu/neochat.po
3504
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
3554
po/fi/neochat.po
3554
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
3544
po/fr/neochat.po
3544
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
3554
po/hu/neochat.po
3554
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
3510
po/ia/neochat.po
3510
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
3628
po/id/neochat.po
3628
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
3645
po/ie/neochat.po
3645
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
3544
po/it/neochat.po
3544
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
3393
po/ja/neochat.po
3393
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
3496
po/ka/neochat.po
3496
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
3535
po/ko/neochat.po
3535
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
3405
po/lt/neochat.po
3405
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
5166
po/lv/neochat.po
Normal file
5166
po/lv/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
3521
po/nl/neochat.po
3521
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
3561
po/nn/neochat.po
3561
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
3734
po/pa/neochat.po
3734
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
3520
po/pl/neochat.po
3520
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
3622
po/pt/neochat.po
3622
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
3778
po/pt_BR/neochat.po
3778
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
3618
po/ru/neochat.po
3618
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
3740
po/sk/neochat.po
3740
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
3508
po/sl/neochat.po
3508
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
3602
po/sv/neochat.po
3602
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
3503
po/ta/neochat.po
3503
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
3515
po/tok/neochat.po
3515
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
3501
po/tr/neochat.po
3501
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
3535
po/uk/neochat.po
3535
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
3409
po/zh_CN/neochat.po
3409
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
3502
po/zh_TW/neochat.po
3502
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -126,6 +126,7 @@ add_library(neochat STATIC
|
||||
events/pollevent.cpp
|
||||
pollhandler.cpp
|
||||
utils.h
|
||||
utils.cpp
|
||||
registration.cpp
|
||||
neochatconnection.cpp
|
||||
neochatconnection.h
|
||||
@@ -171,9 +172,16 @@ add_library(neochat STATIC
|
||||
models/statekeysmodel.h
|
||||
sharehandler.cpp
|
||||
sharehandler.h
|
||||
models/roomtreeitem.cpp
|
||||
models/roomtreeitem.h
|
||||
)
|
||||
|
||||
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
||||
QT_QML_SINGLETON_TYPE TRUE
|
||||
)
|
||||
|
||||
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat
|
||||
QML_FILES
|
||||
qml/main.qml
|
||||
qml/AccountMenu.qml
|
||||
@@ -187,18 +195,12 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/UserInfo.qml
|
||||
qml/UserInfoDesktop.qml
|
||||
qml/RoomPage.qml
|
||||
qml/RoomWindow.qml
|
||||
qml/ExploreRoomsPage.qml
|
||||
qml/ManualRoomDialog.qml
|
||||
qml/ExplorerDelegate.qml
|
||||
qml/InviteUserPage.qml
|
||||
qml/ImageEditorPage.qml
|
||||
qml/WelcomePage.qml
|
||||
qml/General.qml
|
||||
qml/RoomSecurity.qml
|
||||
qml/PushNotification.qml
|
||||
qml/Categories.qml
|
||||
qml/Permissions.qml
|
||||
qml/NeochatMaximizeComponent.qml
|
||||
qml/FancyEffectsContainer.qml
|
||||
qml/TypingPane.qml
|
||||
@@ -210,8 +212,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/CompletionMenu.qml
|
||||
qml/PieProgressBar.qml
|
||||
qml/QuickFormatBar.qml
|
||||
qml/RoomData.qml
|
||||
qml/ServerData.qml
|
||||
qml/EmojiPicker.qml
|
||||
qml/LoginStep.qml
|
||||
qml/Login.qml
|
||||
@@ -245,26 +245,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/FileDelegateContextMenu.qml
|
||||
qml/MessageSourceSheet.qml
|
||||
qml/ReportSheet.qml
|
||||
qml/SettingsPage.qml
|
||||
qml/ThemeRadioButton.qml
|
||||
qml/ColorScheme.qml
|
||||
qml/GeneralSettingsPage.qml
|
||||
qml/EmoticonsPage.qml
|
||||
qml/EmoticonEditorPage.qml
|
||||
qml/EmoticonFormCard.qml
|
||||
qml/GlobalNotificationsPage.qml
|
||||
qml/NotificationRuleItem.qml
|
||||
qml/AppearanceSettingsPage.qml
|
||||
qml/AccountsPage.qml
|
||||
qml/AccountEditorPage.qml
|
||||
qml/DevicesPage.qml
|
||||
qml/DeviceDelegate.qml
|
||||
qml/DevicesCard.qml
|
||||
qml/About.qml
|
||||
qml/AboutKDE.qml
|
||||
qml/SonnetConfigPage.qml
|
||||
qml/NetworkProxyPage.qml
|
||||
qml/DevtoolsPage.qml
|
||||
qml/ConfirmEncryptionDialog.qml
|
||||
qml/RemoveSheet.qml
|
||||
qml/BanSheet.qml
|
||||
@@ -292,7 +272,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/SpaceHierarchyDelegate.qml
|
||||
qml/RemoveChildDialog.qml
|
||||
qml/SelectParentDialog.qml
|
||||
qml/Security.qml
|
||||
qml/QrCodeMaximizeComponent.qml
|
||||
qml/SelectSpacesDialog.qml
|
||||
qml/AttachDialog.qml
|
||||
@@ -305,16 +284,21 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/RoomTreeSection.qml
|
||||
qml/DelegateContextMenu.qml
|
||||
qml/ShareDialog.qml
|
||||
qml/FeatureFlagPage.qml
|
||||
qml/IgnoredUsersDialog.qml
|
||||
qml/AccountData.qml
|
||||
qml/StateKeys.qml
|
||||
qml/UnlockSSSSDialog.qml
|
||||
qml/QrScannerPage.qml
|
||||
qml/JoinRoomDialog.qml
|
||||
qml/ConfirmUrlDialog.qml
|
||||
qml/AccountSwitchDialog.qml
|
||||
qml/ConfirmLeaveDialog.qml
|
||||
qml/CodeMaximizeComponent.qml
|
||||
RESOURCES
|
||||
qml/confetti.png
|
||||
qml/glowdot.png
|
||||
)
|
||||
|
||||
add_subdirectory(settings)
|
||||
add_subdirectory(timeline)
|
||||
add_subdirectory(devtools)
|
||||
|
||||
if(UNIX)
|
||||
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
|
||||
@@ -406,7 +390,7 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
endif()
|
||||
|
||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
|
||||
target_link_libraries(neochat PRIVATE timelineplugin)
|
||||
target_link_libraries(neochat PRIVATE settingsplugin timelineplugin devtoolsplugin)
|
||||
target_link_libraries(neochat PUBLIC
|
||||
Qt::Core
|
||||
Qt::Quick
|
||||
@@ -532,7 +516,7 @@ if(ANDROID)
|
||||
)
|
||||
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
|
||||
else()
|
||||
target_link_libraries(neochat PUBLIC Qt::Widgets KF6::KIOWidgets)
|
||||
target_link_libraries(neochat PUBLIC Qt::Widgets KF6::KIOWidgets KF6::SyntaxHighlighting)
|
||||
install(FILES neochat.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
|
||||
endif()
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *ment
|
||||
|
||||
void ActionsHandler::handleMessage(const QString &text, QString handledText, ChatBarCache *chatBarCache)
|
||||
{
|
||||
Q_ASSERT(m_room);
|
||||
if (NeoChatConfig::allowQuickEdit()) {
|
||||
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
|
||||
auto match = sed.match(text);
|
||||
|
||||
@@ -58,7 +58,7 @@ public Q_SLOTS:
|
||||
void handleMessageEvent(ChatBarCache *chatBarCache);
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
void checkEffects(const QString &text);
|
||||
|
||||
QString handleMentions(QString handledText, QList<Mention> *mentions);
|
||||
|
||||
@@ -156,6 +156,15 @@ void Controller::addConnection(NeoChatConnection *c)
|
||||
c->saveState();
|
||||
});
|
||||
connect(c, &NeoChatConnection::loggedOut, this, [this, c] {
|
||||
if (accounts().count() > 1) {
|
||||
// Only set the connection if the the account being logged out is currently active
|
||||
if (c == activeConnection()) {
|
||||
setActiveConnection(dynamic_cast<NeoChatConnection *>(accounts().accounts()[0]));
|
||||
}
|
||||
} else {
|
||||
setActiveConnection(nullptr);
|
||||
}
|
||||
|
||||
dropConnection(c);
|
||||
});
|
||||
connect(c, &NeoChatConnection::badgeNotificationCountChanged, this, &Controller::updateBadgeNotificationCount);
|
||||
@@ -309,7 +318,7 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
|
||||
updateBadgeNotificationCount(m_connection, m_connection->badgeNotificationCount());
|
||||
}
|
||||
|
||||
Q_EMIT activeConnectionChanged();
|
||||
Q_EMIT activeConnectionChanged(m_connection);
|
||||
}
|
||||
|
||||
void Controller::listenForNotifications()
|
||||
@@ -377,6 +386,14 @@ AccountRegistry &Controller::accounts()
|
||||
return m_accountRegistry;
|
||||
}
|
||||
|
||||
QString Controller::loadFileContent(const QString &path) const
|
||||
{
|
||||
QUrl url(path);
|
||||
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
|
||||
file.open(QFile::ReadOnly);
|
||||
return QString::fromLatin1(file.readAll());
|
||||
}
|
||||
|
||||
#include "moc_controller.cpp"
|
||||
|
||||
void Controller::setTestMode(bool test)
|
||||
@@ -393,3 +410,12 @@ void Controller::removeConnection(const QString &userId)
|
||||
SettingsGroup("Accounts"_ls).remove(userId);
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::ssssSupported() const
|
||||
{
|
||||
#if __has_include("Quotient/e2ee/sssshandler.h")
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ class Controller : public QObject
|
||||
|
||||
Q_PROPERTY(QStringList accountsLoading MEMBER m_accountsLoading NOTIFY accountsLoadingChanged)
|
||||
|
||||
Q_PROPERTY(bool ssssSupported READ ssssSupported CONSTANT)
|
||||
|
||||
public:
|
||||
static Controller &instance();
|
||||
static Controller *create(QQmlEngine *engine, QJSEngine *)
|
||||
@@ -92,12 +94,16 @@ public:
|
||||
*/
|
||||
static void listenForNotifications();
|
||||
|
||||
Q_INVOKABLE QString loadFileContent(const QString &path) const;
|
||||
|
||||
Quotient::AccountRegistry &accounts();
|
||||
|
||||
static void setTestMode(bool testMode);
|
||||
|
||||
Q_INVOKABLE void removeConnection(const QString &userId);
|
||||
|
||||
bool ssssSupported() const;
|
||||
|
||||
private:
|
||||
explicit Controller(QObject *parent = nullptr);
|
||||
|
||||
@@ -123,6 +129,6 @@ Q_SIGNALS:
|
||||
void errorOccured(const QString &error, const QString &detail);
|
||||
void connectionAdded(NeoChatConnection *connection);
|
||||
void connectionDropped(NeoChatConnection *connection);
|
||||
void activeConnectionChanged();
|
||||
void activeConnectionChanged(NeoChatConnection *connection);
|
||||
void accountsLoadingChanged();
|
||||
};
|
||||
|
||||
15
src/devtools/CMakeLists.txt
Normal file
15
src/devtools/CMakeLists.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
# SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
qt_add_library(devtools STATIC)
|
||||
qt_add_qml_module(devtools
|
||||
URI org.kde.neochat.devtools
|
||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/devtools
|
||||
QML_FILES
|
||||
DevtoolsPage.qml
|
||||
AccountData.qml
|
||||
FeatureFlagPage.qml
|
||||
RoomData.qml
|
||||
ServerData.qml
|
||||
StateKeys.qml
|
||||
)
|
||||
@@ -7,7 +7,6 @@ import QtQuick.Layouts
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
|
||||
import org.kde.neochat
|
||||
import org.kde.neochat.config
|
||||
|
||||
FormCard.FormCardPage {
|
||||
@@ -23,5 +22,11 @@ FormCard.FormCardPage {
|
||||
|
||||
onToggled: Config.threads = checked
|
||||
}
|
||||
FormCard.FormCheckDelegate {
|
||||
text: i18nc("@option:check Enable the matrix 'secret backup' feature", "Secret Backup")
|
||||
checked: Config.secretBackup
|
||||
|
||||
onToggled: Config.secretBackup = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,14 +25,11 @@ ColumnLayout {
|
||||
text: i18n("Room")
|
||||
textRole: "escapedDisplayName"
|
||||
valueRole: "roomId"
|
||||
displayText: roomListModel.data(roomListModel.index(currentIndex, 0), RoomListModel.DisplayNameRole)
|
||||
model: RoomListModel {
|
||||
id: roomListModel
|
||||
connection: root.connection
|
||||
}
|
||||
displayText: RoomManager.roomListModel.data(RoomManager.roomListModel.index(currentIndex, 0), RoomListModel.DisplayNameRole)
|
||||
model: RoomManager.roomListModel
|
||||
currentIndex: 0
|
||||
Component.onCompleted: currentIndex = roomListModel.rowForRoom(root.room)
|
||||
onCurrentValueChanged: root.room = roomListModel.roomByAliasOrId(roomComboBox.currentValue)
|
||||
Component.onCompleted: currentIndex = RoomManager.roomListModel.rowForRoom(root.room)
|
||||
onCurrentValueChanged: root.room = RoomManager.roomListModel.roomByAliasOrId(roomComboBox.currentValue)
|
||||
}
|
||||
FormCard.FormTextDelegate {
|
||||
text: i18n("Room Id: %1", root.room.id)
|
||||
@@ -40,7 +40,8 @@ public:
|
||||
Code, /**< A code section. */
|
||||
Quote, /**< A quote section. */
|
||||
File, /**< A message that is a file. */
|
||||
Itinerary, /**< A preview for a file that can integrate with KDE itinerary.. */
|
||||
Itinerary, /**< A preview for a file that can integrate with KDE itinerary. */
|
||||
Pdf, /**< A preview for a PDF file. */
|
||||
Poll, /**< The initial event for a poll. */
|
||||
Location, /**< A location event. */
|
||||
LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */
|
||||
@@ -50,6 +51,7 @@ public:
|
||||
LinkPreview, /**< A preview of a URL in the message. */
|
||||
LinkPreviewLoad, /**< A loading dialog for a link preview. */
|
||||
Edit, /**< A text edit for editing a message. */
|
||||
Verification, /**< A user verification session start message. */
|
||||
Other, /**< Anything that cannot be classified as another type. */
|
||||
};
|
||||
Q_ENUM(Type);
|
||||
|
||||
@@ -29,6 +29,7 @@ public:
|
||||
Deprioritized, /**< The room is set as low priority. */
|
||||
Space, /**< The room is a space. */
|
||||
AddDirect, /**< So we can show the add friend delegate. */
|
||||
TypesCount, /**< Number of different types (this should always be last). */
|
||||
};
|
||||
Q_ENUM(Types);
|
||||
|
||||
|
||||
@@ -656,6 +656,7 @@ QVariantMap EventHandler::getMediaInfoForEvent(const Quotient::RoomEvent *event)
|
||||
|
||||
// Get the file info for the event.
|
||||
const EventContent::FileInfo *fileInfo;
|
||||
bool isSticker = false;
|
||||
if (event->is<RoomMessageEvent>()) {
|
||||
auto roomMessageEvent = eventCast<const RoomMessageEvent>(event);
|
||||
if (!roomMessageEvent->hasFileContent()) {
|
||||
@@ -665,14 +666,15 @@ QVariantMap EventHandler::getMediaInfoForEvent(const Quotient::RoomEvent *event)
|
||||
} else if (event->is<StickerEvent>()) {
|
||||
auto stickerEvent = eventCast<const StickerEvent>(event);
|
||||
fileInfo = &stickerEvent->image();
|
||||
isSticker = true;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
return getMediaInfoFromFileInfo(fileInfo, eventId);
|
||||
return getMediaInfoFromFileInfo(fileInfo, eventId, false, isSticker);
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail) const
|
||||
QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail, bool isSticker) const
|
||||
{
|
||||
QVariantMap mediaInfo;
|
||||
|
||||
@@ -699,6 +701,8 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
|
||||
// Add media size if available.
|
||||
mediaInfo["size"_ls] = fileInfo->payloadSize;
|
||||
|
||||
mediaInfo["isSticker"_ls] = isSticker;
|
||||
|
||||
// Add parameter depending on media type.
|
||||
if (mimeType.name().contains(QStringLiteral("image"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::ImageContent *>(fileInfo)) {
|
||||
|
||||
@@ -229,6 +229,7 @@ public:
|
||||
* - width - The width in pixels of the audio media.
|
||||
* - height - The height in pixels of the audio media.
|
||||
* - tempInfo - mediaInfo (with the same properties as this except no tempInfo) for a temporary image while the file downloads.
|
||||
* - isSticker - Whether the image is a sticker or not
|
||||
*/
|
||||
QVariantMap getMediaInfo() const;
|
||||
|
||||
@@ -320,6 +321,7 @@ public:
|
||||
* - width - The width in pixels of the audio media.
|
||||
* - height - The height in pixels of the audio media.
|
||||
* - tempInfo - mediaInfo (with the same properties as this except no tempInfo) for a temporary image while the file downloads.
|
||||
* - isSticker - Whether the image is a sticker or not
|
||||
*/
|
||||
QVariantMap getReplyMediaInfo() const;
|
||||
|
||||
@@ -405,5 +407,6 @@ private:
|
||||
QString getMessageBody(const Quotient::RoomMessageEvent &event, Qt::TextFormat format, bool stripNewlines) const;
|
||||
|
||||
QVariantMap getMediaInfoForEvent(const Quotient::RoomEvent *event) const;
|
||||
QVariantMap getMediaInfoFromFileInfo(const Quotient::EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail = false) const;
|
||||
QVariantMap
|
||||
getMediaInfoFromFileInfo(const Quotient::EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail = false, bool isSticker = false) const;
|
||||
};
|
||||
|
||||
@@ -41,6 +41,12 @@ FileType::~FileType() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
FileType &FileType::instance()
|
||||
{
|
||||
static FileType _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
QMimeType FileType::mimeTypeForName(const QString &nameOrAlias) const
|
||||
{
|
||||
Q_D(const FileType);
|
||||
@@ -113,4 +119,10 @@ QStringList FileType::supportedAnimatedImageFormats() const
|
||||
return d->supportedAnimatedImageFormats;
|
||||
}
|
||||
|
||||
bool FileType::fileHasImage(const QUrl &file) const
|
||||
{
|
||||
const auto mimeType = mimeTypeForFile(file.toString());
|
||||
return mimeType.isValid() && supportedImageFormats().contains(mimeType.preferredSuffix());
|
||||
}
|
||||
|
||||
#include "moc_filetype.cpp"
|
||||
|
||||
@@ -41,8 +41,13 @@ class FileType : public QObject
|
||||
Q_PROPERTY(QStringList supportedAnimatedImageFormats READ supportedAnimatedImageFormats CONSTANT FINAL)
|
||||
|
||||
public:
|
||||
explicit FileType(QObject *parent = nullptr);
|
||||
~FileType();
|
||||
static FileType &instance();
|
||||
static FileType *create(QQmlEngine *engine, QJSEngine *)
|
||||
{
|
||||
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
|
||||
return &instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for nameOrAlias or an invalid one if none found.
|
||||
@@ -120,7 +125,11 @@ public:
|
||||
QStringList supportedImageFormats() const;
|
||||
QStringList supportedAnimatedImageFormats() const;
|
||||
|
||||
bool fileHasImage(const QUrl &file) const;
|
||||
|
||||
private:
|
||||
explicit FileType(QObject *parent = nullptr);
|
||||
|
||||
const QScopedPointer<FileTypePrivate> d_ptr;
|
||||
Q_DECLARE_PRIVATE(FileType)
|
||||
Q_DISABLE_COPY(FileType)
|
||||
|
||||
10
src/main.cpp
10
src/main.cpp
@@ -35,6 +35,11 @@
|
||||
|
||||
#include "neochat-version.h"
|
||||
|
||||
#include <Quotient/accountregistry.h>
|
||||
#if __has_include("Quotient/e2ee/sssshandler.h")
|
||||
#include <Quotient/e2ee/sssshandler.h>
|
||||
#endif
|
||||
#include <Quotient/keyverificationsession.h>
|
||||
#include <Quotient/networkaccessmanager.h>
|
||||
|
||||
#include "blurhashimageprovider.h"
|
||||
@@ -223,13 +228,18 @@ int main(int argc, char *argv[])
|
||||
KDBusService service(KDBusService::Unique);
|
||||
#endif
|
||||
|
||||
Q_IMPORT_QML_PLUGIN(org_kde_neochat_settingsPlugin)
|
||||
Q_IMPORT_QML_PLUGIN(org_kde_neochat_timelinePlugin)
|
||||
Q_IMPORT_QML_PLUGIN(org_kde_neochat_devtoolsPlugin)
|
||||
|
||||
qml_register_types_org_kde_neochat();
|
||||
qmlRegisterSingletonInstance("org.kde.neochat.config", 1, 0, "Config", NeoChatConfig::self());
|
||||
qmlRegisterSingletonInstance("org.kde.neochat.accounts", 1, 0, "AccountRegistry", &Controller::instance().accounts());
|
||||
|
||||
qmlRegisterUncreatableType<KeyVerificationSession>("com.github.quotient_im.libquotient", 1, 0, "KeyVerificationSession", {});
|
||||
#if __has_include("Quotient/e2ee/sssshandler.h")
|
||||
qmlRegisterType<SSSSHandler>("com.github.quotient_im.libquotient", 1, 0, "SSSSHandler");
|
||||
#endif
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
|
||||
|
||||
@@ -91,7 +91,9 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
|
||||
if (mediaId.isEmpty()) {
|
||||
return QVariant();
|
||||
}
|
||||
return m_room->connection()->makeMediaUrl(QUrl(QStringLiteral("mxc://%1").arg(mediaId)));
|
||||
if (m_room) {
|
||||
return m_room->connection()->makeMediaUrl(QUrl(QStringLiteral("mxc://%1").arg(mediaId)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_autoCompletionType == Emoji) {
|
||||
|
||||
@@ -118,7 +118,7 @@ private:
|
||||
QString m_text;
|
||||
QString m_fullText;
|
||||
CompletionProxyModel *m_filterModel;
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
AutoCompletionType m_autoCompletionType = None;
|
||||
|
||||
void updateCompletion();
|
||||
|
||||
@@ -161,7 +161,7 @@ QVariant CustomEmojiModel::data(const QModelIndex &idx, int role) const
|
||||
case Roles::ImageURL:
|
||||
return QUrl(QStringLiteral("image://mxc/") + data.url.mid(6));
|
||||
case Roles::MxcUrl:
|
||||
return data.url.mid(6);
|
||||
return m_connection->makeMediaUrl(QUrl(data.url));
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -19,6 +19,15 @@ using namespace Quotient;
|
||||
DevicesModel::DevicesModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
connect(m_connection, &Connection::sessionVerified, this, [this](const QString &, const QString &deviceId) {
|
||||
const auto it = std::find_if(m_devices.begin(), m_devices.end(), [deviceId](const Quotient::Device &device) {
|
||||
return device.deviceId == deviceId;
|
||||
});
|
||||
if (it != m_devices.end()) {
|
||||
const auto index = this->index(it - m_devices.begin());
|
||||
Q_EMIT dataChanged(index, index, {Type});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void DevicesModel::fetchDevices()
|
||||
|
||||
@@ -26,20 +26,68 @@ QVariant ItineraryModel::data(const QModelIndex &index, int role) const
|
||||
auto data = m_data[row];
|
||||
if (role == NameRole) {
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("TrainReservation")) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("trainNumber")];
|
||||
auto trainName = QStringLiteral("%1 %2").arg(data[QStringLiteral("reservationFor")][QStringLiteral("trainName")].toString(),
|
||||
data[QStringLiteral("reservationFor")][QStringLiteral("trainNumber")].toString());
|
||||
if (trainName.trimmed().isEmpty()) {
|
||||
return QStringLiteral("%1 to %2")
|
||||
.arg(data[QStringLiteral("reservationFor")][QStringLiteral("departureStation")][QStringLiteral("name")].toString(),
|
||||
data[QStringLiteral("reservationFor")][QStringLiteral("arrivalStation")][QStringLiteral("name")].toString());
|
||||
;
|
||||
}
|
||||
return trainName;
|
||||
}
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("LodgingReservation")) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("name")];
|
||||
}
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("FoodEstablishmentReservation")) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("name")];
|
||||
}
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("FlightReservation")) {
|
||||
return QStringLiteral("%1 %2 %3 → %4")
|
||||
.arg(data[QStringLiteral("reservationFor")][QStringLiteral("airline")][QStringLiteral("iataCode")].toString(),
|
||||
data[QStringLiteral("reservationFor")][QStringLiteral("flightNumber")].toString(),
|
||||
data[QStringLiteral("reservationFor")][QStringLiteral("departureAirport")][QStringLiteral("iataCode")].toString(),
|
||||
data[QStringLiteral("reservationFor")][QStringLiteral("arrivalAirport")][QStringLiteral("iataCode")].toString());
|
||||
}
|
||||
}
|
||||
if (role == TypeRole) {
|
||||
return data[QStringLiteral("@type")];
|
||||
}
|
||||
if (role == DepartureStationRole) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("departureStation")][QStringLiteral("name")];
|
||||
if (role == DepartureLocationRole) {
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("TrainReservation")) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("departureStation")][QStringLiteral("name")];
|
||||
}
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("FlightReservation")) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("departureAirport")][QStringLiteral("iataCode")];
|
||||
}
|
||||
}
|
||||
if (role == ArrivalStationRole) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("arrivalStation")][QStringLiteral("name")];
|
||||
if (role == DepartureAddressRole) {
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("TrainReservation")) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("departureStation")][QStringLiteral("address")][QStringLiteral("addressCountry")]
|
||||
.toString();
|
||||
}
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("FlightReservation")) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("departureAirport")][QStringLiteral("address")][QStringLiteral("addressCountry")]
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
if (role == ArrivalLocationRole) {
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("TrainReservation")) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("arrivalStation")][QStringLiteral("name")];
|
||||
}
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("FlightReservation")) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("arrivalAirport")][QStringLiteral("iataCode")];
|
||||
}
|
||||
}
|
||||
if (role == ArrivalAddressRole) {
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("TrainReservation")) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("arrivalStation")][QStringLiteral("address")][QStringLiteral("addressCountry")]
|
||||
.toString();
|
||||
}
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("FlightReservation")) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("arrivalAirport")][QStringLiteral("address")][QStringLiteral("addressCountry")]
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
if (role == DepartureTimeRole) {
|
||||
const auto &time = data[QStringLiteral("reservationFor")][QStringLiteral("departureTime")];
|
||||
@@ -66,7 +114,16 @@ QVariant ItineraryModel::data(const QModelIndex &index, int role) const
|
||||
addressData[QStringLiteral("addressCountry")].toString());
|
||||
}
|
||||
if (role == StartTimeRole) {
|
||||
auto dateTime = data[QStringLiteral("checkinTime")][QStringLiteral("@value")].toVariant().toDateTime();
|
||||
QDateTime dateTime;
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("LodgingReservation")) {
|
||||
dateTime = data[QStringLiteral("checkinTime")][QStringLiteral("@value")].toVariant().toDateTime();
|
||||
}
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("FoodEstablishmentReservation")) {
|
||||
dateTime = data[QStringLiteral("startTime")][QStringLiteral("@value")].toVariant().toDateTime();
|
||||
}
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("FlightReservation")) {
|
||||
dateTime = data[QStringLiteral("reservationFor")][QStringLiteral("boardingTime")][QStringLiteral("@value")].toVariant().toDateTime();
|
||||
}
|
||||
return dateTime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
|
||||
}
|
||||
if (role == EndTimeRole) {
|
||||
@@ -99,8 +156,10 @@ QHash<int, QByteArray> ItineraryModel::roleNames() const
|
||||
return {
|
||||
{NameRole, "name"},
|
||||
{TypeRole, "type"},
|
||||
{DepartureStationRole, "departureStation"},
|
||||
{ArrivalStationRole, "arrivalStation"},
|
||||
{DepartureLocationRole, "departureLocation"},
|
||||
{DepartureAddressRole, "departureAddress"},
|
||||
{ArrivalLocationRole, "arrivalLocation"},
|
||||
{ArrivalAddressRole, "arrivalAddress"},
|
||||
{DepartureTimeRole, "departureTime"},
|
||||
{ArrivalTimeRole, "arrivalTime"},
|
||||
{AddressRole, "address"},
|
||||
@@ -133,6 +192,11 @@ void ItineraryModel::loadData()
|
||||
beginResetModel();
|
||||
m_data = QJsonDocument::fromJson(data).array();
|
||||
endResetModel();
|
||||
|
||||
Q_EMIT loaded();
|
||||
});
|
||||
connect(process, &QProcess::errorOccurred, this, [this]() {
|
||||
Q_EMIT loadErrorOccurred();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,12 @@ public:
|
||||
enum Roles {
|
||||
NameRole = Qt::DisplayRole,
|
||||
TypeRole,
|
||||
DepartureStationRole,
|
||||
ArrivalStationRole,
|
||||
DepartureLocationRole,
|
||||
ArrivalLocationRole,
|
||||
DepartureTimeRole,
|
||||
DepartureAddressRole,
|
||||
ArrivalTimeRole,
|
||||
ArrivalAddressRole,
|
||||
AddressRole,
|
||||
StartTimeRole,
|
||||
EndTimeRole,
|
||||
@@ -44,6 +46,10 @@ public:
|
||||
|
||||
Q_INVOKABLE void sendToItinerary();
|
||||
|
||||
Q_SIGNALS:
|
||||
void loaded();
|
||||
void loadErrorOccurred();
|
||||
|
||||
private:
|
||||
QJsonArray m_data;
|
||||
QString m_path;
|
||||
|
||||
@@ -3,15 +3,25 @@
|
||||
|
||||
#include "messagecontentmodel.h"
|
||||
|
||||
#include <QImageReader>
|
||||
|
||||
#include <Quotient/events/redactionevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
#include <Quotient/room.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <KSyntaxHighlighting/Definition>
|
||||
#include <KSyntaxHighlighting/Repository>
|
||||
#endif
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "enums/messagecomponenttype.h"
|
||||
#include "eventhandler.h"
|
||||
#include "filetype.h"
|
||||
#include "itinerarymodel.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "neochatroom.h"
|
||||
#include "texthandler.h"
|
||||
@@ -234,41 +244,75 @@ void MessageContentModel::updateComponents(bool isEditing)
|
||||
beginResetModel();
|
||||
m_components.clear();
|
||||
|
||||
EventHandler eventHandler(m_room, m_event);
|
||||
if (eventHandler.hasReply()) {
|
||||
if (m_room->findInTimeline(eventHandler.getReplyId()) == m_room->historyEdge()) {
|
||||
m_components += MessageComponent{MessageComponentType::ReplyLoad, QString(), {}};
|
||||
m_room->loadReply(m_event->id(), eventHandler.getReplyId());
|
||||
} else {
|
||||
m_components += MessageComponent{MessageComponentType::Reply, QString(), {}};
|
||||
}
|
||||
}
|
||||
|
||||
if (isEditing) {
|
||||
m_components += MessageComponent{MessageComponentType::Edit, QString(), {}};
|
||||
} else if (m_event->isRedacted()) {
|
||||
m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||
if (eventCast<const Quotient::RoomMessageEvent>(m_event)
|
||||
&& eventCast<const Quotient::RoomMessageEvent>(m_event)->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
|
||||
m_components += MessageComponent{MessageComponentType::Verification, QString(), {}};
|
||||
} else {
|
||||
if (eventHandler.messageComponentType() == MessageComponentType::Text) {
|
||||
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
|
||||
auto body = EventHandler::rawMessageBody(*event);
|
||||
m_components.append(TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced()));
|
||||
} else if (eventHandler.messageComponentType() == MessageComponentType::File) {
|
||||
m_components += MessageComponent{MessageComponentType::File, QString(), {}};
|
||||
updateItineraryModel();
|
||||
if (m_itineraryModel != nullptr) {
|
||||
m_components += MessageComponent{MessageComponentType::Itinerary, QString(), {}};
|
||||
EventHandler eventHandler(m_room, m_event);
|
||||
if (eventHandler.hasReply()) {
|
||||
if (m_room->findInTimeline(eventHandler.getReplyId()) == m_room->historyEdge()) {
|
||||
m_components += MessageComponent{MessageComponentType::ReplyLoad, QString(), {}};
|
||||
m_room->loadReply(m_event->id(), eventHandler.getReplyId());
|
||||
} else {
|
||||
m_components += MessageComponent{MessageComponentType::Reply, QString(), {}};
|
||||
}
|
||||
} else {
|
||||
m_components += MessageComponent{eventHandler.messageComponentType(), QString(), {}};
|
||||
}
|
||||
}
|
||||
|
||||
if (m_linkPreviewer != nullptr) {
|
||||
if (m_linkPreviewer->loaded()) {
|
||||
m_components += MessageComponent{MessageComponentType::LinkPreview, QString(), {}};
|
||||
if (isEditing) {
|
||||
m_components += MessageComponent{MessageComponentType::Edit, QString(), {}};
|
||||
} else if (m_event->isRedacted()) {
|
||||
m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||
} else {
|
||||
m_components += MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {}};
|
||||
if (eventHandler.messageComponentType() == MessageComponentType::Text) {
|
||||
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
|
||||
auto body = EventHandler::rawMessageBody(*event);
|
||||
m_components.append(TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced()));
|
||||
} else if (eventHandler.messageComponentType() == MessageComponentType::File) {
|
||||
m_components += MessageComponent{MessageComponentType::File, QString(), {}};
|
||||
if (m_emptyItinerary) {
|
||||
Quotient::FileTransferInfo fileTransferInfo;
|
||||
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
||||
if (event->hasFileContent()) {
|
||||
fileTransferInfo = m_room->fileTransferInfo(event->id());
|
||||
}
|
||||
}
|
||||
if (auto event = eventCast<const Quotient::StickerEvent>(m_event)) {
|
||||
fileTransferInfo = m_room->fileTransferInfo(event->id());
|
||||
}
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
KSyntaxHighlighting::Repository repository;
|
||||
const auto definitionForFile = repository.definitionForFileName(fileTransferInfo.localPath.toString());
|
||||
if (definitionForFile.isValid() || QFileInfo(fileTransferInfo.localPath.path()).suffix() == QStringLiteral("txt")) {
|
||||
QFile file(fileTransferInfo.localPath.path());
|
||||
file.open(QIODevice::ReadOnly);
|
||||
m_components += MessageComponent{MessageComponentType::Code,
|
||||
QString::fromStdString(file.readAll().toStdString()),
|
||||
{{QStringLiteral("class"), definitionForFile.name()}}};
|
||||
}
|
||||
#endif
|
||||
|
||||
if (FileType::instance().fileHasImage(fileTransferInfo.localPath)) {
|
||||
QImageReader reader(fileTransferInfo.localPath.path());
|
||||
m_components += MessageComponent{MessageComponentType::Pdf, QString(), {{QStringLiteral("size"), reader.size()}}};
|
||||
}
|
||||
} else {
|
||||
updateItineraryModel();
|
||||
if (m_itineraryModel != nullptr) {
|
||||
m_components += MessageComponent{MessageComponentType::Itinerary, QString(), {}};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_components += MessageComponent{eventHandler.messageComponentType(), QString(), {}};
|
||||
}
|
||||
}
|
||||
|
||||
if (m_linkPreviewer != nullptr) {
|
||||
if (m_linkPreviewer->loaded()) {
|
||||
m_components += MessageComponent{MessageComponentType::LinkPreview, QString(), {}};
|
||||
} else {
|
||||
m_components += MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,6 +334,20 @@ void MessageContentModel::updateItineraryModel()
|
||||
} else if (!filePath.isEmpty()) {
|
||||
if (m_itineraryModel == nullptr) {
|
||||
m_itineraryModel = new ItineraryModel(this);
|
||||
connect(m_itineraryModel, &ItineraryModel::loaded, this, [this]() {
|
||||
if (m_itineraryModel->rowCount() == 0) {
|
||||
m_itineraryModel->deleteLater();
|
||||
m_itineraryModel = nullptr;
|
||||
m_emptyItinerary = true;
|
||||
updateComponents();
|
||||
}
|
||||
});
|
||||
connect(m_itineraryModel, &ItineraryModel::loadErrorOccurred, this, [this]() {
|
||||
m_itineraryModel->deleteLater();
|
||||
m_itineraryModel = nullptr;
|
||||
m_emptyItinerary = true;
|
||||
updateComponents();
|
||||
});
|
||||
}
|
||||
m_itineraryModel->setPath(filePath.toString());
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ public:
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
const Quotient::RoomEvent *m_event = nullptr;
|
||||
|
||||
QList<MessageComponent> m_components;
|
||||
@@ -97,4 +97,5 @@ private:
|
||||
ItineraryModel *m_itineraryModel = nullptr;
|
||||
|
||||
void updateItineraryModel();
|
||||
bool m_emptyItinerary = false;
|
||||
};
|
||||
|
||||
@@ -629,6 +629,10 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
|
||||
int MessageEventModel::eventIdToRow(const QString &eventID) const
|
||||
{
|
||||
if (m_currentRoom == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const auto it = m_currentRoom->findInTimeline(eventID);
|
||||
if (it == m_currentRoom->historyEdge()) {
|
||||
// qWarning() << "Trying to find inexistent event:" << eventID;
|
||||
|
||||
@@ -113,7 +113,7 @@ private Q_SLOTS:
|
||||
void refreshRow(int row);
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_currentRoom = nullptr;
|
||||
QPointer<NeoChatRoom> m_currentRoom = nullptr;
|
||||
QString lastReadEventId;
|
||||
QPersistentModelIndex m_lastReadEventIndex;
|
||||
int rowBelowInserted = -1;
|
||||
|
||||
@@ -124,6 +124,15 @@ void PublicRoomListModel::setShowOnlySpaces(bool showOnlySpaces)
|
||||
}
|
||||
m_showOnlySpaces = showOnlySpaces;
|
||||
Q_EMIT showOnlySpacesChanged();
|
||||
|
||||
nextBatch = QString();
|
||||
attempted = false;
|
||||
|
||||
if (job) {
|
||||
job->abandon();
|
||||
job = nullptr;
|
||||
Q_EMIT searchingChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void PublicRoomListModel::search(int limit)
|
||||
@@ -243,6 +252,9 @@ QVariant PublicRoomListModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
return m_connection->room(room.roomId, JoinState::Join) != nullptr;
|
||||
}
|
||||
if (role == IsSpaceRole) {
|
||||
return room.roomType == QLatin1String("m.space");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -259,6 +271,7 @@ QHash<int, QByteArray> PublicRoomListModel::roleNames() const
|
||||
roles[AllowGuestsRole] = "allowGuests";
|
||||
roles[WorldReadableRole] = "worldReadable";
|
||||
roles[IsJoinedRole] = "isJoined";
|
||||
roles[IsSpaceRole] = "isSpace";
|
||||
roles[AliasRole] = "alias";
|
||||
|
||||
return roles;
|
||||
|
||||
@@ -69,6 +69,7 @@ public:
|
||||
AllowGuestsRole, /**< Whether the room allows guest users. */
|
||||
WorldReadableRole, /**< Whether the room events can be seen by non-members. */
|
||||
IsJoinedRole, /**< Whether the local user has joined the room. */
|
||||
IsSpaceRole, /**< Whether the room is a space. */
|
||||
};
|
||||
|
||||
explicit PublicRoomListModel(QObject *parent = nullptr);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
#include <Quotient/user.h>
|
||||
|
||||
ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, const NeoChatRoom *room)
|
||||
ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room)
|
||||
: QAbstractListModel(nullptr)
|
||||
, m_room(room)
|
||||
, m_event(event)
|
||||
|
||||
@@ -44,7 +44,7 @@ public:
|
||||
HasLocalUser, /**< Whether the local user is in the list of authors. */
|
||||
};
|
||||
|
||||
explicit ReactionModel(const Quotient::RoomMessageEvent *event, const NeoChatRoom *room);
|
||||
explicit ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
const NeoChatRoom *m_room;
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
const Quotient::RoomMessageEvent *m_event;
|
||||
QList<Reaction> m_reactions;
|
||||
QMap<QString, QString> m_shortcodes;
|
||||
|
||||
100
src/models/roomtreeitem.cpp
Normal file
100
src/models/roomtreeitem.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
// SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "roomtreeitem.h"
|
||||
|
||||
RoomTreeItem::RoomTreeItem(TreeData data, RoomTreeItem *parent)
|
||||
: m_parentItem(parent)
|
||||
, m_data(data)
|
||||
{
|
||||
}
|
||||
|
||||
bool RoomTreeItem::operator==(const RoomTreeItem &other) const
|
||||
{
|
||||
if (std::holds_alternative<NeoChatRoomType::Types>(m_data) && std::holds_alternative<NeoChatRoomType::Types>(other.data())) {
|
||||
return std::get<NeoChatRoomType::Types>(m_data) == std::get<NeoChatRoomType::Types>(m_data);
|
||||
}
|
||||
if (std::holds_alternative<NeoChatRoom *>(m_data) && std::holds_alternative<NeoChatRoom *>(other.data())) {
|
||||
return std::get<NeoChatRoom *>(m_data)->id() == std::get<NeoChatRoom *>(m_data)->id();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RoomTreeItem *RoomTreeItem::child(int row)
|
||||
{
|
||||
return row >= 0 && row < childCount() ? m_children.at(row).get() : nullptr;
|
||||
}
|
||||
|
||||
int RoomTreeItem::childCount() const
|
||||
{
|
||||
return int(m_children.size());
|
||||
}
|
||||
|
||||
bool RoomTreeItem::insertChild(std::unique_ptr<RoomTreeItem> newChild)
|
||||
{
|
||||
if (newChild == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto it = m_children.begin(), end = m_children.end(); it != end; ++it) {
|
||||
if (*it == newChild) {
|
||||
*it = std::move(newChild);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
m_children.push_back(std::move(newChild));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RoomTreeItem::removeChild(int row)
|
||||
{
|
||||
if (row < 0 || row >= childCount()) {
|
||||
return false;
|
||||
}
|
||||
m_children.erase(m_children.begin() + row);
|
||||
return true;
|
||||
}
|
||||
|
||||
int RoomTreeItem::row() const
|
||||
{
|
||||
if (m_parentItem == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto it = std::find_if(m_parentItem->m_children.cbegin(), m_parentItem->m_children.cend(), [this](const std::unique_ptr<RoomTreeItem> &treeItem) {
|
||||
return treeItem.get() == this;
|
||||
});
|
||||
|
||||
if (it != m_parentItem->m_children.cend()) {
|
||||
return std::distance(m_parentItem->m_children.cbegin(), it);
|
||||
}
|
||||
Q_ASSERT(false); // should not happen
|
||||
return -1;
|
||||
}
|
||||
|
||||
RoomTreeItem *RoomTreeItem::parentItem() const
|
||||
{
|
||||
return m_parentItem;
|
||||
}
|
||||
|
||||
RoomTreeItem::TreeData RoomTreeItem::data() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
std::optional<int> RoomTreeItem::rowForRoom(Quotient::Room *room) const
|
||||
{
|
||||
Q_ASSERT_X(std::holds_alternative<NeoChatRoomType::Types>(m_data), __FUNCTION__, "rowForRoom only works items for rooms not categories");
|
||||
|
||||
int i = 0;
|
||||
for (const auto &child : m_children) {
|
||||
if (std::get<NeoChatRoom *>(child->data()) == room) {
|
||||
return i;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
78
src/models/roomtreeitem.h
Normal file
78
src/models/roomtreeitem.h
Normal file
@@ -0,0 +1,78 @@
|
||||
// SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "enums/neochatroomtype.h"
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class RoomTreeItem
|
||||
*
|
||||
* This class defines an item in the space tree hierarchy model.
|
||||
*
|
||||
* @note This is separate from Quotient::Room and NeoChatRoom because we don't have
|
||||
* full room information for any room/space the user hasn't joined and we
|
||||
* don't want to create one for ever possible child in a space as that would
|
||||
* be expensive.
|
||||
*
|
||||
* @sa Quotient::Room, NeoChatRoom
|
||||
*/
|
||||
class RoomTreeItem
|
||||
{
|
||||
public:
|
||||
using TreeData = std::variant<NeoChatRoom *, NeoChatRoomType::Types>;
|
||||
|
||||
explicit RoomTreeItem(TreeData data, RoomTreeItem *parent = nullptr);
|
||||
|
||||
bool operator==(const RoomTreeItem &other) const;
|
||||
|
||||
/**
|
||||
* @brief Return the child at the given row number.
|
||||
*
|
||||
* Nullptr is returned if there is no child at the given row number.
|
||||
*/
|
||||
RoomTreeItem *child(int row);
|
||||
|
||||
/**
|
||||
* @brief The number of children this item has.
|
||||
*/
|
||||
int childCount() const;
|
||||
|
||||
/**
|
||||
* @brief Insert the given child.
|
||||
*/
|
||||
bool insertChild(std::unique_ptr<RoomTreeItem> newChild);
|
||||
|
||||
/**
|
||||
* @brief Remove the child at the given row number.
|
||||
*
|
||||
* @return True if a child was removed, false if the given row isn't valid.
|
||||
*/
|
||||
bool removeChild(int row);
|
||||
|
||||
/**
|
||||
* @brief Return this item's parent.
|
||||
*/
|
||||
RoomTreeItem *parentItem() const;
|
||||
|
||||
/**
|
||||
* @brief Return the row number for this child relative to the parent.
|
||||
*
|
||||
* @return The row value if the child has a parent, 0 otherwise.
|
||||
*/
|
||||
int row() const;
|
||||
|
||||
/**
|
||||
* @brief Return this item's data.
|
||||
*/
|
||||
TreeData data() const;
|
||||
|
||||
std::optional<int> rowForRoom(Quotient::Room *room) const;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<RoomTreeItem>> m_children;
|
||||
RoomTreeItem *m_parentItem;
|
||||
|
||||
TreeData m_data;
|
||||
};
|
||||
@@ -15,21 +15,47 @@ using namespace Quotient;
|
||||
|
||||
RoomTreeModel::RoomTreeModel(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
, m_rootItem(new RoomTreeItem(nullptr))
|
||||
{
|
||||
initializeCategories();
|
||||
}
|
||||
|
||||
void RoomTreeModel::initializeCategories()
|
||||
RoomTreeItem *RoomTreeModel::getItem(const QModelIndex &index) const
|
||||
{
|
||||
for (const auto &key : m_rooms.keys()) {
|
||||
for (const auto &room : m_rooms[key]) {
|
||||
room->disconnect(this);
|
||||
if (index.isValid()) {
|
||||
RoomTreeItem *item = static_cast<RoomTreeItem *>(index.internalPointer());
|
||||
if (item) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
m_rooms.clear();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
m_rooms[NeoChatRoomType::Types(i)] = {};
|
||||
return m_rootItem.get();
|
||||
}
|
||||
|
||||
void RoomTreeModel::resetModel()
|
||||
{
|
||||
if (m_connection == nullptr) {
|
||||
beginResetModel();
|
||||
m_rootItem.reset();
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
m_rootItem.reset(new RoomTreeItem(nullptr));
|
||||
|
||||
for (int i = 0; i < NeoChatRoomType::TypesCount; i++) {
|
||||
m_rootItem->insertChild(std::make_unique<RoomTreeItem>(NeoChatRoomType::Types(i), m_rootItem.get()));
|
||||
}
|
||||
|
||||
for (const auto &r : m_connection->allRooms()) {
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(r);
|
||||
const auto type = NeoChatRoomType::typeForRoom(room);
|
||||
const auto categoryItem = m_rootItem->child(type);
|
||||
if (categoryItem->insertChild(std::make_unique<RoomTreeItem>(room, categoryItem))) {
|
||||
connectRoomSignals(room);
|
||||
}
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void RoomTreeModel::setConnection(NeoChatConnection *connection)
|
||||
@@ -41,16 +67,13 @@ void RoomTreeModel::setConnection(NeoChatConnection *connection)
|
||||
disconnect(m_connection.get(), nullptr, this, nullptr);
|
||||
}
|
||||
m_connection = connection;
|
||||
beginResetModel();
|
||||
initializeCategories();
|
||||
endResetModel();
|
||||
|
||||
resetModel();
|
||||
|
||||
connect(connection, &Connection::newRoom, this, &RoomTreeModel::newRoom);
|
||||
connect(connection, &Connection::leftRoom, this, &RoomTreeModel::leftRoom);
|
||||
connect(connection, &Connection::aboutToDeleteRoom, this, &RoomTreeModel::leftRoom);
|
||||
|
||||
for (const auto &room : m_connection->allRooms()) {
|
||||
newRoom(dynamic_cast<NeoChatRoom *>(room));
|
||||
}
|
||||
Q_EMIT connectionChanged();
|
||||
}
|
||||
|
||||
@@ -68,23 +91,28 @@ void RoomTreeModel::newRoom(Room *r)
|
||||
return;
|
||||
}
|
||||
|
||||
beginInsertRows(index(type, 0), m_rooms[type].size(), m_rooms[type].size());
|
||||
m_rooms[type].append(room);
|
||||
const auto parentItem = m_rootItem->child(type);
|
||||
beginInsertRows(index(parentItem->row(), 0), parentItem->childCount(), parentItem->childCount());
|
||||
parentItem->insertChild(std::make_unique<RoomTreeItem>(room, parentItem));
|
||||
connectRoomSignals(room);
|
||||
endInsertRows();
|
||||
qWarning() << "adding room" << type << "new count" << parentItem->childCount();
|
||||
}
|
||||
|
||||
void RoomTreeModel::leftRoom(Room *r)
|
||||
{
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(r);
|
||||
const auto type = NeoChatRoomType::typeForRoom(room);
|
||||
auto row = m_rooms[type].indexOf(room);
|
||||
if (row == -1) {
|
||||
auto index = indexForRoom(room);
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
beginRemoveRows(index(type, 0), row, row);
|
||||
m_rooms[type][row]->disconnect(this);
|
||||
m_rooms[type].removeAt(row);
|
||||
|
||||
const auto parentItem = getItem(index.parent());
|
||||
Q_ASSERT(parentItem);
|
||||
|
||||
beginRemoveRows(index.parent(), index.row(), index.row());
|
||||
parentItem->removeChild(index.row());
|
||||
room->disconnect(this);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
@@ -94,30 +122,41 @@ void RoomTreeModel::moveRoom(Quotient::Room *room)
|
||||
// NeoChatRoomType::typeForRoom doesn't match it's current location. So find the room.
|
||||
NeoChatRoomType::Types oldType;
|
||||
int oldRow = -1;
|
||||
for (const auto &key : m_rooms.keys()) {
|
||||
if (m_rooms[key].contains(room)) {
|
||||
oldType = key;
|
||||
oldRow = m_rooms[key].indexOf(room);
|
||||
for (int i = 0; i < NeoChatRoomType::TypesCount; i++) {
|
||||
const auto categoryItem = m_rootItem->child(i);
|
||||
const auto row = categoryItem->rowForRoom(room);
|
||||
if (row) {
|
||||
oldType = static_cast<NeoChatRoomType::Types>(i);
|
||||
oldRow = *row;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldRow == -1) {
|
||||
return;
|
||||
}
|
||||
const auto newType = NeoChatRoomType::typeForRoom(dynamic_cast<NeoChatRoom *>(room));
|
||||
auto neochatRoom = dynamic_cast<NeoChatRoom *>(room);
|
||||
const auto newType = NeoChatRoomType::typeForRoom(neochatRoom);
|
||||
if (newType == oldType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto oldParent = index(oldType, 0, {});
|
||||
auto oldParentItem = getItem(oldParent);
|
||||
Q_ASSERT(oldParentItem);
|
||||
|
||||
const auto newParent = index(newType, 0, {});
|
||||
auto newParentItem = getItem(newParent);
|
||||
Q_ASSERT(newParentItem);
|
||||
|
||||
// HACK: We're doing this as a remove then insert because moving doesn't work
|
||||
// properly with DelegateChooser for whatever reason.
|
||||
Q_ASSERT(checkIndex(index(oldRow, 0, oldParent), QAbstractItemModel::CheckIndexOption::IndexIsValid));
|
||||
beginRemoveRows(oldParent, oldRow, oldRow);
|
||||
m_rooms[oldType].removeAt(oldRow);
|
||||
const bool success = oldParentItem->removeChild(oldRow);
|
||||
Q_ASSERT(success);
|
||||
endRemoveRows();
|
||||
beginInsertRows(newParent, m_rooms[newType].size(), m_rooms[newType].size());
|
||||
m_rooms[newType].append(dynamic_cast<NeoChatRoom *>(room));
|
||||
beginInsertRows(newParent, newParentItem->childCount(), newParentItem->childCount());
|
||||
newParentItem->insertChild(std::make_unique<RoomTreeItem>(neochatRoom, newParentItem));
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
@@ -151,15 +190,12 @@ void RoomTreeModel::connectRoomSignals(NeoChatRoom *room)
|
||||
|
||||
void RoomTreeModel::refreshRoomRoles(NeoChatRoom *room, const QList<int> &roles)
|
||||
{
|
||||
const auto roomType = NeoChatRoomType::typeForRoom(room);
|
||||
const auto it = std::find(m_rooms[roomType].begin(), m_rooms[roomType].end(), room);
|
||||
if (it == m_rooms[roomType].end()) {
|
||||
const auto index = indexForRoom(room);
|
||||
if (!index.isValid()) {
|
||||
qCritical() << "Room" << room->id() << "not found in the room list";
|
||||
return;
|
||||
}
|
||||
const auto parentIndex = index(roomType, 0, {});
|
||||
const auto idx = index(it - m_rooms[roomType].begin(), 0, parentIndex);
|
||||
Q_EMIT dataChanged(idx, idx, roles);
|
||||
Q_EMIT dataChanged(index, index, roles);
|
||||
}
|
||||
|
||||
NeoChatConnection *RoomTreeModel::connection() const
|
||||
@@ -175,32 +211,55 @@ int RoomTreeModel::columnCount(const QModelIndex &parent) const
|
||||
|
||||
int RoomTreeModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
RoomTreeItem *parentItem;
|
||||
if (parent.column() > 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!parent.isValid()) {
|
||||
return m_rooms.keys().size();
|
||||
parentItem = m_rootItem.get();
|
||||
} else {
|
||||
parentItem = static_cast<RoomTreeItem *>(parent.internalPointer());
|
||||
}
|
||||
if (!parent.parent().isValid()) {
|
||||
return m_rooms.values()[parent.row()].size();
|
||||
}
|
||||
return 0;
|
||||
|
||||
return parentItem->childCount();
|
||||
}
|
||||
|
||||
QModelIndex RoomTreeModel::parent(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.internalPointer()) {
|
||||
return {};
|
||||
if (!index.isValid()) {
|
||||
return QModelIndex();
|
||||
}
|
||||
return this->index(NeoChatRoomType::typeForRoom(static_cast<NeoChatRoom *>(index.internalPointer())), 0, QModelIndex());
|
||||
|
||||
RoomTreeItem *childItem = static_cast<RoomTreeItem *>(index.internalPointer());
|
||||
if (!childItem) {
|
||||
return QModelIndex();
|
||||
}
|
||||
RoomTreeItem *parentItem = childItem->parentItem();
|
||||
|
||||
if (parentItem == m_rootItem.get()) {
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
return createIndex(parentItem->row(), 0, parentItem);
|
||||
}
|
||||
|
||||
QModelIndex RoomTreeModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (!parent.isValid()) {
|
||||
return createIndex(row, column, nullptr);
|
||||
if (!hasIndex(row, column, parent)) {
|
||||
return QModelIndex();
|
||||
}
|
||||
if (row >= rowCount(parent)) {
|
||||
return {};
|
||||
|
||||
RoomTreeItem *parentItem = getItem(parent);
|
||||
if (!parentItem) {
|
||||
return QModelIndex();
|
||||
}
|
||||
return createIndex(row, column, m_rooms[NeoChatRoomType::Types(parent.row())][row]);
|
||||
|
||||
RoomTreeItem *childItem = parentItem->child(row);
|
||||
if (childItem) {
|
||||
return createIndex(row, column, childItem);
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> RoomTreeModel::roleNames() const
|
||||
@@ -235,7 +294,8 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (!index.parent().isValid()) {
|
||||
RoomTreeItem *child = getItem(index);
|
||||
if (std::holds_alternative<NeoChatRoomType::Types>(child->data())) {
|
||||
if (role == DisplayNameRole) {
|
||||
return NeoChatRoomType::typeName(index.row());
|
||||
}
|
||||
@@ -256,7 +316,8 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
return {};
|
||||
}
|
||||
const auto room = m_rooms.values()[index.parent().row()][index.row()].get();
|
||||
|
||||
const auto room = std::get<NeoChatRoom *>(child->data());
|
||||
Q_ASSERT(room);
|
||||
|
||||
if (role == DisplayNameRole) {
|
||||
@@ -338,16 +399,20 @@ QModelIndex RoomTreeModel::indexForRoom(NeoChatRoom *room) const
|
||||
|
||||
// Try and find by checking type.
|
||||
const auto type = NeoChatRoomType::typeForRoom(room);
|
||||
auto row = m_rooms[type].indexOf(room);
|
||||
if (row >= 0) {
|
||||
return index(row, 0, index(type, 0));
|
||||
const auto parentItem = m_rootItem->child(type);
|
||||
const auto row = parentItem->rowForRoom(room);
|
||||
if (row) {
|
||||
return index(*row, 0, index(type, 0));
|
||||
}
|
||||
// Double check that the room isn't in the wrong category.
|
||||
for (const auto &key : m_rooms.keys()) {
|
||||
if (m_rooms[key].contains(room)) {
|
||||
return index(m_rooms[key].indexOf(room), 0, index(key, 0));
|
||||
for (int i = 0; i < NeoChatRoomType::TypesCount; i++) {
|
||||
const auto parentItem = m_rootItem->child(i);
|
||||
const auto row = parentItem->rowForRoom(room);
|
||||
if (row) {
|
||||
return index(*row, 0, index(i, 0));
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <QPointer>
|
||||
|
||||
#include "enums/neochatroomtype.h"
|
||||
#include "roomtreeitem.h"
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
@@ -82,10 +83,12 @@ Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
|
||||
private:
|
||||
QPointer<NeoChatConnection> m_connection = nullptr;
|
||||
QMap<NeoChatRoomType::Types, QList<QPointer<NeoChatRoom>>> m_rooms;
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
std::unique_ptr<RoomTreeItem> m_rootItem;
|
||||
|
||||
void initializeCategories();
|
||||
RoomTreeItem *getItem(const QModelIndex &index) const;
|
||||
|
||||
void resetModel();
|
||||
void connectRoomSignals(NeoChatRoom *room);
|
||||
|
||||
void newRoom(Quotient::Room *room);
|
||||
|
||||
@@ -121,7 +121,7 @@ private:
|
||||
void setSearching(bool searching);
|
||||
|
||||
QString m_searchText;
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
Quotient::Omittable<Quotient::SearchJob::ResultRoomEvents> m_result = Quotient::none;
|
||||
Quotient::SearchJob *m_job = nullptr;
|
||||
bool m_searching = false;
|
||||
|
||||
@@ -5,17 +5,20 @@
|
||||
|
||||
#include "roomlistmodel.h"
|
||||
|
||||
SortFilterRoomListModel::SortFilterRoomListModel(QObject *parent)
|
||||
SortFilterRoomListModel::SortFilterRoomListModel(RoomListModel *sourceModel, QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
Q_ASSERT(sourceModel);
|
||||
setSourceModel(sourceModel);
|
||||
|
||||
sort(0);
|
||||
invalidateFilter();
|
||||
connect(this, &SortFilterRoomListModel::filterTextChanged, this, [this]() {
|
||||
invalidateFilter();
|
||||
});
|
||||
connect(this, &SortFilterRoomListModel::sourceModelChanged, this, [this]() {
|
||||
connect(sourceModel(), &QAbstractListModel::rowsInserted, this, &SortFilterRoomListModel::invalidateRowsFilter);
|
||||
connect(sourceModel(), &QAbstractListModel::rowsRemoved, this, &SortFilterRoomListModel::invalidateRowsFilter);
|
||||
connect(this->sourceModel(), &QAbstractListModel::rowsInserted, this, &SortFilterRoomListModel::invalidateRowsFilter);
|
||||
connect(this->sourceModel(), &QAbstractListModel::rowsRemoved, this, &SortFilterRoomListModel::invalidateRowsFilter);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <QQmlEngine>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "models/roomlistmodel.h"
|
||||
|
||||
/**
|
||||
* @class SortFilterRoomListModel
|
||||
*
|
||||
@@ -29,6 +31,7 @@ class SortFilterRoomListModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
/**
|
||||
* @brief The text to use to filter room names.
|
||||
@@ -36,7 +39,7 @@ class SortFilterRoomListModel : public QSortFilterProxyModel
|
||||
Q_PROPERTY(QString filterText READ filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
|
||||
public:
|
||||
explicit SortFilterRoomListModel(QObject *parent = nullptr);
|
||||
explicit SortFilterRoomListModel(RoomListModel *sourceModel, QObject *parent = nullptr);
|
||||
|
||||
void setFilterText(const QString &text);
|
||||
[[nodiscard]] QString filterText() const;
|
||||
|
||||
@@ -10,9 +10,12 @@
|
||||
#include "roomtreemodel.h"
|
||||
#include "spacehierarchycache.h"
|
||||
|
||||
SortFilterRoomTreeModel::SortFilterRoomTreeModel(QObject *parent)
|
||||
SortFilterRoomTreeModel::SortFilterRoomTreeModel(RoomTreeModel *sourceModel, QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
Q_ASSERT(sourceModel);
|
||||
setSourceModel(sourceModel);
|
||||
|
||||
setRoomSortOrder(static_cast<RoomSortOrder>(NeoChatConfig::sortOrder()));
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::SortOrderChanged, this, [this]() {
|
||||
setRoomSortOrder(static_cast<RoomSortOrder>(NeoChatConfig::sortOrder()));
|
||||
@@ -21,12 +24,11 @@ SortFilterRoomTreeModel::SortFilterRoomTreeModel(QObject *parent)
|
||||
|
||||
setRecursiveFilteringEnabled(true);
|
||||
sort(0);
|
||||
invalidateFilter();
|
||||
connect(this, &SortFilterRoomTreeModel::filterTextChanged, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
connect(this, &SortFilterRoomTreeModel::sourceModelChanged, this, [this]() {
|
||||
sourceModel()->disconnect(this);
|
||||
connect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
this->sourceModel()->disconnect(this);
|
||||
connect(this->sourceModel(), &QAbstractItemModel::rowsInserted, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
connect(this->sourceModel(), &QAbstractItemModel::rowsRemoved, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
});
|
||||
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::CollapsedChanged, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
|
||||
@@ -32,6 +32,7 @@ class SortFilterRoomTreeModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
/**
|
||||
* @brief The text to use to filter room names.
|
||||
@@ -62,7 +63,7 @@ public:
|
||||
};
|
||||
Q_ENUM(Mode)
|
||||
|
||||
explicit SortFilterRoomTreeModel(QObject *parent = nullptr);
|
||||
explicit SortFilterRoomTreeModel(RoomTreeModel *sourceModel, QObject *parent = nullptr);
|
||||
|
||||
void setRoomSortOrder(RoomSortOrder sortOrder);
|
||||
|
||||
|
||||
@@ -5,22 +5,21 @@
|
||||
|
||||
#include "roomlistmodel.h"
|
||||
|
||||
SortFilterSpaceListModel::SortFilterSpaceListModel(QObject *parent)
|
||||
SortFilterSpaceListModel::SortFilterSpaceListModel(RoomListModel *sourceModel, QObject *parent)
|
||||
: QSortFilterProxyModel{parent}
|
||||
{
|
||||
setSortRole(RoomListModel::RoomIdRole);
|
||||
sort(0);
|
||||
invalidateFilter();
|
||||
connect(this, &QAbstractProxyModel::sourceModelChanged, this, [this]() {
|
||||
connect(sourceModel(), &QAbstractListModel::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, QList<int> roles) {
|
||||
if (roles.contains(RoomListModel::IsChildSpaceRole)) {
|
||||
invalidate();
|
||||
}
|
||||
countChanged();
|
||||
});
|
||||
invalidate();
|
||||
Q_ASSERT(sourceModel);
|
||||
setSourceModel(sourceModel);
|
||||
|
||||
connect(this->sourceModel(), &QAbstractListModel::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, QList<int> roles) {
|
||||
if (roles.contains(RoomListModel::IsChildSpaceRole)) {
|
||||
invalidate();
|
||||
}
|
||||
Q_EMIT countChanged();
|
||||
});
|
||||
|
||||
setSortRole(RoomListModel::RoomIdRole);
|
||||
sort(0);
|
||||
}
|
||||
|
||||
bool SortFilterSpaceListModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <QQmlEngine>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "models/roomlistmodel.h"
|
||||
|
||||
/**
|
||||
* @class SortFilterSpaceListModel
|
||||
*
|
||||
@@ -18,6 +20,7 @@ class SortFilterSpaceListModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
/**
|
||||
* @brief The number of spaces in the model.
|
||||
@@ -25,7 +28,7 @@ class SortFilterSpaceListModel : public QSortFilterProxyModel
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
|
||||
public:
|
||||
explicit SortFilterSpaceListModel(QObject *parent = nullptr);
|
||||
explicit SortFilterSpaceListModel(RoomListModel *sourceModel, QObject *parent = nullptr);
|
||||
|
||||
Q_SIGNALS:
|
||||
void countChanged();
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
, m_rootItem(new SpaceTreeItem(nullptr))
|
||||
{
|
||||
m_rootItem = new SpaceTreeItem(nullptr);
|
||||
}
|
||||
|
||||
SpaceChildrenModel::~SpaceChildrenModel()
|
||||
@@ -40,6 +40,12 @@ void SpaceChildrenModel::setSpace(NeoChatRoom *space)
|
||||
m_space = space;
|
||||
Q_EMIT spaceChanged();
|
||||
|
||||
refreshModel();
|
||||
|
||||
if (!m_space) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto connection = m_space->connection();
|
||||
connect(connection, &Quotient::Connection::loadedRoomState, this, [this](Quotient::Room *room) {
|
||||
if (m_pendingChildren.contains(room->name())) {
|
||||
@@ -50,8 +56,6 @@ void SpaceChildrenModel::setSpace(NeoChatRoom *space)
|
||||
connect(m_space, &Quotient::Room::changed, this, [this]() {
|
||||
refreshModel();
|
||||
});
|
||||
|
||||
refreshModel();
|
||||
}
|
||||
|
||||
bool SpaceChildrenModel::loading() const
|
||||
@@ -69,6 +73,10 @@ void SpaceChildrenModel::refreshModel()
|
||||
m_currentJobs.clear();
|
||||
|
||||
if (m_space == nullptr) {
|
||||
beginResetModel();
|
||||
delete m_rootItem;
|
||||
m_rootItem = nullptr;
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -131,18 +139,18 @@ void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJ
|
||||
insertChildren(job->rooms(), index(insertRow, 0, parent));
|
||||
});
|
||||
}
|
||||
parentItem->insertChild(new SpaceTreeItem(dynamic_cast<NeoChatConnection *>(m_space->connection()),
|
||||
parentItem,
|
||||
children[i].roomId,
|
||||
children[i].name,
|
||||
children[i].canonicalAlias,
|
||||
children[i].topic,
|
||||
children[i].numJoinedMembers,
|
||||
children[i].avatarUrl,
|
||||
children[i].guestCanJoin,
|
||||
children[i].worldReadable,
|
||||
children[i].roomType == QLatin1String("m.space"),
|
||||
std::move(children[i].childrenState)));
|
||||
parentItem->insertChild(std::make_unique<SpaceTreeItem>(dynamic_cast<NeoChatConnection *>(m_space->connection()),
|
||||
parentItem,
|
||||
children[i].roomId,
|
||||
children[i].name,
|
||||
children[i].canonicalAlias,
|
||||
children[i].topic,
|
||||
children[i].numJoinedMembers,
|
||||
children[i].avatarUrl,
|
||||
children[i].guestCanJoin,
|
||||
children[i].worldReadable,
|
||||
children[i].roomType == QLatin1String("m.space"),
|
||||
std::move(children[i].childrenState)));
|
||||
}
|
||||
}
|
||||
endInsertRows();
|
||||
|
||||
@@ -131,7 +131,7 @@ Q_SIGNALS:
|
||||
void loadingChanged();
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_space = nullptr;
|
||||
QPointer<NeoChatRoom> m_space;
|
||||
SpaceTreeItem *m_rootItem;
|
||||
|
||||
bool m_loading = false;
|
||||
|
||||
@@ -32,30 +32,22 @@ SpaceTreeItem::SpaceTreeItem(NeoChatConnection *connection,
|
||||
{
|
||||
}
|
||||
|
||||
SpaceTreeItem::~SpaceTreeItem()
|
||||
{
|
||||
qDeleteAll(m_children);
|
||||
}
|
||||
|
||||
bool SpaceTreeItem::operator==(const SpaceTreeItem &other) const
|
||||
{
|
||||
return m_id == other.id();
|
||||
}
|
||||
|
||||
SpaceTreeItem *SpaceTreeItem::child(int number)
|
||||
SpaceTreeItem *SpaceTreeItem::child(int row)
|
||||
{
|
||||
if (number < 0 || number >= m_children.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_children[number];
|
||||
return row >= 0 && row < childCount() ? m_children.at(row).get() : nullptr;
|
||||
}
|
||||
|
||||
int SpaceTreeItem::childCount() const
|
||||
{
|
||||
return m_children.count();
|
||||
return int(m_children.size());
|
||||
}
|
||||
|
||||
bool SpaceTreeItem::insertChild(SpaceTreeItem *newChild)
|
||||
bool SpaceTreeItem::insertChild(std::unique_ptr<SpaceTreeItem> newChild)
|
||||
{
|
||||
if (newChild == nullptr) {
|
||||
return false;
|
||||
@@ -63,30 +55,39 @@ bool SpaceTreeItem::insertChild(SpaceTreeItem *newChild)
|
||||
|
||||
for (auto it = m_children.begin(), end = m_children.end(); it != end; ++it) {
|
||||
if (*it == newChild) {
|
||||
*it = newChild;
|
||||
*it = std::move(newChild);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
m_children.append(newChild);
|
||||
m_children.push_back(std::move(newChild));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SpaceTreeItem::removeChild(int row)
|
||||
{
|
||||
if (row < 0 || row >= m_children.size()) {
|
||||
if (row < 0 || row >= childCount()) {
|
||||
return false;
|
||||
}
|
||||
delete m_children.takeAt(row);
|
||||
m_children.erase(m_children.begin() + row);
|
||||
return true;
|
||||
}
|
||||
|
||||
int SpaceTreeItem::row() const
|
||||
{
|
||||
if (m_parentItem) {
|
||||
return m_parentItem->m_children.indexOf(const_cast<SpaceTreeItem *>(this));
|
||||
if (m_parentItem == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
|
||||
const auto it = std::find_if(m_parentItem->m_children.cbegin(), m_parentItem->m_children.cend(), [this](const std::unique_ptr<SpaceTreeItem> &treeItem) {
|
||||
return treeItem.get() == this;
|
||||
});
|
||||
|
||||
if (it != m_parentItem->m_children.cend()) {
|
||||
return std::distance(m_parentItem->m_children.cbegin(), it);
|
||||
}
|
||||
Q_ASSERT(false); // should not happen
|
||||
return -1;
|
||||
}
|
||||
|
||||
SpaceTreeItem *SpaceTreeItem::parentItem() const
|
||||
|
||||
@@ -33,7 +33,6 @@ public:
|
||||
bool worldReadable = {},
|
||||
bool isSpace = {},
|
||||
Quotient::StateEvents childStates = {});
|
||||
~SpaceTreeItem();
|
||||
|
||||
bool operator==(const SpaceTreeItem &other) const;
|
||||
|
||||
@@ -42,7 +41,7 @@ public:
|
||||
*
|
||||
* Nullptr is returned if there is no child at the given row number.
|
||||
*/
|
||||
SpaceTreeItem *child(int number);
|
||||
SpaceTreeItem *child(int row);
|
||||
|
||||
/**
|
||||
* @brief The number of children this item has.
|
||||
@@ -52,7 +51,7 @@ public:
|
||||
/**
|
||||
* @brief Insert the given child.
|
||||
*/
|
||||
bool insertChild(SpaceTreeItem *newChild);
|
||||
bool insertChild(std::unique_ptr<SpaceTreeItem> newChild);
|
||||
|
||||
/**
|
||||
* @brief Remove the child at the given row number.
|
||||
@@ -151,7 +150,7 @@ public:
|
||||
|
||||
private:
|
||||
NeoChatConnection *m_connection;
|
||||
QList<SpaceTreeItem *> m_children;
|
||||
std::vector<std::unique_ptr<SpaceTreeItem>> m_children;
|
||||
SpaceTreeItem *m_parentItem;
|
||||
|
||||
QString m_id;
|
||||
|
||||
@@ -55,11 +55,14 @@ void StateKeysModel::setRoom(NeoChatRoom *room)
|
||||
|
||||
m_room = room;
|
||||
Q_EMIT roomChanged();
|
||||
loadState();
|
||||
|
||||
connect(room, &NeoChatRoom::changed, this, [this] {
|
||||
if (room) {
|
||||
loadState();
|
||||
});
|
||||
|
||||
connect(room, &NeoChatRoom::changed, this, [this] {
|
||||
loadState();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QString StateKeysModel::eventType() const
|
||||
|
||||
@@ -76,7 +76,7 @@ Q_SIGNALS:
|
||||
void eventTypeChanged();
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
QString m_eventType;
|
||||
QVector<const Quotient::StateEvent *> m_stateKeys;
|
||||
void loadState();
|
||||
|
||||
@@ -22,7 +22,9 @@ QVariant StickerModel::data(const QModelIndex &index, int role) const
|
||||
const auto &row = index.row();
|
||||
const auto &image = m_images[row];
|
||||
if (role == UrlRole) {
|
||||
return m_room->connection()->makeMediaUrl(image.url);
|
||||
if (m_room) {
|
||||
return m_room->connection()->makeMediaUrl(image.url);
|
||||
}
|
||||
}
|
||||
if (role == BodyRole) {
|
||||
if (image.body) {
|
||||
@@ -108,6 +110,10 @@ void StickerModel::setRoom(NeoChatRoom *room)
|
||||
|
||||
void StickerModel::postSticker(int index)
|
||||
{
|
||||
if (!m_room) {
|
||||
qWarning() << "No room";
|
||||
}
|
||||
|
||||
const auto &image = m_images[index];
|
||||
const auto &body = image.body ? *image.body : image.shortcode;
|
||||
QJsonObject infoJson;
|
||||
|
||||
@@ -101,6 +101,6 @@ private:
|
||||
ImagePacksModel *m_model = nullptr;
|
||||
int m_index = 0;
|
||||
QList<Quotient::ImagePackEventContent::ImagePackImage> m_images;
|
||||
NeoChatRoom *m_room;
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
void reloadImages();
|
||||
};
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
QPointer<NeoChatRoom> m_room = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,7 @@ Name[it]=NeoChat
|
||||
Name[ka]=NeoChat
|
||||
Name[ko]=NeoChat
|
||||
Name[lt]=NeoChat
|
||||
Name[lv]=NeoChat
|
||||
Name[nl]=NeoChat
|
||||
Name[nn]=NeoChat
|
||||
Name[pa]=ਨਿਓ-ਚੈਟ
|
||||
@@ -65,6 +66,7 @@ Comment[it]=Un client per matrix, il protocollo di comunicazione decentralizzato
|
||||
Comment[ka]=კლიენტი Matrix-სთვის, დეცენტრალიზებული კომუნიკაციის პროტოკოლისთვის
|
||||
Comment[ko]=Matrix, 분산 대화 프로토콜 클라이언트
|
||||
Comment[lt]=Matrix decentralizuoto bendravimo protokolo kliento programa
|
||||
Comment[lv]=Klients „Matrix“ protokolam — decentralizētam komunikācijas protokolam
|
||||
Comment[nl]=Een client voor matrix, het gedecentraliseerde communicatieprotocol
|
||||
Comment[nn]=Ein klient for Matrix – protokollen for desentralisert kommunikasjon
|
||||
Comment[pa]=ਮੈਟਰਿਕਸ, ਸਰਬ-ਸਾਂਝੇ ਸੰਚਾਰ ਪਰੋਟੋਕਾਲ, ਲਈ ਕਲਾਈਂਟ ਹੈ
|
||||
@@ -108,6 +110,7 @@ Name[it]=Nuovo messaggio
|
||||
Name[ka]=ახალი შეტყობინება
|
||||
Name[ko]=새 메시지
|
||||
Name[lt]=Nauja žinutė
|
||||
Name[lv]=Jauns ziņojums
|
||||
Name[nl]=Nieuw bericht
|
||||
Name[nn]=Ny melding
|
||||
Name[pa]=ਨਵਾਂ ਸੁਨੇਹਾ
|
||||
@@ -147,6 +150,7 @@ Comment[it]=È presente un nuovo messaggio
|
||||
Comment[ka]=გაქვთ ახალი შეტყობინება
|
||||
Comment[ko]=새 메시지가 있음
|
||||
Comment[lt]=Yra nauja žinutė
|
||||
Comment[lv]=Ir pienācis jauns ziņojums
|
||||
Comment[nl]=Er is een nieuw bericht
|
||||
Comment[nn]=Du har ei ny melding
|
||||
Comment[pa]=ਨਵਾਂ ਸੁਨੇਹਾ ਹੈ
|
||||
@@ -190,6 +194,7 @@ Name[it]=Nuovo invito
|
||||
Name[ka]=ახალი მოსაწვევი
|
||||
Name[ko]=새 초대장
|
||||
Name[lt]=Naujas pakvietimas
|
||||
Name[lv]=Jauns uzaicinājums
|
||||
Name[nl]=Nieuwe uitnodiging
|
||||
Name[nn]=Ny invitasjon
|
||||
Name[pa]=ਨਵਾਂ ਸੱਦਾ
|
||||
@@ -228,6 +233,7 @@ Comment[it]=È presente un nuovo invito a una stanza
|
||||
Comment[ka]=გაქვთ ახალი ოთახის მოსაწვევი
|
||||
Comment[ko]=새로운 대화방 초대장을 받음
|
||||
Comment[lt]=Yra naujas pakvietimas į kambarį
|
||||
Comment[lv]=Istabā ir jauns uzaicinājums
|
||||
Comment[nl]=Er is een nieuwe uitnodiging naar een room
|
||||
Comment[nn]=Du har ein ny invitasjon til eit rom
|
||||
Comment[pa]=ਰੂਮ ਲਈ ਨਵਾਂ ਸੱਦਾ ਹੈ
|
||||
@@ -258,6 +264,7 @@ Name[hu]=Megosztás
|
||||
Name[ia]=Comparti
|
||||
Name[it]=Condivisione
|
||||
Name[ka]=გაზიარება
|
||||
Name[lv]=Kopīgot
|
||||
Name[nl]=Gedeelde
|
||||
Name[pl]=Udostępnij
|
||||
Name[sl]=Deli
|
||||
@@ -277,6 +284,7 @@ Comment[hu]=Tartalom megosztásának eredménye
|
||||
Comment[ia]=Le exito de compartir un pecietta de contento
|
||||
Comment[it]=Il risultato della condivisione di un contenuto
|
||||
Comment[ka]=შემცველობის ნაწილის გაზიარების შედეგი
|
||||
Comment[lv]=Satura kopīgošanas rezultāts
|
||||
Comment[nl]=Het resultaat van het delen van een stukje inhoud
|
||||
Comment[pl]=Wynik udostępniania kawałka treści
|
||||
Comment[sl]=Rezultat deljenega kosa vsebine
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user