Compare commits
689 Commits
v1.0.1
...
work/purpo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a27a1e70f | ||
|
|
f2cf82ee8e | ||
|
|
a146fab5a0 | ||
|
|
fb6745b49a | ||
|
|
6c3ae87340 | ||
|
|
6afeaf1619 | ||
|
|
890860df92 | ||
|
|
6b8358874a | ||
|
|
fc9f37d4a4 | ||
|
|
48e410196c | ||
|
|
65cc392805 | ||
|
|
a6dd5b9a57 | ||
|
|
1d7c20e1c7 | ||
|
|
22609b21df | ||
|
|
6c5ca0ac9d | ||
|
|
b22ebf3671 | ||
|
|
ec1cc34855 | ||
|
|
a5aafde331 | ||
|
|
d42ad85b30 | ||
|
|
8648b4a3bf | ||
|
|
bdca636fb8 | ||
|
|
7bb7b7850b | ||
|
|
82d49b91e7 | ||
|
|
ecc6c6e2eb | ||
|
|
e6c594f3d2 | ||
|
|
97f8ad88fb | ||
|
|
14a358ed58 | ||
|
|
1e78c6caa4 | ||
|
|
2fc4c6456f | ||
|
|
76675719cf | ||
|
|
6b331ca8b4 | ||
|
|
7d6faaafea | ||
|
|
4e26dc5d57 | ||
|
|
304c74101e | ||
|
|
c844f2a157 | ||
|
|
21c571e2a1 | ||
|
|
011cf3c652 | ||
|
|
6b86c113f4 | ||
|
|
4511c1a07e | ||
|
|
1345ddc9ee | ||
|
|
452380e274 | ||
|
|
cd745d1df7 | ||
|
|
5bf0aa7a68 | ||
|
|
fa631ece3a | ||
|
|
334930808c | ||
|
|
c105642e4c | ||
|
|
6a0b51a78e | ||
|
|
00ecb4703e | ||
|
|
3977aee4ba | ||
|
|
e9f935609f | ||
|
|
886ee1f5b6 | ||
|
|
71d01593b1 | ||
|
|
6dab24bc74 | ||
|
|
7c7d296981 | ||
|
|
d28279313d | ||
|
|
b3d9861d3d | ||
|
|
218eda63b3 | ||
|
|
cb2ea5f4e3 | ||
|
|
50040230f5 | ||
|
|
da7b3a1ea9 | ||
|
|
0a39fccec8 | ||
|
|
132c9c50fa | ||
|
|
821993c443 | ||
|
|
8118068fb5 | ||
|
|
68830bae97 | ||
|
|
c421d4047b | ||
|
|
9cdf6bfa04 | ||
|
|
bda7326d3b | ||
|
|
9d4151dd00 | ||
|
|
af29925798 | ||
|
|
c1ee480823 | ||
|
|
f5853dd1f7 | ||
|
|
bc76197487 | ||
|
|
83d03b0e06 | ||
|
|
2a9d739bc9 | ||
|
|
44bc2388c6 | ||
|
|
35b88de526 | ||
|
|
7dc48cabcb | ||
|
|
98a35fe09f | ||
|
|
caafd8c0bc | ||
|
|
5795bf5429 | ||
|
|
c39e46a1f0 | ||
|
|
8d59e2ba67 | ||
|
|
265fcbfead | ||
|
|
5c86692fb5 | ||
|
|
e778cd72af | ||
|
|
9e709e4741 | ||
|
|
868fe7543e | ||
|
|
0c95556618 | ||
|
|
31d395009e | ||
|
|
710f4d86d6 | ||
|
|
1d5b7dee94 | ||
|
|
c9935804b0 | ||
|
|
6570cf396a | ||
|
|
bd08f7fd36 | ||
|
|
2ccde21896 | ||
|
|
cd461da413 | ||
|
|
df4df171dd | ||
|
|
61202dcd5f | ||
|
|
589dfe0343 | ||
|
|
0f106b1bbc | ||
|
|
d7e9bc4c14 | ||
|
|
d1451780c2 | ||
|
|
b72cde9543 | ||
|
|
046e823d1b | ||
|
|
dada3e300b | ||
|
|
4955b1f7a0 | ||
|
|
d7ce0b7468 | ||
|
|
94c4bbc3db | ||
|
|
ecc639fb0a | ||
|
|
21bd5fa94e | ||
|
|
17bbc60f6f | ||
|
|
e064243d66 | ||
|
|
e91a4f79a5 | ||
|
|
9e4d0ddc9b | ||
|
|
2554ce55ed | ||
|
|
de98fd05f0 | ||
|
|
383d2a6e71 | ||
|
|
db1a9a0c4c | ||
|
|
4658574732 | ||
|
|
4efd1207ee | ||
|
|
d92b1895a9 | ||
|
|
3be9c0cfb3 | ||
|
|
1427c8bf3f | ||
|
|
adbbb7e42a | ||
|
|
e187110680 | ||
|
|
77318436ad | ||
|
|
db77dad0c2 | ||
|
|
73245c0f00 | ||
|
|
07d4d384f3 | ||
|
|
4b75e7d588 | ||
|
|
00494f8f88 | ||
|
|
de7354f056 | ||
|
|
2e1ab639c6 | ||
|
|
40f0893048 | ||
|
|
5245d2399f | ||
|
|
bbf9eb2ddc | ||
|
|
592d3160df | ||
|
|
97737b753b | ||
|
|
f8db9c4ecb | ||
|
|
9567c7ecf7 | ||
|
|
df6c2a85f6 | ||
|
|
ae60834c36 | ||
|
|
44de5c1730 | ||
|
|
35aac4e362 | ||
|
|
73de1af321 | ||
|
|
aba96ffb83 | ||
|
|
537416354a | ||
|
|
6bbb4b4985 | ||
|
|
7411447f22 | ||
|
|
84bc207174 | ||
|
|
54f5097ff4 | ||
|
|
dd841bb836 | ||
|
|
92eb06aaa6 | ||
|
|
3e8c7caefd | ||
|
|
b1d8956036 | ||
|
|
f2215f10e2 | ||
|
|
211f6004eb | ||
|
|
9ec20dc02d | ||
|
|
680b0cc3bd | ||
|
|
8474136f57 | ||
|
|
81d0db7f1e | ||
|
|
b8a341eda8 | ||
|
|
d7345ee4e6 | ||
|
|
6487ea7414 | ||
|
|
edd5fadbde | ||
|
|
9ad5a7b02b | ||
|
|
768fd74361 | ||
|
|
6898670499 | ||
|
|
30965cb503 | ||
|
|
9961483f5c | ||
|
|
d5cd175d71 | ||
|
|
c79d835b5c | ||
|
|
75b70dc6a2 | ||
|
|
56ca6bf2a4 | ||
|
|
9f6d8aa550 | ||
|
|
8c1129f88e | ||
|
|
a086964769 | ||
|
|
5c6a540807 | ||
|
|
b412719f2c | ||
|
|
68194055aa | ||
|
|
d1d4d6ef38 | ||
|
|
0ff9425fee | ||
|
|
5cb8424a83 | ||
|
|
ff8a71663a | ||
|
|
0d0f180c3e | ||
|
|
efb70287b9 | ||
|
|
8f309ca958 | ||
|
|
23bd73c499 | ||
|
|
d9eaa95b9d | ||
|
|
51ca4994ef | ||
|
|
01f861fbbd | ||
|
|
3b86d0088d | ||
|
|
ce15a8d697 | ||
|
|
5d927065bb | ||
|
|
60d78036ed | ||
|
|
d55e27b791 | ||
|
|
649f7716d6 | ||
|
|
4539aa3442 | ||
|
|
87d1fefae2 | ||
|
|
6e5bca4928 | ||
|
|
4d236a201b | ||
|
|
a015497efe | ||
|
|
3e78bff8a1 | ||
|
|
807112fb19 | ||
|
|
e15e10d319 | ||
|
|
c7fd5cc511 | ||
|
|
b37152ff89 | ||
|
|
562b59d834 | ||
|
|
df0ad391ba | ||
|
|
3329739d55 | ||
|
|
7bec8c73f8 | ||
|
|
d9125148fe | ||
|
|
7cd9f788dd | ||
|
|
db0f421811 | ||
|
|
8a55db4eb8 | ||
|
|
3d251b9b25 | ||
|
|
13888401fa | ||
|
|
d6394fcd47 | ||
|
|
bbcf4239a4 | ||
|
|
92fcff1dce | ||
|
|
41838d01df | ||
|
|
af75cebba1 | ||
|
|
1cec672c0a | ||
|
|
bd5f6c9c9e | ||
|
|
6e04d343b7 | ||
|
|
454e35433b | ||
|
|
4dea02197c | ||
|
|
294f0c7e1a | ||
|
|
d14674c2cd | ||
|
|
49c1736f7c | ||
|
|
db62f06de4 | ||
|
|
34da8734a2 | ||
|
|
0dbb56ba1e | ||
|
|
93064ec5bf | ||
|
|
7bdfdc0eec | ||
|
|
bae7813f68 | ||
|
|
dded804f00 | ||
|
|
4026bf10b2 | ||
|
|
e04234a0d4 | ||
|
|
c755084bb0 | ||
|
|
dbb43addc8 | ||
|
|
7939055640 | ||
|
|
135b2e49fa | ||
|
|
011f649cbf | ||
|
|
36a2f5719f | ||
|
|
89d4c1ff95 | ||
|
|
af6880b2ca | ||
|
|
48d1fa27cf | ||
|
|
bd893adb34 | ||
|
|
b48c9bdadc | ||
|
|
116f883699 | ||
|
|
3ea783b370 | ||
|
|
b5edfc909e | ||
|
|
0bc51627bb | ||
|
|
a24df37d74 | ||
|
|
20a7672008 | ||
|
|
eb300e0beb | ||
|
|
141d1d15d5 | ||
|
|
b4cb3259e1 | ||
|
|
773e633867 | ||
|
|
7ac232d372 | ||
|
|
5c0bfee6e1 | ||
|
|
179139c623 | ||
|
|
596cb00367 | ||
|
|
407b071e04 | ||
|
|
1eab77ed01 | ||
|
|
8f403012c2 | ||
|
|
ddc0d5c255 | ||
|
|
7a065c18b6 | ||
|
|
f9ae1f97c4 | ||
|
|
aec11964e0 | ||
|
|
f20501fe34 | ||
|
|
af409aa9a2 | ||
|
|
873ab328dc | ||
|
|
afa7b822f9 | ||
|
|
5f8795c41f | ||
|
|
1615695b21 | ||
|
|
35f7b29e54 | ||
|
|
52cce4eb94 | ||
|
|
ff79ff8fa7 | ||
|
|
fbdf9999a6 | ||
|
|
173d8075ad | ||
|
|
17b6f4e78a | ||
|
|
757cc99ff0 | ||
|
|
3f20534e4a | ||
|
|
32756c56f6 | ||
|
|
28d01167b6 | ||
|
|
145532c89d | ||
|
|
8314e19cb1 | ||
|
|
f5a42e64ee | ||
|
|
1e7d3046aa | ||
|
|
471b525151 | ||
|
|
07bee8d9de | ||
|
|
0a51c845e6 | ||
|
|
7b1c5f5aab | ||
|
|
a329790129 | ||
|
|
d77d8c3835 | ||
|
|
2139112301 | ||
|
|
affcbca199 | ||
|
|
ac9dcb48c7 | ||
|
|
fcfde394ad | ||
|
|
24bf460e9f | ||
|
|
ea403eb679 | ||
|
|
18a2d6d6d6 | ||
|
|
584cd59f93 | ||
|
|
461128c6a7 | ||
|
|
929e21fc59 | ||
|
|
472490f257 | ||
|
|
ccb12e1bed | ||
|
|
75dc3e6611 | ||
|
|
32c21b7b84 | ||
|
|
e3558f5bbd | ||
|
|
6c47594bc3 | ||
|
|
859b27ddb7 | ||
|
|
539d519987 | ||
|
|
465b0f8b4c | ||
|
|
6781c0c964 | ||
|
|
e234861ee6 | ||
|
|
6575d23072 | ||
|
|
ee595ed374 | ||
|
|
57684aa454 | ||
|
|
e8816310d2 | ||
|
|
023cb2a991 | ||
|
|
1066b010b8 | ||
|
|
f30b17bf73 | ||
|
|
b7d98fc6d9 | ||
|
|
a2a6983123 | ||
|
|
7e778d225b | ||
|
|
fefcbbe190 | ||
|
|
6487adafb8 | ||
|
|
6b21289af3 | ||
|
|
59f89d96e1 | ||
|
|
d5d83ff7b8 | ||
|
|
08632b4178 | ||
|
|
a117eaa12b | ||
|
|
89056ed6c1 | ||
|
|
f98eb78185 | ||
|
|
00681a8abe | ||
|
|
077844ba61 | ||
|
|
dcb135c2ca | ||
|
|
23b7945f4c | ||
|
|
66545dbc31 | ||
|
|
25a8a8b011 | ||
|
|
41b53b5245 | ||
|
|
71fcc20943 | ||
|
|
7f63b58067 | ||
|
|
3a4f44f2a9 | ||
|
|
52f87d6344 | ||
|
|
ff8c3eb282 | ||
|
|
becb3a1870 | ||
|
|
86c43ce169 | ||
|
|
f309460879 | ||
|
|
eed14e9c14 | ||
|
|
b68902cd0d | ||
|
|
352c55418b | ||
|
|
dddeb108ce | ||
|
|
d170cc5161 | ||
|
|
9932823ad3 | ||
|
|
aa2d332b89 | ||
|
|
2cb81d1276 | ||
|
|
9888e8424f | ||
|
|
592c33ed8b | ||
|
|
095174f87e | ||
|
|
6b0a51a216 | ||
|
|
7078298892 | ||
|
|
a2f35e142e | ||
|
|
57d678d4d8 | ||
|
|
94ad17d50e | ||
|
|
7ba58994a7 | ||
|
|
73f18f4fe9 | ||
|
|
78f7f815ca | ||
|
|
e4ab2e565f | ||
|
|
025f00e99d | ||
|
|
40daa1e6b8 | ||
|
|
f007e96fdf | ||
|
|
75a2ba86ee | ||
|
|
71d4b8763e | ||
|
|
44a7b3c700 | ||
|
|
868d8108ac | ||
|
|
eab5a43a2e | ||
|
|
b268e82e0e | ||
|
|
95f4f4fc90 | ||
|
|
c24ab098c6 | ||
|
|
4e02fa8290 | ||
|
|
0b2bd84085 | ||
|
|
7da342e629 | ||
|
|
d256287bef | ||
|
|
2fadaf3d79 | ||
|
|
24d08dbe91 | ||
|
|
e79df870e2 | ||
|
|
662c570371 | ||
|
|
4adc1fc031 | ||
|
|
bb3f4297b0 | ||
|
|
308af0d3cd | ||
|
|
4f52c5293b | ||
|
|
997972a3d3 | ||
|
|
9f637ab925 | ||
|
|
a1fb3471c9 | ||
|
|
d631731fcf | ||
|
|
8c492cc23e | ||
|
|
128ff82958 | ||
|
|
d0104009a9 | ||
|
|
0b4c578c19 | ||
|
|
e1327dfde0 | ||
|
|
05548bc8a9 | ||
|
|
9b916edfdc | ||
|
|
c9d86c6a36 | ||
|
|
2c4cc9672d | ||
|
|
684226a4ef | ||
|
|
96c402040d | ||
|
|
f44716d9b0 | ||
|
|
e15810ab0c | ||
|
|
dec5e40acd | ||
|
|
df2933ff01 | ||
|
|
1bc2719665 | ||
|
|
1d06feee11 | ||
|
|
b5fda7175e | ||
|
|
d963814552 | ||
|
|
11c2e56320 | ||
|
|
603d4e1f0d | ||
|
|
743c9972b9 | ||
|
|
c7df3f903a | ||
|
|
d928c2e02d | ||
|
|
4ef75cfdf3 | ||
|
|
b13082a8d4 | ||
|
|
ac94204687 | ||
|
|
b770213e09 | ||
|
|
361605df71 | ||
|
|
798c5e8b7c | ||
|
|
e2aefb6bdc | ||
|
|
dd20df5c26 | ||
|
|
525d691cf8 | ||
|
|
32f3748ced | ||
|
|
b349c2376d | ||
|
|
8e5ca78249 | ||
|
|
0e521f5b03 | ||
|
|
ee9f521a37 | ||
|
|
38e2c7222b | ||
|
|
b67f03d33f | ||
|
|
8f1f02fa22 | ||
|
|
0289822e6c | ||
|
|
571ee638bd | ||
|
|
fa4ecd7d41 | ||
|
|
53670f5e81 | ||
|
|
879009a6f7 | ||
|
|
4860330c27 | ||
|
|
a1ee00147b | ||
|
|
e569936a85 | ||
|
|
21fb674f7d | ||
|
|
650365213d | ||
|
|
03a1562b23 | ||
|
|
612fb4924e | ||
|
|
724a579f0d | ||
|
|
17930e2e2c | ||
|
|
34311e4d48 | ||
|
|
b6787ae242 | ||
|
|
a9678b6fc3 | ||
|
|
e9d7cd5be0 | ||
|
|
699a86ef2e | ||
|
|
7f13bb81f8 | ||
|
|
3e4fe7862d | ||
|
|
2f06d45589 | ||
|
|
ffe9026830 | ||
|
|
418f22932d | ||
|
|
5692066bbc | ||
|
|
63d05272fa | ||
|
|
70b15103aa | ||
|
|
481a2e3681 | ||
|
|
1351dff514 | ||
|
|
74b3a83624 | ||
|
|
d1a029806d | ||
|
|
12624c991c | ||
|
|
7ddd28406d | ||
|
|
12fa970544 | ||
|
|
6fca7830a3 | ||
|
|
250398dc0d | ||
|
|
668968990c | ||
|
|
3c12eff304 | ||
|
|
008d19e68b | ||
|
|
01f8c3b09f | ||
|
|
09dff4553a | ||
|
|
45c9295d49 | ||
|
|
5ab44f1897 | ||
|
|
c2aab690b6 | ||
|
|
9d6d7789bb | ||
|
|
e7a862a1d9 | ||
|
|
cd17339847 | ||
|
|
89f9ec1ba6 | ||
|
|
b67a35bfe3 | ||
|
|
37a681596b | ||
|
|
f71bbe20dc | ||
|
|
7ff54f62f3 | ||
|
|
cc2b183fc5 | ||
|
|
2558e1f6b5 | ||
|
|
a7e61f0e20 | ||
|
|
345eb0c229 | ||
|
|
91ef8806f2 | ||
|
|
e546c12b45 | ||
|
|
43f81fcead | ||
|
|
bfbca5b1c2 | ||
|
|
babbc039ab | ||
|
|
d3b8c9b98e | ||
|
|
0ca2eb4008 | ||
|
|
3979cf59ce | ||
|
|
f4ab281789 | ||
|
|
7d100b2a0f | ||
|
|
9432e28685 | ||
|
|
b84375749b | ||
|
|
07dffa7e73 | ||
|
|
7816d220ea | ||
|
|
28dfc4b6d7 | ||
|
|
d78196d7c7 | ||
|
|
21c4f8b636 | ||
|
|
4c7be7426e | ||
|
|
bd4dfb037a | ||
|
|
a7720409ca | ||
|
|
b689e55068 | ||
|
|
464c48540e | ||
|
|
e7bada4cde | ||
|
|
9cd441dc1d | ||
|
|
ff6bff208a | ||
|
|
7ae222d427 | ||
|
|
82945ab153 | ||
|
|
1411d28b81 | ||
|
|
6dcfad1f8d | ||
|
|
10054bf879 | ||
|
|
66b06aac6e | ||
|
|
c17392bd9d | ||
|
|
1cb6b3bbd6 | ||
|
|
546d17b1a2 | ||
|
|
72907a1f18 | ||
|
|
465334e23f | ||
|
|
66bcc2105a | ||
|
|
f217bbd3c4 | ||
|
|
70691fb295 | ||
|
|
7aedfd0e17 | ||
|
|
92e00587f7 | ||
|
|
ab4db4dd3d | ||
|
|
b4e996aecd | ||
|
|
5c8b9c0803 | ||
|
|
c1d5883af9 | ||
|
|
dae7ee2f67 | ||
|
|
e6f2b5ea7f | ||
|
|
9603811a6d | ||
|
|
d9cf7ce552 | ||
|
|
4894470e7d | ||
|
|
8e6d1a8ea2 | ||
|
|
c2b388d553 | ||
|
|
f67f319854 | ||
|
|
fde637b1df | ||
|
|
75d3b346ac | ||
|
|
80b6d80c7d | ||
|
|
0f043e36c4 | ||
|
|
b9d34487a4 | ||
|
|
157f7cd870 | ||
|
|
cec47b40cc | ||
|
|
f7cbb876f0 | ||
|
|
6f7f0e025d | ||
|
|
30eeb538e0 | ||
|
|
fe1e3ee374 | ||
|
|
a653be8be8 | ||
|
|
6893cb361e | ||
|
|
038441b854 | ||
|
|
b7da732a15 | ||
|
|
7762f5f5ae | ||
|
|
1abc28ad7f | ||
|
|
c24c25eb38 | ||
|
|
bd11f543f5 | ||
|
|
085ebaa451 | ||
|
|
e5771e2733 | ||
|
|
3ebda274ef | ||
|
|
3ac85bacad | ||
|
|
eff8c08ccf | ||
|
|
a9c2e3ec49 | ||
|
|
92488343cc | ||
|
|
59df28822c | ||
|
|
8b0a14a2cf | ||
|
|
d9128ca483 | ||
|
|
07f637c854 | ||
|
|
a3e1e1d0a4 | ||
|
|
ed26e87c96 | ||
|
|
f4784bb0a1 | ||
|
|
a82b9dc14e | ||
|
|
2cb38ad1ea | ||
|
|
4be3eac7af | ||
|
|
de23eef519 | ||
|
|
cd1bec9977 | ||
|
|
8e846f73d7 | ||
|
|
af7003e680 | ||
|
|
cb57a1ec06 | ||
|
|
2e0096380f | ||
|
|
ca5f95b298 | ||
|
|
f6ac8ccb45 | ||
|
|
249054b57f | ||
|
|
ab8dabc280 | ||
|
|
43c7e00ec5 | ||
|
|
085bd4a2cf | ||
|
|
6cc29f0254 | ||
|
|
338553de16 | ||
|
|
9a17c07fdd | ||
|
|
50d8bd5b7e | ||
|
|
f232c40955 | ||
|
|
cbc082c1b6 | ||
|
|
d45b0675fb | ||
|
|
c60ee602e2 | ||
|
|
8224d3ae9f | ||
|
|
4463e3e3f2 | ||
|
|
e6b97e3350 | ||
|
|
2c1cbc91d8 | ||
|
|
bd00a73aa9 | ||
|
|
88cc972edc | ||
|
|
5c8d916752 | ||
|
|
9ba0a755e4 | ||
|
|
4e765f51a7 | ||
|
|
60e0ad9c48 | ||
|
|
5a831732c5 | ||
|
|
494e6dca42 | ||
|
|
feb123c1e6 | ||
|
|
1f065e46cf | ||
|
|
a4cebe9b36 | ||
|
|
a929f7bca3 | ||
|
|
f00cd82676 | ||
|
|
c69d3587ba | ||
|
|
9d82ebb0ed | ||
|
|
724f10a895 | ||
|
|
0fe0f45944 | ||
|
|
b1080df9dd | ||
|
|
066ab1e6c6 | ||
|
|
a52dbb0042 | ||
|
|
6a1fd3ff31 | ||
|
|
9c97983794 | ||
|
|
3858956e82 | ||
|
|
93e0a2b2f6 | ||
|
|
dce3b796c2 | ||
|
|
a5cf0af004 | ||
|
|
e55f0bd84b | ||
|
|
c449a8fafe | ||
|
|
6351454759 | ||
|
|
8aec6b67cb | ||
|
|
c515f1bdbd | ||
|
|
43b094d446 | ||
|
|
1a28e52d79 | ||
|
|
57e05e2114 | ||
|
|
b4e528b047 | ||
|
|
59f9c36854 | ||
|
|
3fe10bfc3c | ||
|
|
8f348eb4fd | ||
|
|
93f35faf95 | ||
|
|
87a7a34d80 | ||
|
|
a1b66f3aa6 | ||
|
|
218f897229 | ||
|
|
ef8c21213a | ||
|
|
dbc82b113b | ||
|
|
f65b494422 | ||
|
|
74c6cc928b | ||
|
|
b3899f1e69 | ||
|
|
44da1ca1bf | ||
|
|
6a4b1983a1 | ||
|
|
6482f08eba | ||
|
|
f61eff2937 | ||
|
|
af65884094 | ||
|
|
449adf993c | ||
|
|
9189a8ca30 | ||
|
|
9c75deee8c | ||
|
|
3fcb40f9dd | ||
|
|
6e659c853b | ||
|
|
eb95813f67 | ||
|
|
00e6584f84 | ||
|
|
13bcb5c0ff | ||
|
|
8312fdd08d | ||
|
|
bd41dcc986 | ||
|
|
2b84c5dd02 | ||
|
|
79dab63993 | ||
|
|
8e2cdc8f08 | ||
|
|
d6e56174b5 | ||
|
|
4e57e47def | ||
|
|
5d80fdfcb6 | ||
|
|
2056d75ee7 | ||
|
|
83098d11b9 | ||
|
|
8435243c7a | ||
|
|
3eb53c2456 | ||
|
|
54b07737c0 | ||
|
|
7ae45d37a8 | ||
|
|
1ec62870f8 | ||
|
|
72fd647b7b | ||
|
|
d646962ea1 | ||
|
|
a18ecdddb2 | ||
|
|
3c5ee404c3 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,3 +3,6 @@ build
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.kdev4/
|
.kdev4/
|
||||||
neochat.kdev4
|
neochat.kdev4
|
||||||
|
compile_commands.json
|
||||||
|
.cache/
|
||||||
|
.vscode/
|
||||||
|
|||||||
42
.gitlab/issue_templates/bug.md
Normal file
42
.gitlab/issue_templates/bug.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!--
|
||||||
|
Hello, thanks for reporting a bug. To help us with the debugging,
|
||||||
|
please make sure to fill all the recommended information.
|
||||||
|
Thanks!
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
(General description of the bug)
|
||||||
|
|
||||||
|
## Steps to reproduce
|
||||||
|
|
||||||
|
1. Open app
|
||||||
|
2. ...
|
||||||
|
3. ...
|
||||||
|
|
||||||
|
## What is the current bug behavior?
|
||||||
|
|
||||||
|
(What actually happens)
|
||||||
|
|
||||||
|
## What is the expected correct behavior?
|
||||||
|
|
||||||
|
(What you should see instead)
|
||||||
|
|
||||||
|
## Relevant logs and/or screenshots
|
||||||
|
|
||||||
|
(Paste any relevant logs - please use code blocks (```) to format console output, logs, and code, as
|
||||||
|
it's very hard to read otherwise.)
|
||||||
|
|
||||||
|
## Possible fixes
|
||||||
|
|
||||||
|
(If you can, link to the line of code that might be responsible for the problem)
|
||||||
|
|
||||||
|
## System/Matrix Information
|
||||||
|
|
||||||
|
- **Distribution / Platform:** Ubuntu 20.04, openSUSE, Flatpak, Windows, MacOS, Android, ...
|
||||||
|
- **Qt Version:** 5.15.2
|
||||||
|
- **NeoChat version:** 1.2
|
||||||
|
- **Quotient version:** 0.6.6
|
||||||
|
- **Matrix server:** matrix.org, kde.org, ....
|
||||||
|
|
||||||
|
/label ~Bug
|
||||||
44
.reuse/dep5
Normal file
44
.reuse/dep5
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: NeoChat
|
||||||
|
Upstream-Contact: Carl Schwan <carlschwan@kde.org>
|
||||||
|
|
||||||
|
Files: 128-logo.png icons/* logo.png org.kde.neochat.svg org.kde.neochat-symbolic.svg android/res/drawable/neochat.png
|
||||||
|
Copyright: 2020 Carson Black <uhhadd@gmail.com>
|
||||||
|
License: LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||||
|
|
||||||
|
Files: qtquickcontrols2.conf
|
||||||
|
Copyright: 2020 Tobias Fella <fella@posteo.de>
|
||||||
|
License: CC0-1.0
|
||||||
|
|
||||||
|
Files: android/res/drawable/splash.xml
|
||||||
|
Copyright: 2020 Tobias Fella <fella@posteo.de>
|
||||||
|
License: BSD-2-Clause
|
||||||
|
|
||||||
|
Files: */qmldir .gitignore
|
||||||
|
Copyright: None
|
||||||
|
License: CC0-1.0
|
||||||
|
|
||||||
|
Files: .gitlab/issue_templates/bug.md
|
||||||
|
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
|
||||||
|
License: CC0-1.0
|
||||||
|
|
||||||
|
Files: res.qrc
|
||||||
|
Copyright: None
|
||||||
|
License: CC0-1.0
|
||||||
|
|
||||||
|
Files: cmake/Flatpak/99-noto-mono-color-emoji.conf
|
||||||
|
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
|
||||||
|
License: BSD-2-Clause
|
||||||
|
|
||||||
|
Files: src/neochatconfig.kcfg
|
||||||
|
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>
|
||||||
|
Copyright: 2020-2021 Tobias Fella <fella@posteo.de>
|
||||||
|
License: BSD-2-Clause
|
||||||
|
|
||||||
|
Files: neochat.notifyrc
|
||||||
|
Copyright: 2020 Tobias Fella <fella@posteo.de>
|
||||||
|
License: BSD-2-Clause
|
||||||
|
|
||||||
|
Files: imports/NeoChat/Component/confetti.png imports/NeoChat/Component/glowdot.png
|
||||||
|
Copyright: 2021 Alexey Andreyev <aa13q@ya.ru>
|
||||||
|
License: CC0-1.0
|
||||||
BIN
128-logo.png
Normal file
BIN
128-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
@@ -1,17 +1,25 @@
|
|||||||
cmake_minimum_required(VERSION 3.1)
|
# SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
# SPDX-FileCopyrightText: 2020-2021 Nicolas Fella <nicolas.fella@gmx.de>
|
||||||
|
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
|
||||||
|
# SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
project(Neochat)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
set(KF5_MIN_VERSION "5.76.0")
|
project(NeoChat)
|
||||||
|
|
||||||
|
set(KF5_MIN_VERSION "5.86.0")
|
||||||
set(QT_MIN_VERSION "5.15.0")
|
set(QT_MIN_VERSION "5.15.0")
|
||||||
|
|
||||||
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
|
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
|
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
set(KDE_COMPILERSETTINGS_LEVEL 5.84)
|
||||||
|
|
||||||
include(FeatureSummary)
|
include(FeatureSummary)
|
||||||
include(ECMSetupVersion)
|
include(ECMSetupVersion)
|
||||||
include(KDEInstallDirs)
|
include(KDEInstallDirs)
|
||||||
@@ -19,22 +27,59 @@ include(ECMQMLModules)
|
|||||||
include(KDEClangFormat)
|
include(KDEClangFormat)
|
||||||
include(KDECMakeSettings)
|
include(KDECMakeSettings)
|
||||||
include(KDECompilerSettings NO_POLICY_SCOPE)
|
include(KDECompilerSettings NO_POLICY_SCOPE)
|
||||||
|
include(ECMAddAppIcon)
|
||||||
|
include(KDEGitCommitHooks)
|
||||||
|
include(ECMCheckOutboundLicense)
|
||||||
|
|
||||||
|
if(NEOCHAT_FLATPAK)
|
||||||
|
include(cmake/Flatpak.cmake)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Fix a crash due to problems with quotient's event system. Can probably be removed once the reworked event system is in
|
# Fix a crash due to problems with quotient's event system. Can probably be removed once the reworked event system is in
|
||||||
cmake_policy(SET CMP0063 OLD)
|
cmake_policy(SET CMP0063 OLD)
|
||||||
|
|
||||||
ecm_setup_version(0.1.0
|
ecm_setup_version(1.2.80
|
||||||
VARIABLE_PREFIX NEOCHAT
|
VARIABLE_PREFIX NEOCHAT
|
||||||
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
|
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Widgets Core Quick Gui QuickControls2 Multimedia Svg)
|
find_package(Qt5 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg)
|
||||||
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 I18n Notifications Config CoreAddons)
|
set_package_properties(Qt5 PROPERTIES
|
||||||
|
TYPE REQUIRED
|
||||||
|
PURPOSE "Basic application components"
|
||||||
|
)
|
||||||
|
find_package(KF5 ${KF5_MIN_VERSION} COMPONENTS Kirigami2 I18n Notifications Config CoreAddons)
|
||||||
|
set_package_properties(KF5 PROPERTIES
|
||||||
|
TYPE REQUIRED
|
||||||
|
PURPOSE "Basic application components"
|
||||||
|
)
|
||||||
|
set_package_properties(KF5Kirigami2 PROPERTIES
|
||||||
|
TYPE REQUIRED
|
||||||
|
PURPOSE "Kirigami application UI framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
find_package(Qt5Keychain)
|
||||||
|
set_package_properties(Qt5Keychain PROPERTIES
|
||||||
|
TYPE REQUIRED
|
||||||
|
PURPOSE "Secure storage of account secrets"
|
||||||
|
)
|
||||||
|
|
||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
find_package(OpenSSL REQUIRED)
|
find_package(OpenSSL)
|
||||||
|
set_package_properties(OpenSSL PROPERTIES
|
||||||
|
TYPE REQUIRED
|
||||||
|
PURPOSE "Encrypted communications"
|
||||||
|
)
|
||||||
else()
|
else()
|
||||||
find_package(Qt5Keychain REQUIRED)
|
find_package(Qt5 ${QT_MIN_VERSION} COMPONENTS Widgets)
|
||||||
|
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem Sonnet)
|
||||||
|
set_package_properties(KF5QQC2DesktopStyle PROPERTIES
|
||||||
|
TYPE RUNTIME
|
||||||
|
)
|
||||||
|
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||||
find_package(KF5DBusAddons ${KF5_MIN_VERSION} REQUIRED)
|
find_package(KF5DBusAddons ${KF5_MIN_VERSION} REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -55,20 +100,29 @@ set_package_properties(cmark PROPERTIES
|
|||||||
)
|
)
|
||||||
|
|
||||||
ecm_find_qmlmodule(org.kde.kquickimageeditor 1.0)
|
ecm_find_qmlmodule(org.kde.kquickimageeditor 1.0)
|
||||||
|
ecm_find_qmlmodule(org.kde.kitemmodels 1.0)
|
||||||
|
|
||||||
find_package(KQuickImageEditor COMPONENTS)
|
find_package(KQuickImageEditor COMPONENTS)
|
||||||
set_package_properties(KQuickImageEditor PROPERTIES
|
set_package_properties(KQuickImageEditor PROPERTIES
|
||||||
|
TYPE REQUIRED
|
||||||
DESCRIPTION "Simple image editor for QtQuick applications"
|
DESCRIPTION "Simple image editor for QtQuick applications"
|
||||||
URL "https://invent.kde.org/libraries/kquickimageeditor/"
|
URL "https://invent.kde.org/libraries/kquickimageeditor/"
|
||||||
PURPOSE "Add image editing capability to image attachments"
|
PURPOSE "Add image editing capability to image attachments"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
find_package(QCoro REQUIRED)
|
||||||
|
|
||||||
|
qcoro_enable_coroutines()
|
||||||
|
|
||||||
install(FILES org.kde.neochat.desktop DESTINATION ${KDE_INSTALL_APPDIR})
|
install(FILES org.kde.neochat.desktop DESTINATION ${KDE_INSTALL_APPDIR})
|
||||||
install(FILES org.kde.neochat.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
|
install(FILES org.kde.neochat.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
|
||||||
install(FILES neochat.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps)
|
install(FILES org.kde.neochat.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps)
|
||||||
|
install(FILES org.kde.neochat-symbolic.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/16x16/apps RENAME org.kde.neochat.svg)
|
||||||
|
install(FILES org.kde.neochat-symbolic.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/16x16@2/apps RENAME org.kde.neochat.svg)
|
||||||
|
install(FILES org.kde.neochat-symbolic.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/16x16@3/apps RENAME org.kde.neochat.svg)
|
||||||
|
install(FILES org.kde.neochat-symbolic.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/symbolic/apps)
|
||||||
install(FILES neochat.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR})
|
install(FILES neochat.notifyrc DESTINATION ${KNOTIFYRC_INSTALL_DIR})
|
||||||
|
|
||||||
# add_definitions(-DQT_NO_KEYWORDS) Need to fix libQuotient first
|
|
||||||
add_definitions(-DQT_NO_FOREACH)
|
add_definitions(-DQT_NO_FOREACH)
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
@@ -77,3 +131,7 @@ feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAG
|
|||||||
|
|
||||||
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h)
|
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h)
|
||||||
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
|
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
|
||||||
|
|
||||||
|
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
|
||||||
|
file(GLOB_RECURSE ALL_SOURCE_FILES *.cpp *.h *.qml)
|
||||||
|
ecm_check_outbound_license(LICENSES GPL-3.0-only FILES ${ALL_SOURCE_FILES})
|
||||||
|
|||||||
119
LICENSES/CC0-1.0.txt
Normal file
119
LICENSES/CC0-1.0.txt
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
Creative Commons Legal Code
|
||||||
|
|
||||||
|
CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES
|
||||||
|
NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE
|
||||||
|
AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION
|
||||||
|
ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE
|
||||||
|
OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS
|
||||||
|
LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION
|
||||||
|
OR WORKS PROVIDED HEREUNDER.
|
||||||
|
|
||||||
|
Statement of Purpose
|
||||||
|
|
||||||
|
The laws of most jurisdictions throughout the world automatically confer exclusive
|
||||||
|
Copyright and Related Rights (defined below) upon the creator and subsequent
|
||||||
|
owner(s) (each and all, an "owner") of an original work of authorship and/or
|
||||||
|
a database (each, a "Work").
|
||||||
|
|
||||||
|
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||||
|
purpose of contributing to a commons of creative, cultural and scientific
|
||||||
|
works ("Commons") that the public can reliably and without fear of later claims
|
||||||
|
of infringement build upon, modify, incorporate in other works, reuse and
|
||||||
|
redistribute as freely as possible in any form whatsoever and for any purposes,
|
||||||
|
including without limitation commercial purposes. These owners may contribute
|
||||||
|
to the Commons to promote the ideal of a free culture and the further production
|
||||||
|
of creative, cultural and scientific works, or to gain reputation or greater
|
||||||
|
distribution for their Work in part through the use and efforts of others.
|
||||||
|
|
||||||
|
For these and/or other purposes and motivations, and without any expectation
|
||||||
|
of additional consideration or compensation, the person associating CC0 with
|
||||||
|
a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
|
||||||
|
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
|
||||||
|
and publicly distribute the Work under its terms, with knowledge of his or
|
||||||
|
her Copyright and Related Rights in the Work and the meaning and intended
|
||||||
|
legal effect of CC0 on those rights.
|
||||||
|
|
||||||
|
1. Copyright and Related Rights. A Work made available under CC0 may be protected
|
||||||
|
by copyright and related or neighboring rights ("Copyright and Related Rights").
|
||||||
|
Copyright and Related Rights include, but are not limited to, the following:
|
||||||
|
|
||||||
|
i. the right to reproduce, adapt, distribute, perform, display, communicate,
|
||||||
|
and translate a Work;
|
||||||
|
|
||||||
|
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||||
|
|
||||||
|
iii. publicity and privacy rights pertaining to a person's image or likeness
|
||||||
|
depicted in a Work;
|
||||||
|
|
||||||
|
iv. rights protecting against unfair competition in regards to a Work, subject
|
||||||
|
to the limitations in paragraph 4(a), below;
|
||||||
|
|
||||||
|
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||||
|
in a Work;
|
||||||
|
|
||||||
|
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||||
|
European Parliament and of the Council of 11 March 1996 on the legal protection
|
||||||
|
of databases, and under any national implementation thereof, including any
|
||||||
|
amended or successor version of such directive); and
|
||||||
|
|
||||||
|
vii. other similar, equivalent or corresponding rights throughout the world
|
||||||
|
based on applicable law or treaty, and any national implementations thereof.
|
||||||
|
|
||||||
|
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||||
|
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||||
|
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
|
||||||
|
and Related Rights and associated claims and causes of action, whether now
|
||||||
|
known or unknown (including existing as well as future claims and causes of
|
||||||
|
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||||
|
duration provided by applicable law or treaty (including future time extensions),
|
||||||
|
(iii) in any current or future medium and for any number of copies, and (iv)
|
||||||
|
for any purpose whatsoever, including without limitation commercial, advertising
|
||||||
|
or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the
|
||||||
|
benefit of each member of the public at large and to the detriment of Affirmer's
|
||||||
|
heirs and successors, fully intending that such Waiver shall not be subject
|
||||||
|
to revocation, rescission, cancellation, termination, or any other legal or
|
||||||
|
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||||
|
as contemplated by Affirmer's express Statement of Purpose.
|
||||||
|
|
||||||
|
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||||
|
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||||
|
shall be preserved to the maximum extent permitted taking into account Affirmer's
|
||||||
|
express Statement of Purpose. In addition, to the extent the Waiver is so
|
||||||
|
judged Affirmer hereby grants to each affected person a royalty-free, non
|
||||||
|
transferable, non sublicensable, non exclusive, irrevocable and unconditional
|
||||||
|
license to exercise Affirmer's Copyright and Related Rights in the Work (i)
|
||||||
|
in all territories worldwide, (ii) for the maximum duration provided by applicable
|
||||||
|
law or treaty (including future time extensions), (iii) in any current or
|
||||||
|
future medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||||
|
including without limitation commercial, advertising or promotional purposes
|
||||||
|
(the "License"). The License shall be deemed effective as of the date CC0
|
||||||
|
was applied by Affirmer to the Work. Should any part of the License for any
|
||||||
|
reason be judged legally invalid or ineffective under applicable law, such
|
||||||
|
partial invalidity or ineffectiveness shall not invalidate the remainder of
|
||||||
|
the License, and in such case Affirmer hereby affirms that he or she will
|
||||||
|
not (i) exercise any of his or her remaining Copyright and Related Rights
|
||||||
|
in the Work or (ii) assert any associated claims and causes of action with
|
||||||
|
respect to the Work, in either case contrary to Affirmer's express Statement
|
||||||
|
of Purpose.
|
||||||
|
|
||||||
|
4. Limitations and Disclaimers.
|
||||||
|
|
||||||
|
a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered,
|
||||||
|
licensed or otherwise affected by this document.
|
||||||
|
|
||||||
|
b. Affirmer offers the Work as-is and makes no representations or warranties
|
||||||
|
of any kind concerning the Work, express, implied, statutory or otherwise,
|
||||||
|
including without limitation warranties of title, merchantability, fitness
|
||||||
|
for a particular purpose, non infringement, or the absence of latent or other
|
||||||
|
defects, accuracy, or the present or absence of errors, whether or not discoverable,
|
||||||
|
all to the greatest extent permissible under applicable law.
|
||||||
|
|
||||||
|
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||||
|
that may apply to the Work or any use thereof, including without limitation
|
||||||
|
any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims
|
||||||
|
responsibility for obtaining any necessary consents, permissions or other
|
||||||
|
rights required for any use of the Work.
|
||||||
|
|
||||||
|
d. Affirmer understands and acknowledges that Creative Commons is not a party
|
||||||
|
to this document and has no duty or obligation with respect to this CC0 or
|
||||||
|
use of the Work.
|
||||||
319
LICENSES/GPL-2.0-only.txt
Normal file
319
LICENSES/GPL-2.0-only.txt
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
|
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||||
|
document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your freedom to share
|
||||||
|
and change it. By contrast, the GNU General Public License is intended to
|
||||||
|
guarantee your freedom to share and change free software--to make sure the
|
||||||
|
software is free for all its users. This General Public License applies to
|
||||||
|
most of the Free Software Foundation's software and to any other program whose
|
||||||
|
authors commit to using it. (Some other Free Software Foundation software
|
||||||
|
is covered by the GNU Lesser General Public License instead.) You can apply
|
||||||
|
it to your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not price. Our
|
||||||
|
General Public Licenses are designed to make sure that you have the freedom
|
||||||
|
to distribute copies of free software (and charge for this service if you
|
||||||
|
wish), that you receive source code or can get it if you want it, that you
|
||||||
|
can change the software or use pieces of it in new free programs; and that
|
||||||
|
you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid anyone to
|
||||||
|
deny you these rights or to ask you to surrender the rights. These restrictions
|
||||||
|
translate to certain responsibilities for you if you distribute copies of
|
||||||
|
the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether gratis or
|
||||||
|
for a fee, you must give the recipients all the rights that you have. You
|
||||||
|
must make sure that they, too, receive or can get the source code. And you
|
||||||
|
must show them these terms so they know their rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and (2)
|
||||||
|
offer you this license which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain that
|
||||||
|
everyone understands that there is no warranty for this free software. If
|
||||||
|
the software is modified by someone else and passed on, we want its recipients
|
||||||
|
to know that what they have is not the original, so that any problems introduced
|
||||||
|
by others will not reflect on the original authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software patents. We
|
||||||
|
wish to avoid the danger that redistributors of a free program will individually
|
||||||
|
obtain patent licenses, in effect making the program proprietary. To prevent
|
||||||
|
this, we have made it clear that any patent must be licensed for everyone's
|
||||||
|
free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and modification
|
||||||
|
follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains a notice
|
||||||
|
placed by the copyright holder saying it may be distributed under the terms
|
||||||
|
of this General Public License. The "Program", below, refers to any such program
|
||||||
|
or work, and a "work based on the Program" means either the Program or any
|
||||||
|
derivative work under copyright law: that is to say, a work containing the
|
||||||
|
Program or a portion of it, either verbatim or with modifications and/or translated
|
||||||
|
into another language. (Hereinafter, translation is included without limitation
|
||||||
|
in the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not covered
|
||||||
|
by this License; they are outside its scope. The act of running the Program
|
||||||
|
is not restricted, and the output from the Program is covered only if its
|
||||||
|
contents constitute a work based on the Program (independent of having been
|
||||||
|
made by running the Program). Whether that is true depends on what the Program
|
||||||
|
does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's source code
|
||||||
|
as you receive it, in any medium, provided that you conspicuously and appropriately
|
||||||
|
publish on each copy an appropriate copyright notice and disclaimer of warranty;
|
||||||
|
keep intact all the notices that refer to this License and to the absence
|
||||||
|
of any warranty; and give any other recipients of the Program a copy of this
|
||||||
|
License along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and you
|
||||||
|
may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion of it,
|
||||||
|
thus forming a work based on the Program, and copy and distribute such modifications
|
||||||
|
or work under the terms of Section 1 above, provided that you also meet all
|
||||||
|
of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices stating that
|
||||||
|
you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in whole or
|
||||||
|
in part contains or is derived from the Program or any part thereof, to be
|
||||||
|
licensed as a whole at no charge to all third parties under the terms of this
|
||||||
|
License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively when run,
|
||||||
|
you must cause it, when started running for such interactive use in the most
|
||||||
|
ordinary way, to print or display an announcement including an appropriate
|
||||||
|
copyright notice and a notice that there is no warranty (or else, saying that
|
||||||
|
you provide a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this License.
|
||||||
|
(Exception: if the Program itself is interactive but does not normally print
|
||||||
|
such an announcement, your work based on the Program is not required to print
|
||||||
|
an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If identifiable
|
||||||
|
sections of that work are not derived from the Program, and can be reasonably
|
||||||
|
considered independent and separate works in themselves, then this License,
|
||||||
|
and its terms, do not apply to those sections when you distribute them as
|
||||||
|
separate works. But when you distribute the same sections as part of a whole
|
||||||
|
which is a work based on the Program, the distribution of the whole must be
|
||||||
|
on the terms of this License, whose permissions for other licensees extend
|
||||||
|
to the entire whole, and thus to each and every part regardless of who wrote
|
||||||
|
it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest your
|
||||||
|
rights to work written entirely by you; rather, the intent is to exercise
|
||||||
|
the right to control the distribution of derivative or collective works based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program with
|
||||||
|
the Program (or with a work based on the Program) on a volume of a storage
|
||||||
|
or distribution medium does not bring the other work under the scope of this
|
||||||
|
License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it, under Section
|
||||||
|
2) in object code or executable form under the terms of Sections 1 and 2 above
|
||||||
|
provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable source code,
|
||||||
|
which must be distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three years, to give
|
||||||
|
any third party, for a charge no more than your cost of physically performing
|
||||||
|
source distribution, a complete machine-readable copy of the corresponding
|
||||||
|
source code, to be distributed under the terms of Sections 1 and 2 above on
|
||||||
|
a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer to distribute
|
||||||
|
corresponding source code. (This alternative is allowed only for noncommercial
|
||||||
|
distribution and only if you received the program in object code or executable
|
||||||
|
form with such an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for making
|
||||||
|
modifications to it. For an executable work, complete source code means all
|
||||||
|
the source code for all modules it contains, plus any associated interface
|
||||||
|
definition files, plus the scripts used to control compilation and installation
|
||||||
|
of the executable. However, as a special exception, the source code distributed
|
||||||
|
need not include anything that is normally distributed (in either source or
|
||||||
|
binary form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component itself
|
||||||
|
accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering access to
|
||||||
|
copy from a designated place, then offering equivalent access to copy the
|
||||||
|
source code from the same place counts as distribution of the source code,
|
||||||
|
even though third parties are not compelled to copy the source along with
|
||||||
|
the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program except
|
||||||
|
as expressly provided under this License. Any attempt otherwise to copy, modify,
|
||||||
|
sublicense or distribute the Program is void, and will automatically terminate
|
||||||
|
your rights under this License. However, parties who have received copies,
|
||||||
|
or rights, from you under this License will not have their licenses terminated
|
||||||
|
so long as such parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not signed
|
||||||
|
it. However, nothing else grants you permission to modify or distribute the
|
||||||
|
Program or its derivative works. These actions are prohibited by law if you
|
||||||
|
do not accept this License. Therefore, by modifying or distributing the Program
|
||||||
|
(or any work based on the Program), you indicate your acceptance of this License
|
||||||
|
to do so, and all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the Program),
|
||||||
|
the recipient automatically receives a license from the original licensor
|
||||||
|
to copy, distribute or modify the Program subject to these terms and conditions.
|
||||||
|
You may not impose any further restrictions on the recipients' exercise of
|
||||||
|
the rights granted herein. You are not responsible for enforcing compliance
|
||||||
|
by third parties to this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent infringement
|
||||||
|
or for any other reason (not limited to patent issues), conditions are imposed
|
||||||
|
on you (whether by court order, agreement or otherwise) that contradict the
|
||||||
|
conditions of this License, they do not excuse you from the conditions of
|
||||||
|
this License. If you cannot distribute so as to satisfy simultaneously your
|
||||||
|
obligations under this License and any other pertinent obligations, then as
|
||||||
|
a consequence you may not distribute the Program at all. For example, if a
|
||||||
|
patent license would not permit royalty-free redistribution of the Program
|
||||||
|
by all those who receive copies directly or indirectly through you, then the
|
||||||
|
only way you could satisfy both it and this License would be to refrain entirely
|
||||||
|
from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under any
|
||||||
|
particular circumstance, the balance of the section is intended to apply and
|
||||||
|
the section as a whole is intended to apply in other circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any patents
|
||||||
|
or other property right claims or to contest validity of any such claims;
|
||||||
|
this section has the sole purpose of protecting the integrity of the free
|
||||||
|
software distribution system, which is implemented by public license practices.
|
||||||
|
Many people have made generous contributions to the wide range of software
|
||||||
|
distributed through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing to
|
||||||
|
distribute software through any other system and a licensee cannot impose
|
||||||
|
that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to be a
|
||||||
|
consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in certain
|
||||||
|
countries either by patents or by copyrighted interfaces, the original copyright
|
||||||
|
holder who places the Program under this License may add an explicit geographical
|
||||||
|
distribution limitation excluding those countries, so that distribution is
|
||||||
|
permitted only in or among countries not thus excluded. In such case, this
|
||||||
|
License incorporates the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the General Public License from time to time. Such new versions will be similar
|
||||||
|
in spirit to the present version, but may differ in detail to address new
|
||||||
|
problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program specifies
|
||||||
|
a version number of this License which applies to it and "any later version",
|
||||||
|
you have the option of following the terms and conditions either of that version
|
||||||
|
or of any later version published by the Free Software Foundation. If the
|
||||||
|
Program does not specify a version number of this License, you may choose
|
||||||
|
any version ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free programs
|
||||||
|
whose distribution conditions are different, write to the author to ask for
|
||||||
|
permission. For software which is copyrighted by the Free Software Foundation,
|
||||||
|
write to the Free Software Foundation; we sometimes make exceptions for this.
|
||||||
|
Our decision will be guided by the two goals of preserving the free status
|
||||||
|
of all derivatives of our free software and of promoting the sharing and reuse
|
||||||
|
of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
|
||||||
|
THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
|
||||||
|
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
|
||||||
|
"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
|
||||||
|
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
|
||||||
|
OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
|
||||||
|
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
|
||||||
|
OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
|
||||||
|
OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
|
||||||
|
OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH
|
||||||
|
HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest possible
|
||||||
|
use to the public, the best way to achieve this is to make it free software
|
||||||
|
which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest to attach
|
||||||
|
them to the start of each source file to most effectively convey the exclusion
|
||||||
|
of warranty; and each file should have at least the "copyright" line and a
|
||||||
|
pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and an idea of what it does.>
|
||||||
|
|
||||||
|
Copyright (C)< yyyy> <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
|
||||||
|
Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this when
|
||||||
|
it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author Gnomovision comes
|
||||||
|
with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software,
|
||||||
|
and you are welcome to redistribute it under certain conditions; type `show
|
||||||
|
c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may be
|
||||||
|
called something other than `show w' and `show c'; they could even be mouse-clicks
|
||||||
|
or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary. Here
|
||||||
|
is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision'
|
||||||
|
(which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon >, 1 April 1989 Ty Coon, President of Vice This General
|
||||||
|
Public License does not permit incorporating your program into proprietary
|
||||||
|
programs. If your program is a subroutine library, you may consider it more
|
||||||
|
useful to permit linking proprietary applications with the library. If this
|
||||||
|
is what you want to do, use the GNU Lesser General Public License instead
|
||||||
|
of this License.
|
||||||
446
LICENSES/LGPL-2.0-or-later.txt
Normal file
446
LICENSES/LGPL-2.0-or-later.txt
Normal file
@@ -0,0 +1,446 @@
|
|||||||
|
GNU LIBRARY GENERAL PUBLIC LICENSE
|
||||||
|
|
||||||
|
Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc.
|
||||||
|
|
||||||
|
51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||||
|
document, but changing it is not allowed.
|
||||||
|
|
||||||
|
[This is the first released version of the library GPL. It is numbered 2 because
|
||||||
|
it goes with version 2 of the ordinary GPL.]
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your freedom to share
|
||||||
|
and change it. By contrast, the GNU General Public Licenses are intended to
|
||||||
|
guarantee your freedom to share and change free software--to make sure the
|
||||||
|
software is free for all its users.
|
||||||
|
|
||||||
|
This license, the Library General Public License, applies to some specially
|
||||||
|
designated Free Software Foundation software, and to any other libraries whose
|
||||||
|
authors decide to use it. You can use it for your libraries, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not price. Our
|
||||||
|
General Public Licenses are designed to make sure that you have the freedom
|
||||||
|
to distribute copies of free software (and charge for this service if you
|
||||||
|
wish), that you receive source code or can get it if you want it, that you
|
||||||
|
can change the software or use pieces of it in new free programs; and that
|
||||||
|
you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid anyone to
|
||||||
|
deny you these rights or to ask you to surrender the rights. These restrictions
|
||||||
|
translate to certain responsibilities for you if you distribute copies of
|
||||||
|
the library, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of the library, whether gratis or for
|
||||||
|
a fee, you must give the recipients all the rights that we gave you. You must
|
||||||
|
make sure that they, too, receive or can get the source code. If you link
|
||||||
|
a program with the library, you must provide complete object files to the
|
||||||
|
recipients so that they can relink them with the library, after making changes
|
||||||
|
to the library and recompiling it. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Our method of protecting your rights has two steps: (1) copyright the library,
|
||||||
|
and (2) offer you this license which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the library.
|
||||||
|
|
||||||
|
Also, for each distributor's protection, we want to make certain that everyone
|
||||||
|
understands that there is no warranty for this free library. If the library
|
||||||
|
is modified by someone else and passed on, we want its recipients to know
|
||||||
|
that what they have is not the original version, so that any problems introduced
|
||||||
|
by others will not reflect on the original authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software patents. We
|
||||||
|
wish to avoid the danger that companies distributing free software will individually
|
||||||
|
obtain patent licenses, thus in effect transforming the program into proprietary
|
||||||
|
software. To prevent this, we have made it clear that any patent must be licensed
|
||||||
|
for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
Most GNU software, including some libraries, is covered by the ordinary GNU
|
||||||
|
General Public License, which was designed for utility programs. This license,
|
||||||
|
the GNU Library General Public License, applies to certain designated libraries.
|
||||||
|
This license is quite different from the ordinary one; be sure to read it
|
||||||
|
in full, and don't assume that anything in it is the same as in the ordinary
|
||||||
|
license.
|
||||||
|
|
||||||
|
The reason we have a separate public license for some libraries is that they
|
||||||
|
blur the distinction we usually make between modifying or adding to a program
|
||||||
|
and simply using it. Linking a program with a library, without changing the
|
||||||
|
library, is in some sense simply using the library, and is analogous to running
|
||||||
|
a utility program or application program. However, in a textual and legal
|
||||||
|
sense, the linked executable is a combined work, a derivative of the original
|
||||||
|
library, and the ordinary General Public License treats it as such.
|
||||||
|
|
||||||
|
Because of this blurred distinction, using the ordinary General Public License
|
||||||
|
for libraries did not effectively promote software sharing, because most developers
|
||||||
|
did not use the libraries. We concluded that weaker conditions might promote
|
||||||
|
sharing better.
|
||||||
|
|
||||||
|
However, unrestricted linking of non-free programs would deprive the users
|
||||||
|
of those programs of all benefit from the free status of the libraries themselves.
|
||||||
|
This Library General Public License is intended to permit developers of non-free
|
||||||
|
programs to use free libraries, while preserving your freedom as a user of
|
||||||
|
such programs to change the free libraries that are incorporated in them.
|
||||||
|
(We have not seen how to achieve this as regards changes in header files,
|
||||||
|
but we have achieved it as regards changes in the actual functions of the
|
||||||
|
Library.) The hope is that this will lead to faster development of free libraries.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and modification
|
||||||
|
follow. Pay close attention to the difference between a "work based on the
|
||||||
|
library" and a "work that uses the library". The former contains code derived
|
||||||
|
from the library, while the latter only works together with the library.
|
||||||
|
|
||||||
|
Note that it is possible for a library to be covered by the ordinary General
|
||||||
|
Public License rather than by this special one.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License Agreement applies to any software library which contains a
|
||||||
|
notice placed by the copyright holder or other authorized party saying it
|
||||||
|
may be distributed under the terms of this Library General Public License
|
||||||
|
(also called "this License"). Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
A "library" means a collection of software functions and/or data prepared
|
||||||
|
so as to be conveniently linked with application programs (which use some
|
||||||
|
of those functions and data) to form executables.
|
||||||
|
|
||||||
|
The "Library", below, refers to any such software library or work which has
|
||||||
|
been distributed under these terms. A "work based on the Library" means either
|
||||||
|
the Library or any derivative work under copyright law: that is to say, a
|
||||||
|
work containing the Library or a portion of it, either verbatim or with modifications
|
||||||
|
and/or translated straightforwardly into another language. (Hereinafter, translation
|
||||||
|
is included without limitation in the term "modification".)
|
||||||
|
|
||||||
|
"Source code" for a work means the preferred form of the work for making modifications
|
||||||
|
to it. For a library, complete source code means all the source code for all
|
||||||
|
modules it contains, plus any associated interface definition files, plus
|
||||||
|
the scripts used to control compilation and installation of the library.
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not covered
|
||||||
|
by this License; they are outside its scope. The act of running a program
|
||||||
|
using the Library is not restricted, and output from such a program is covered
|
||||||
|
only if its contents constitute a work based on the Library (independent of
|
||||||
|
the use of the Library in a tool for writing it). Whether that is true depends
|
||||||
|
on what the Library does and what the program that uses the Library does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Library's complete source
|
||||||
|
code as you receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice and disclaimer
|
||||||
|
of warranty; keep intact all the notices that refer to this License and to
|
||||||
|
the absence of any warranty; and distribute a copy of this License along with
|
||||||
|
the Library.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and you
|
||||||
|
may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Library or any portion of it,
|
||||||
|
thus forming a work based on the Library, and copy and distribute such modifications
|
||||||
|
or work under the terms of Section 1 above, provided that you also meet all
|
||||||
|
of these conditions:
|
||||||
|
|
||||||
|
a) The modified work must itself be a software library.
|
||||||
|
|
||||||
|
b) You must cause the files modified to carry prominent notices stating that
|
||||||
|
you changed the files and the date of any change.
|
||||||
|
|
||||||
|
c) You must cause the whole of the work to be licensed at no charge to all
|
||||||
|
third parties under the terms of this License.
|
||||||
|
|
||||||
|
d) If a facility in the modified Library refers to a function or a table of
|
||||||
|
data to be supplied by an application program that uses the facility, other
|
||||||
|
than as an argument passed when the facility is invoked, then you must make
|
||||||
|
a good faith effort to ensure that, in the event an application does not supply
|
||||||
|
such function or table, the facility still operates, and performs whatever
|
||||||
|
part of its purpose remains meaningful.
|
||||||
|
|
||||||
|
(For example, a function in a library to compute square roots has a purpose
|
||||||
|
that is entirely well-defined independent of the application. Therefore, Subsection
|
||||||
|
2d requires that any application-supplied function or table used by this function
|
||||||
|
must be optional: if the application does not supply it, the square root function
|
||||||
|
must still compute square roots.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If identifiable
|
||||||
|
sections of that work are not derived from the Library, and can be reasonably
|
||||||
|
considered independent and separate works in themselves, then this License,
|
||||||
|
and its terms, do not apply to those sections when you distribute them as
|
||||||
|
separate works. But when you distribute the same sections as part of a whole
|
||||||
|
which is a work based on the Library, the distribution of the whole must be
|
||||||
|
on the terms of this License, whose permissions for other licensees extend
|
||||||
|
to the entire whole, and thus to each and every part regardless of who wrote
|
||||||
|
it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest your
|
||||||
|
rights to work written entirely by you; rather, the intent is to exercise
|
||||||
|
the right to control the distribution of derivative or collective works based
|
||||||
|
on the Library.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Library with
|
||||||
|
the Library (or with a work based on the Library) on a volume of a storage
|
||||||
|
or distribution medium does not bring the other work under the scope of this
|
||||||
|
License.
|
||||||
|
|
||||||
|
3. You may opt to apply the terms of the ordinary GNU General Public License
|
||||||
|
instead of this License to a given copy of the Library. To do this, you must
|
||||||
|
alter all the notices that refer to this License, so that they refer to the
|
||||||
|
ordinary GNU General Public License, version 2, instead of to this License.
|
||||||
|
(If a newer version than version 2 of the ordinary GNU General Public License
|
||||||
|
has appeared, then you can specify that version instead if you wish.) Do not
|
||||||
|
make any other change in these notices.
|
||||||
|
|
||||||
|
Once this change is made in a given copy, it is irreversible for that copy,
|
||||||
|
so the ordinary GNU General Public License applies to all subsequent copies
|
||||||
|
and derivative works made from that copy.
|
||||||
|
|
||||||
|
This option is useful when you wish to copy part of the code of the Library
|
||||||
|
into a program that is not a library.
|
||||||
|
|
||||||
|
4. You may copy and distribute the Library (or a portion or derivative of
|
||||||
|
it, under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you accompany it with the complete corresponding
|
||||||
|
machine-readable source code, which must be distributed under the terms of
|
||||||
|
Sections 1 and 2 above on a medium customarily used for software interchange.
|
||||||
|
|
||||||
|
If distribution of object code is made by offering access to copy from a designated
|
||||||
|
place, then offering equivalent access to copy the source code from the same
|
||||||
|
place satisfies the requirement to distribute the source code, even though
|
||||||
|
third parties are not compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
5. A program that contains no derivative of any portion of the Library, but
|
||||||
|
is designed to work with the Library by being compiled or linked with it,
|
||||||
|
is called a "work that uses the Library". Such a work, in isolation, is not
|
||||||
|
a derivative work of the Library, and therefore falls outside the scope of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
However, linking a "work that uses the Library" with the Library creates an
|
||||||
|
executable that is a derivative of the Library (because it contains portions
|
||||||
|
of the Library), rather than a "work that uses the library". The executable
|
||||||
|
is therefore covered by this License. Section 6 states terms for distribution
|
||||||
|
of such executables.
|
||||||
|
|
||||||
|
When a "work that uses the Library" uses material from a header file that
|
||||||
|
is part of the Library, the object code for the work may be a derivative work
|
||||||
|
of the Library even though the source code is not. Whether this is true is
|
||||||
|
especially significant if the work can be linked without the Library, or if
|
||||||
|
the work is itself a library. The threshold for this to be true is not precisely
|
||||||
|
defined by law.
|
||||||
|
|
||||||
|
If such an object file uses only numerical parameters, data structure layouts
|
||||||
|
and accessors, and small macros and small inline functions (ten lines or less
|
||||||
|
in length), then the use of the object file is unrestricted, regardless of
|
||||||
|
whether it is legally a derivative work. (Executables containing this object
|
||||||
|
code plus portions of the Library will still fall under Section 6.)
|
||||||
|
|
||||||
|
Otherwise, if the work is a derivative of the Library, you may distribute
|
||||||
|
the object code for the work under the terms of Section 6. Any executables
|
||||||
|
containing that work also fall under Section 6, whether or not they are linked
|
||||||
|
directly with the Library itself.
|
||||||
|
|
||||||
|
6. As an exception to the Sections above, you may also compile or link a "work
|
||||||
|
that uses the Library" with the Library to produce a work containing portions
|
||||||
|
of the Library, and distribute that work under terms of your choice, provided
|
||||||
|
that the terms permit modification of the work for the customer's own use
|
||||||
|
and reverse engineering for debugging such modifications.
|
||||||
|
|
||||||
|
You must give prominent notice with each copy of the work that the Library
|
||||||
|
is used in it and that the Library and its use are covered by this License.
|
||||||
|
You must supply a copy of this License. If the work during execution displays
|
||||||
|
copyright notices, you must include the copyright notice for the Library among
|
||||||
|
them, as well as a reference directing the user to the copy of this License.
|
||||||
|
Also, you must do one of these things:
|
||||||
|
|
||||||
|
a) Accompany the work with the complete corresponding machine-readable source
|
||||||
|
code for the Library including whatever changes were used in the work (which
|
||||||
|
must be distributed under Sections 1 and 2 above); and, if the work is an
|
||||||
|
executable linked with the Library, with the complete machine-readable "work
|
||||||
|
that uses the Library", as object code and/or source code, so that the user
|
||||||
|
can modify the Library and then relink to produce a modified executable containing
|
||||||
|
the modified Library. (It is understood that the user who changes the contents
|
||||||
|
of definitions files in the Library will not necessarily be able to recompile
|
||||||
|
the application to use the modified definitions.)
|
||||||
|
|
||||||
|
b) Accompany the work with a written offer, valid for at least three years,
|
||||||
|
to give the same user the materials specified in Subsection 6a, above, for
|
||||||
|
a charge no more than the cost of performing this distribution.
|
||||||
|
|
||||||
|
c) If distribution of the work is made by offering access to copy from a designated
|
||||||
|
place, offer equivalent access to copy the above specified materials from
|
||||||
|
the same place.
|
||||||
|
|
||||||
|
d) Verify that the user has already received a copy of these materials or
|
||||||
|
that you have already sent this user a copy.
|
||||||
|
|
||||||
|
For an executable, the required form of the "work that uses the Library" must
|
||||||
|
include any data and utility programs needed for reproducing the executable
|
||||||
|
from it. However, as a special exception, the source code distributed need
|
||||||
|
not include anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the operating
|
||||||
|
system on which the executable runs, unless that component itself accompanies
|
||||||
|
the executable.
|
||||||
|
|
||||||
|
It may happen that this requirement contradicts the license restrictions of
|
||||||
|
other proprietary libraries that do not normally accompany the operating system.
|
||||||
|
Such a contradiction means you cannot use both them and the Library together
|
||||||
|
in an executable that you distribute.
|
||||||
|
|
||||||
|
7. You may place library facilities that are a work based on the Library side-by-side
|
||||||
|
in a single library together with other library facilities not covered by
|
||||||
|
this License, and distribute such a combined library, provided that the separate
|
||||||
|
distribution of the work based on the Library and of the other library facilities
|
||||||
|
is otherwise permitted, and provided that you do these two things:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based on the
|
||||||
|
Library, uncombined with any other library facilities. This must be distributed
|
||||||
|
under the terms of the Sections above.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library of the fact that part of
|
||||||
|
it is a work based on the Library, and explaining where to find the accompanying
|
||||||
|
uncombined form of the same work.
|
||||||
|
|
||||||
|
8. You may not copy, modify, sublicense, link with, or distribute the Library
|
||||||
|
except as expressly provided under this License. Any attempt otherwise to
|
||||||
|
copy, modify, sublicense, link with, or distribute the Library is void, and
|
||||||
|
will automatically terminate your rights under this License. However, parties
|
||||||
|
who have received copies, or rights, from you under this License will not
|
||||||
|
have their licenses terminated so long as such parties remain in full compliance.
|
||||||
|
|
||||||
|
9. You are not required to accept this License, since you have not signed
|
||||||
|
it. However, nothing else grants you permission to modify or distribute the
|
||||||
|
Library or its derivative works. These actions are prohibited by law if you
|
||||||
|
do not accept this License. Therefore, by modifying or distributing the Library
|
||||||
|
(or any work based on the Library), you indicate your acceptance of this License
|
||||||
|
to do so, and all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Library or works based on it.
|
||||||
|
|
||||||
|
10. Each time you redistribute the Library (or any work based on the Library),
|
||||||
|
the recipient automatically receives a license from the original licensor
|
||||||
|
to copy, distribute, link with or modify the Library subject to these terms
|
||||||
|
and conditions. You may not impose any further restrictions on the recipients'
|
||||||
|
exercise of the rights granted herein. You are not responsible for enforcing
|
||||||
|
compliance by third parties to this License.
|
||||||
|
|
||||||
|
11. If, as a consequence of a court judgment or allegation of patent infringement
|
||||||
|
or for any other reason (not limited to patent issues), conditions are imposed
|
||||||
|
on you (whether by court order, agreement or otherwise) that contradict the
|
||||||
|
conditions of this License, they do not excuse you from the conditions of
|
||||||
|
this License. If you cannot distribute so as to satisfy simultaneously your
|
||||||
|
obligations under this License and any other pertinent obligations, then as
|
||||||
|
a consequence you may not distribute the Library at all. For example, if a
|
||||||
|
patent license would not permit royalty-free redistribution of the Library
|
||||||
|
by all those who receive copies directly or indirectly through you, then the
|
||||||
|
only way you could satisfy both it and this License would be to refrain entirely
|
||||||
|
from distribution of the Library.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under any
|
||||||
|
particular circumstance, the balance of the section is intended to apply,
|
||||||
|
and the section as a whole is intended to apply in other circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any patents
|
||||||
|
or other property right claims or to contest validity of any such claims;
|
||||||
|
this section has the sole purpose of protecting the integrity of the free
|
||||||
|
software distribution system which is implemented by public license practices.
|
||||||
|
Many people have made generous contributions to the wide range of software
|
||||||
|
distributed through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing to
|
||||||
|
distribute software through any other system and a licensee cannot impose
|
||||||
|
that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to be a
|
||||||
|
consequence of the rest of this License.
|
||||||
|
|
||||||
|
12. If the distribution and/or use of the Library is restricted in certain
|
||||||
|
countries either by patents or by copyrighted interfaces, the original copyright
|
||||||
|
holder who places the Library under this License may add an explicit geographical
|
||||||
|
distribution limitation excluding those countries, so that distribution is
|
||||||
|
permitted only in or among countries not thus excluded. In such case, this
|
||||||
|
License incorporates the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
13. The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the Library General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to address
|
||||||
|
new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Library specifies
|
||||||
|
a version number of this License which applies to it and "any later version",
|
||||||
|
you have the option of following the terms and conditions either of that version
|
||||||
|
or of any later version published by the Free Software Foundation. If the
|
||||||
|
Library does not specify a license version number, you may choose any version
|
||||||
|
ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
14. If you wish to incorporate parts of the Library into other free programs
|
||||||
|
whose distribution conditions are incompatible with these, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free Software
|
||||||
|
Foundation, write to the Free Software Foundation; we sometimes make exceptions
|
||||||
|
for this. Our decision will be guided by the two goals of preserving the free
|
||||||
|
status of all derivatives of our free software and of promoting the sharing
|
||||||
|
and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
|
||||||
|
THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
|
||||||
|
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY
|
||||||
|
"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
|
||||||
|
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
|
||||||
|
OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||||
|
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
|
||||||
|
THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
|
||||||
|
OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
|
||||||
|
OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
|
||||||
|
OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH
|
||||||
|
HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Libraries
|
||||||
|
|
||||||
|
If you develop a new library, and you want it to be of the greatest possible
|
||||||
|
use to the public, we recommend making it free software that everyone can
|
||||||
|
redistribute and change. You can do so by permitting redistribution under
|
||||||
|
these terms (or, alternatively, under the terms of the ordinary General Public
|
||||||
|
License).
|
||||||
|
|
||||||
|
To apply these terms, attach the following notices to the library. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively convey
|
||||||
|
the exclusion of warranty; and each file should have at least the "copyright"
|
||||||
|
line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
one line to give the library's name and an idea of what it does.
|
||||||
|
|
||||||
|
Copyright (C) year name of author
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU Library General Public License as published by the Free
|
||||||
|
Software Foundation; either version 2 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Library General Public License
|
||||||
|
along with this library; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the library, if necessary. Here
|
||||||
|
is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in
|
||||||
|
|
||||||
|
the library `Frob' (a library for tweaking knobs) written
|
||||||
|
|
||||||
|
by James Random Hacker.
|
||||||
|
|
||||||
|
signature of Ty Coon, 1 April 1990
|
||||||
|
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
That's all there is to it!
|
||||||
468
LICENSES/LGPL-2.1-or-later.txt
Normal file
468
LICENSES/LGPL-2.1-or-later.txt
Normal file
@@ -0,0 +1,468 @@
|
|||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
|
||||||
|
Version 2.1, February 1999
|
||||||
|
|
||||||
|
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||||
|
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||||
|
document, but changing it is not allowed.
|
||||||
|
|
||||||
|
[This is the first released version of the Lesser GPL. It also counts as the
|
||||||
|
successor of the GNU Library Public License, version 2, hence the version
|
||||||
|
number 2.1.]
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your freedom to share
|
||||||
|
and change it. By contrast, the GNU General Public Licenses are intended to
|
||||||
|
guarantee your freedom to share and change free software--to make sure the
|
||||||
|
software is free for all its users.
|
||||||
|
|
||||||
|
This license, the Lesser General Public License, applies to some specially
|
||||||
|
designated software packages--typically libraries--of the Free Software Foundation
|
||||||
|
and other authors who decide to use it. You can use it too, but we suggest
|
||||||
|
you first think carefully about whether this license or the ordinary General
|
||||||
|
Public License is the better strategy to use in any particular case, based
|
||||||
|
on the explanations below.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom of use, not price.
|
||||||
|
Our General Public Licenses are designed to make sure that you have the freedom
|
||||||
|
to distribute copies of free software (and charge for this service if you
|
||||||
|
wish); that you receive source code or can get it if you want it; that you
|
||||||
|
can change the software and use pieces of it in new free programs; and that
|
||||||
|
you are informed that you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid distributors
|
||||||
|
to deny you these rights or to ask you to surrender these rights. These restrictions
|
||||||
|
translate to certain responsibilities for you if you distribute copies of
|
||||||
|
the library or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of the library, whether gratis or for
|
||||||
|
a fee, you must give the recipients all the rights that we gave you. You must
|
||||||
|
make sure that they, too, receive or can get the source code. If you link
|
||||||
|
other code with the library, you must provide complete object files to the
|
||||||
|
recipients, so that they can relink them with the library after making changes
|
||||||
|
to the library and recompiling it. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
We protect your rights with a two-step method: (1) we copyright the library,
|
||||||
|
and (2) we offer you this license, which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the library.
|
||||||
|
|
||||||
|
To protect each distributor, we want to make it very clear that there is no
|
||||||
|
warranty for the free library. Also, if the library is modified by someone
|
||||||
|
else and passed on, the recipients should know that what they have is not
|
||||||
|
the original version, so that the original author's reputation will not be
|
||||||
|
affected by problems that might be introduced by others.
|
||||||
|
|
||||||
|
Finally, software patents pose a constant threat to the existence of any free
|
||||||
|
program. We wish to make sure that a company cannot effectively restrict the
|
||||||
|
users of a free program by obtaining a restrictive license from a patent holder.
|
||||||
|
Therefore, we insist that any patent license obtained for a version of the
|
||||||
|
library must be consistent with the full freedom of use specified in this
|
||||||
|
license.
|
||||||
|
|
||||||
|
Most GNU software, including some libraries, is covered by the ordinary GNU
|
||||||
|
General Public License. This license, the GNU Lesser General Public License,
|
||||||
|
applies to certain designated libraries, and is quite different from the ordinary
|
||||||
|
General Public License. We use this license for certain libraries in order
|
||||||
|
to permit linking those libraries into non-free programs.
|
||||||
|
|
||||||
|
When a program is linked with a library, whether statically or using a shared
|
||||||
|
library, the combination of the two is legally speaking a combined work, a
|
||||||
|
derivative of the original library. The ordinary General Public License therefore
|
||||||
|
permits such linking only if the entire combination fits its criteria of freedom.
|
||||||
|
The Lesser General Public License permits more lax criteria for linking other
|
||||||
|
code with the library.
|
||||||
|
|
||||||
|
We call this license the "Lesser" General Public License because it does Less
|
||||||
|
to protect the user's freedom than the ordinary General Public License. It
|
||||||
|
also provides other free software developers Less of an advantage over competing
|
||||||
|
non-free programs. These disadvantages are the reason we use the ordinary
|
||||||
|
General Public License for many libraries. However, the Lesser license provides
|
||||||
|
advantages in certain special circumstances.
|
||||||
|
|
||||||
|
For example, on rare occasions, there may be a special need to encourage the
|
||||||
|
widest possible use of a certain library, so that it becomes a de-facto standard.
|
||||||
|
To achieve this, non-free programs must be allowed to use the library. A more
|
||||||
|
frequent case is that a free library does the same job as widely used non-free
|
||||||
|
libraries. In this case, there is little to gain by limiting the free library
|
||||||
|
to free software only, so we use the Lesser General Public License.
|
||||||
|
|
||||||
|
In other cases, permission to use a particular library in non-free programs
|
||||||
|
enables a greater number of people to use a large body of free software. For
|
||||||
|
example, permission to use the GNU C Library in non-free programs enables
|
||||||
|
many more people to use the whole GNU operating system, as well as its variant,
|
||||||
|
the GNU/Linux operating system.
|
||||||
|
|
||||||
|
Although the Lesser General Public License is Less protective of the users'
|
||||||
|
freedom, it does ensure that the user of a program that is linked with the
|
||||||
|
Library has the freedom and the wherewithal to run that program using a modified
|
||||||
|
version of the Library.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and modification
|
||||||
|
follow. Pay close attention to the difference between a "work based on the
|
||||||
|
library" and a "work that uses the library". The former contains code derived
|
||||||
|
from the library, whereas the latter must be combined with the library in
|
||||||
|
order to run.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License Agreement applies to any software library or other program
|
||||||
|
which contains a notice placed by the copyright holder or other authorized
|
||||||
|
party saying it may be distributed under the terms of this Lesser General
|
||||||
|
Public License (also called "this License"). Each licensee is addressed as
|
||||||
|
"you".
|
||||||
|
|
||||||
|
A "library" means a collection of software functions and/or data prepared
|
||||||
|
so as to be conveniently linked with application programs (which use some
|
||||||
|
of those functions and data) to form executables.
|
||||||
|
|
||||||
|
The "Library", below, refers to any such software library or work which has
|
||||||
|
been distributed under these terms. A "work based on the Library" means either
|
||||||
|
the Library or any derivative work under copyright law: that is to say, a
|
||||||
|
work containing the Library or a portion of it, either verbatim or with modifications
|
||||||
|
and/or translated straightforwardly into another language. (Hereinafter, translation
|
||||||
|
is included without limitation in the term "modification".)
|
||||||
|
|
||||||
|
"Source code" for a work means the preferred form of the work for making modifications
|
||||||
|
to it. For a library, complete source code means all the source code for all
|
||||||
|
modules it contains, plus any associated interface definition files, plus
|
||||||
|
the scripts used to control compilation and installation of the library.
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not covered
|
||||||
|
by this License; they are outside its scope. The act of running a program
|
||||||
|
using the Library is not restricted, and output from such a program is covered
|
||||||
|
only if its contents constitute a work based on the Library (independent of
|
||||||
|
the use of the Library in a tool for writing it). Whether that is true depends
|
||||||
|
on what the Library does and what the program that uses the Library does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Library's complete source
|
||||||
|
code as you receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice and disclaimer
|
||||||
|
of warranty; keep intact all the notices that refer to this License and to
|
||||||
|
the absence of any warranty; and distribute a copy of this License along with
|
||||||
|
the Library.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and you
|
||||||
|
may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Library or any portion of it,
|
||||||
|
thus forming a work based on the Library, and copy and distribute such modifications
|
||||||
|
or work under the terms of Section 1 above, provided that you also meet all
|
||||||
|
of these conditions:
|
||||||
|
|
||||||
|
a) The modified work must itself be a software library.
|
||||||
|
|
||||||
|
b) You must cause the files modified to carry prominent notices stating that
|
||||||
|
you changed the files and the date of any change.
|
||||||
|
|
||||||
|
c) You must cause the whole of the work to be licensed at no charge to all
|
||||||
|
third parties under the terms of this License.
|
||||||
|
|
||||||
|
d) If a facility in the modified Library refers to a function or a table of
|
||||||
|
data to be supplied by an application program that uses the facility, other
|
||||||
|
than as an argument passed when the facility is invoked, then you must make
|
||||||
|
a good faith effort to ensure that, in the event an application does not supply
|
||||||
|
such function or table, the facility still operates, and performs whatever
|
||||||
|
part of its purpose remains meaningful.
|
||||||
|
|
||||||
|
(For example, a function in a library to compute square roots has a purpose
|
||||||
|
that is entirely well-defined independent of the application. Therefore, Subsection
|
||||||
|
2d requires that any application-supplied function or table used by this function
|
||||||
|
must be optional: if the application does not supply it, the square root function
|
||||||
|
must still compute square roots.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If identifiable
|
||||||
|
sections of that work are not derived from the Library, and can be reasonably
|
||||||
|
considered independent and separate works in themselves, then this License,
|
||||||
|
and its terms, do not apply to those sections when you distribute them as
|
||||||
|
separate works. But when you distribute the same sections as part of a whole
|
||||||
|
which is a work based on the Library, the distribution of the whole must be
|
||||||
|
on the terms of this License, whose permissions for other licensees extend
|
||||||
|
to the entire whole, and thus to each and every part regardless of who wrote
|
||||||
|
it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest your
|
||||||
|
rights to work written entirely by you; rather, the intent is to exercise
|
||||||
|
the right to control the distribution of derivative or collective works based
|
||||||
|
on the Library.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Library with
|
||||||
|
the Library (or with a work based on the Library) on a volume of a storage
|
||||||
|
or distribution medium does not bring the other work under the scope of this
|
||||||
|
License.
|
||||||
|
|
||||||
|
3. You may opt to apply the terms of the ordinary GNU General Public License
|
||||||
|
instead of this License to a given copy of the Library. To do this, you must
|
||||||
|
alter all the notices that refer to this License, so that they refer to the
|
||||||
|
ordinary GNU General Public License, version 2, instead of to this License.
|
||||||
|
(If a newer version than version 2 of the ordinary GNU General Public License
|
||||||
|
has appeared, then you can specify that version instead if you wish.) Do not
|
||||||
|
make any other change in these notices.
|
||||||
|
|
||||||
|
Once this change is made in a given copy, it is irreversible for that copy,
|
||||||
|
so the ordinary GNU General Public License applies to all subsequent copies
|
||||||
|
and derivative works made from that copy.
|
||||||
|
|
||||||
|
This option is useful when you wish to copy part of the code of the Library
|
||||||
|
into a program that is not a library.
|
||||||
|
|
||||||
|
4. You may copy and distribute the Library (or a portion or derivative of
|
||||||
|
it, under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you accompany it with the complete corresponding
|
||||||
|
machine-readable source code, which must be distributed under the terms of
|
||||||
|
Sections 1 and 2 above on a medium customarily used for software interchange.
|
||||||
|
|
||||||
|
If distribution of object code is made by offering access to copy from a designated
|
||||||
|
place, then offering equivalent access to copy the source code from the same
|
||||||
|
place satisfies the requirement to distribute the source code, even though
|
||||||
|
third parties are not compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
5. A program that contains no derivative of any portion of the Library, but
|
||||||
|
is designed to work with the Library by being compiled or linked with it,
|
||||||
|
is called a "work that uses the Library". Such a work, in isolation, is not
|
||||||
|
a derivative work of the Library, and therefore falls outside the scope of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
However, linking a "work that uses the Library" with the Library creates an
|
||||||
|
executable that is a derivative of the Library (because it contains portions
|
||||||
|
of the Library), rather than a "work that uses the library". The executable
|
||||||
|
is therefore covered by this License. Section 6 states terms for distribution
|
||||||
|
of such executables.
|
||||||
|
|
||||||
|
When a "work that uses the Library" uses material from a header file that
|
||||||
|
is part of the Library, the object code for the work may be a derivative work
|
||||||
|
of the Library even though the source code is not. Whether this is true is
|
||||||
|
especially significant if the work can be linked without the Library, or if
|
||||||
|
the work is itself a library. The threshold for this to be true is not precisely
|
||||||
|
defined by law.
|
||||||
|
|
||||||
|
If such an object file uses only numerical parameters, data structure layouts
|
||||||
|
and accessors, and small macros and small inline functions (ten lines or less
|
||||||
|
in length), then the use of the object file is unrestricted, regardless of
|
||||||
|
whether it is legally a derivative work. (Executables containing this object
|
||||||
|
code plus portions of the Library will still fall under Section 6.)
|
||||||
|
|
||||||
|
Otherwise, if the work is a derivative of the Library, you may distribute
|
||||||
|
the object code for the work under the terms of Section 6. Any executables
|
||||||
|
containing that work also fall under Section 6, whether or not they are linked
|
||||||
|
directly with the Library itself.
|
||||||
|
|
||||||
|
6. As an exception to the Sections above, you may also combine or link a "work
|
||||||
|
that uses the Library" with the Library to produce a work containing portions
|
||||||
|
of the Library, and distribute that work under terms of your choice, provided
|
||||||
|
that the terms permit modification of the work for the customer's own use
|
||||||
|
and reverse engineering for debugging such modifications.
|
||||||
|
|
||||||
|
You must give prominent notice with each copy of the work that the Library
|
||||||
|
is used in it and that the Library and its use are covered by this License.
|
||||||
|
You must supply a copy of this License. If the work during execution displays
|
||||||
|
copyright notices, you must include the copyright notice for the Library among
|
||||||
|
them, as well as a reference directing the user to the copy of this License.
|
||||||
|
Also, you must do one of these things:
|
||||||
|
|
||||||
|
a) Accompany the work with the complete corresponding machine-readable source
|
||||||
|
code for the Library including whatever changes were used in the work (which
|
||||||
|
must be distributed under Sections 1 and 2 above); and, if the work is an
|
||||||
|
executable linked with the Library, with the complete machine-readable "work
|
||||||
|
that uses the Library", as object code and/or source code, so that the user
|
||||||
|
can modify the Library and then relink to produce a modified executable containing
|
||||||
|
the modified Library. (It is understood that the user who changes the contents
|
||||||
|
of definitions files in the Library will not necessarily be able to recompile
|
||||||
|
the application to use the modified definitions.)
|
||||||
|
|
||||||
|
b) Use a suitable shared library mechanism for linking with the Library. A
|
||||||
|
suitable mechanism is one that (1) uses at run time a copy of the library
|
||||||
|
already present on the user's computer system, rather than copying library
|
||||||
|
functions into the executable, and (2) will operate properly with a modified
|
||||||
|
version of the library, if the user installs one, as long as the modified
|
||||||
|
version is interface-compatible with the version that the work was made with.
|
||||||
|
|
||||||
|
c) Accompany the work with a written offer, valid for at least three years,
|
||||||
|
to give the same user the materials specified in Subsection 6a, above, for
|
||||||
|
a charge no more than the cost of performing this distribution.
|
||||||
|
|
||||||
|
d) If distribution of the work is made by offering access to copy from a designated
|
||||||
|
place, offer equivalent access to copy the above specified materials from
|
||||||
|
the same place.
|
||||||
|
|
||||||
|
e) Verify that the user has already received a copy of these materials or
|
||||||
|
that you have already sent this user a copy.
|
||||||
|
|
||||||
|
For an executable, the required form of the "work that uses the Library" must
|
||||||
|
include any data and utility programs needed for reproducing the executable
|
||||||
|
from it. However, as a special exception, the materials to be distributed
|
||||||
|
need not include anything that is normally distributed (in either source or
|
||||||
|
binary form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component itself
|
||||||
|
accompanies the executable.
|
||||||
|
|
||||||
|
It may happen that this requirement contradicts the license restrictions of
|
||||||
|
other proprietary libraries that do not normally accompany the operating system.
|
||||||
|
Such a contradiction means you cannot use both them and the Library together
|
||||||
|
in an executable that you distribute.
|
||||||
|
|
||||||
|
7. You may place library facilities that are a work based on the Library side-by-side
|
||||||
|
in a single library together with other library facilities not covered by
|
||||||
|
this License, and distribute such a combined library, provided that the separate
|
||||||
|
distribution of the work based on the Library and of the other library facilities
|
||||||
|
is otherwise permitted, and provided that you do these two things:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based on the
|
||||||
|
Library, uncombined with any other library facilities. This must be distributed
|
||||||
|
under the terms of the Sections above.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library of the fact that part of
|
||||||
|
it is a work based on the Library, and explaining where to find the accompanying
|
||||||
|
uncombined form of the same work.
|
||||||
|
|
||||||
|
8. You may not copy, modify, sublicense, link with, or distribute the Library
|
||||||
|
except as expressly provided under this License. Any attempt otherwise to
|
||||||
|
copy, modify, sublicense, link with, or distribute the Library is void, and
|
||||||
|
will automatically terminate your rights under this License. However, parties
|
||||||
|
who have received copies, or rights, from you under this License will not
|
||||||
|
have their licenses terminated so long as such parties remain in full compliance.
|
||||||
|
|
||||||
|
9. You are not required to accept this License, since you have not signed
|
||||||
|
it. However, nothing else grants you permission to modify or distribute the
|
||||||
|
Library or its derivative works. These actions are prohibited by law if you
|
||||||
|
do not accept this License. Therefore, by modifying or distributing the Library
|
||||||
|
(or any work based on the Library), you indicate your acceptance of this License
|
||||||
|
to do so, and all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Library or works based on it.
|
||||||
|
|
||||||
|
10. Each time you redistribute the Library (or any work based on the Library),
|
||||||
|
the recipient automatically receives a license from the original licensor
|
||||||
|
to copy, distribute, link with or modify the Library subject to these terms
|
||||||
|
and conditions. You may not impose any further restrictions on the recipients'
|
||||||
|
exercise of the rights granted herein. You are not responsible for enforcing
|
||||||
|
compliance by third parties with this License.
|
||||||
|
|
||||||
|
11. If, as a consequence of a court judgment or allegation of patent infringement
|
||||||
|
or for any other reason (not limited to patent issues), conditions are imposed
|
||||||
|
on you (whether by court order, agreement or otherwise) that contradict the
|
||||||
|
conditions of this License, they do not excuse you from the conditions of
|
||||||
|
this License. If you cannot distribute so as to satisfy simultaneously your
|
||||||
|
obligations under this License and any other pertinent obligations, then as
|
||||||
|
a consequence you may not distribute the Library at all. For example, if a
|
||||||
|
patent license would not permit royalty-free redistribution of the Library
|
||||||
|
by all those who receive copies directly or indirectly through you, then the
|
||||||
|
only way you could satisfy both it and this License would be to refrain entirely
|
||||||
|
from distribution of the Library.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under any
|
||||||
|
particular circumstance, the balance of the section is intended to apply,
|
||||||
|
and the section as a whole is intended to apply in other circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any patents
|
||||||
|
or other property right claims or to contest validity of any such claims;
|
||||||
|
this section has the sole purpose of protecting the integrity of the free
|
||||||
|
software distribution system which is implemented by public license practices.
|
||||||
|
Many people have made generous contributions to the wide range of software
|
||||||
|
distributed through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing to
|
||||||
|
distribute software through any other system and a licensee cannot impose
|
||||||
|
that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to be a
|
||||||
|
consequence of the rest of this License.
|
||||||
|
|
||||||
|
12. If the distribution and/or use of the Library is restricted in certain
|
||||||
|
countries either by patents or by copyrighted interfaces, the original copyright
|
||||||
|
holder who places the Library under this License may add an explicit geographical
|
||||||
|
distribution limitation excluding those countries, so that distribution is
|
||||||
|
permitted only in or among countries not thus excluded. In such case, this
|
||||||
|
License incorporates the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
13. The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the Lesser General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to address
|
||||||
|
new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Library specifies
|
||||||
|
a version number of this License which applies to it and "any later version",
|
||||||
|
you have the option of following the terms and conditions either of that version
|
||||||
|
or of any later version published by the Free Software Foundation. If the
|
||||||
|
Library does not specify a license version number, you may choose any version
|
||||||
|
ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
14. If you wish to incorporate parts of the Library into other free programs
|
||||||
|
whose distribution conditions are incompatible with these, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free Software
|
||||||
|
Foundation, write to the Free Software Foundation; we sometimes make exceptions
|
||||||
|
for this. Our decision will be guided by the two goals of preserving the free
|
||||||
|
status of all derivatives of our free software and of promoting the sharing
|
||||||
|
and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
|
||||||
|
THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
|
||||||
|
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY
|
||||||
|
"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
|
||||||
|
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
|
||||||
|
OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||||
|
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
|
||||||
|
THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE
|
||||||
|
OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
|
||||||
|
OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES
|
||||||
|
OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH
|
||||||
|
HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Libraries
|
||||||
|
|
||||||
|
If you develop a new library, and you want it to be of the greatest possible
|
||||||
|
use to the public, we recommend making it free software that everyone can
|
||||||
|
redistribute and change. You can do so by permitting redistribution under
|
||||||
|
these terms (or, alternatively, under the terms of the ordinary General Public
|
||||||
|
License).
|
||||||
|
|
||||||
|
To apply these terms, attach the following notices to the library. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively convey
|
||||||
|
the exclusion of warranty; and each file should have at least the "copyright"
|
||||||
|
line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the library's name and an idea of what it does.>
|
||||||
|
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU Lesser General Public License as published by the Free
|
||||||
|
Software Foundation; either version 2.1 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this library; if not, write to the Free Software Foundation, Inc., 51
|
||||||
|
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the library, if necessary. Here
|
||||||
|
is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in
|
||||||
|
|
||||||
|
the library `Frob' (a library for tweaking knobs) written
|
||||||
|
|
||||||
|
by James Random Hacker.
|
||||||
|
|
||||||
|
< signature of Ty Coon > , 1 April 1990
|
||||||
|
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
That's all there is to it!
|
||||||
163
LICENSES/LGPL-3.0-only.txt
Normal file
163
LICENSES/LGPL-3.0-only.txt
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||||
|
document, but changing it is not allowed.
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates the terms
|
||||||
|
and conditions of version 3 of the GNU General Public License, supplemented
|
||||||
|
by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser General
|
||||||
|
Public License, and the "GNU GPL" refers to version 3 of the GNU General Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License, other than
|
||||||
|
an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided by the
|
||||||
|
Library, but which is not otherwise based on the Library. Defining a subclass
|
||||||
|
of a class defined by the Library is deemed a mode of using an interface provided
|
||||||
|
by the Library.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an Application
|
||||||
|
with the Library. The particular version of the Library with which the Combined
|
||||||
|
Work was made is also called the "Linked Version".
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the Corresponding
|
||||||
|
Source for the Combined Work, excluding any source code for portions of the
|
||||||
|
Combined Work that, considered in isolation, are based on the Application,
|
||||||
|
and not on the Linked Version.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the object
|
||||||
|
code and/or source code for the Application, including any data and utility
|
||||||
|
programs needed for reproducing the Combined Work from the Application, but
|
||||||
|
excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License without
|
||||||
|
being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a facility
|
||||||
|
refers to a function or data to be supplied by an Application that uses the
|
||||||
|
facility (other than as an argument passed when the facility is invoked),
|
||||||
|
then you may convey a copy of the modified version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to ensure
|
||||||
|
that, in the event an Application does not supply the function or data, the
|
||||||
|
facility still operates, and performs whatever part of its purpose remains
|
||||||
|
meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of this License
|
||||||
|
applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from a header
|
||||||
|
file that is part of the Library. You may convey such object code under terms
|
||||||
|
of your choice, provided that, if the incorporated material is not limited
|
||||||
|
to numerical parameters, data structure layouts and accessors, or small macros,
|
||||||
|
inline functions and templates (ten or fewer lines in length), you do both
|
||||||
|
of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the Library
|
||||||
|
is used in it and that the Library and its use are covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that, taken together,
|
||||||
|
effectively do not restrict modification of the portions of the Library contained
|
||||||
|
in the Combined Work and reverse engineering for debugging such modifications,
|
||||||
|
if you also do each of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that the Library
|
||||||
|
is used in it and that the Library and its use are covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during execution, include
|
||||||
|
the copyright notice for the Library among these notices, as well as a reference
|
||||||
|
directing the user to the copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this License,
|
||||||
|
and the Corresponding Application Code in a form suitable for, and under terms
|
||||||
|
that permit, the user to recombine or relink the Application with a modified
|
||||||
|
version of the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the Library. A
|
||||||
|
suitable mechanism is one that (a) uses at run time a copy of the Library
|
||||||
|
already present on the user's computer system, and (b) will operate properly
|
||||||
|
with a modified version of the Library that is interface-compatible with the
|
||||||
|
Linked Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise be required
|
||||||
|
to provide such information under section 6 of the GNU GPL, and only to the
|
||||||
|
extent that such information is necessary to install and execute a modified
|
||||||
|
version of the Combined Work produced by recombining or relinking the Application
|
||||||
|
with a modified version of the Linked Version. (If you use option 4d0, the
|
||||||
|
Installation Information must accompany the Minimal Corresponding Source and
|
||||||
|
Corresponding Application Code. If you use option 4d1, you must provide the
|
||||||
|
Installation Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the Library side
|
||||||
|
by side in a single library together with other library facilities that are
|
||||||
|
not Applications and are not covered by this License, and convey such a combined
|
||||||
|
library under terms of your choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based on the
|
||||||
|
Library, uncombined with any other library facilities, conveyed under the
|
||||||
|
terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it is a work
|
||||||
|
based on the Library, and explaining where to find the accompanying uncombined
|
||||||
|
form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of the
|
||||||
|
GNU Lesser General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to address
|
||||||
|
new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Library as you
|
||||||
|
received it specifies that a certain numbered version of the GNU Lesser General
|
||||||
|
Public License "or any later version" applies to it, you have the option of
|
||||||
|
following the terms and conditions either of that published version or of
|
||||||
|
any later version published by the Free Software Foundation. If the Library
|
||||||
|
as you received it does not specify a version number of the GNU Lesser General
|
||||||
|
Public License, you may choose any version of the GNU Lesser General Public
|
||||||
|
License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide whether
|
||||||
|
future versions of the GNU Lesser General Public License shall apply, that
|
||||||
|
proxy's public statement of acceptance of any version is permanent authorization
|
||||||
|
for you to choose that version for the Library.
|
||||||
12
LICENSES/LicenseRef-KDE-Accepted-GPL.txt
Normal file
12
LICENSES/LicenseRef-KDE-Accepted-GPL.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU General Public License as
|
||||||
|
published by the Free Software Foundation; either version 3 of
|
||||||
|
the license or (at your option) at any later version that is
|
||||||
|
accepted by the membership of KDE e.V. (or its successor
|
||||||
|
approved by the membership of KDE e.V.), which shall act as a
|
||||||
|
proxy as defined in Section 14 of version 3 of the license.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
12
LICENSES/LicenseRef-KDE-Accepted-LGPL.txt
Normal file
12
LICENSES/LicenseRef-KDE-Accepted-LGPL.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 3 of the license or (at your option) any later version
|
||||||
|
that is accepted by the membership of KDE e.V. (or its successor
|
||||||
|
approved by the membership of KDE e.V.), which shall act as a
|
||||||
|
proxy as defined in Section 6 of version 3 of the license.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
19
LICENSES/MIT.txt
Executable file
19
LICENSES/MIT.txt
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
MIT License Copyright (c) <year> <copyright holders>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next
|
||||||
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||||
|
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||||
|
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
#! /usr/bin/env bash
|
#! /usr/bin/env bash
|
||||||
|
# SPDX-FileCopyrightText: None
|
||||||
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
$XGETTEXT `find . \( -name \*.cpp -o -name \*.h -o -name \*.qml \)` -o $podir/neochat.pot
|
$XGETTEXT `find . \( -name \*.cpp -o -name \*.h -o -name \*.qml \)` -o $podir/neochat.pot
|
||||||
|
|||||||
25
README.md
25
README.md
@@ -1,12 +1,23 @@
|
|||||||
# Neochat
|
<!--
|
||||||
|
SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carlschwan@kde.org>
|
||||||
|
SPDX-FileCopyrightText: 2020-2021 Tobias Fella <fella@posteo.de>
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
||||||
|
-->
|
||||||
|
# NeoChat
|
||||||
|
|
||||||
Neochat is a client for Matrix, the decentralized communication protocol for instant
|
NeoChat is a client for Matrix, the decentralized communication protocol for instant
|
||||||
messaging. It is a fork of Spectral, using KDE frameworks, most notably Kirigami,
|
messaging. It is a fork of Spectral, using KDE frameworks, most notably Kirigami,
|
||||||
KConfig and KI18n.
|
KConfig and KI18n.
|
||||||
|
|
||||||
|
<a href='https://flathub.org/apps/details/org.kde.neochat'><img width='190px' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-i-en.png'/></a>
|
||||||
|
|
||||||
|
|
||||||
## Get it
|
## Get it
|
||||||
|
|
||||||
There is no stable release for now, but a Flatpak version is available for the nightly
|
A stable release [is available](https://apps.kde.org/en/neochat) for download for Linux distributions.
|
||||||
|
|
||||||
|
|
||||||
|
Along with the stable release, a Flatpak version is available for the nightly
|
||||||
version:
|
version:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -15,10 +26,12 @@ flatpak remote-add --if-not-exists kdeapps --from https://distribute.kde.org/kde
|
|||||||
flatpak install kdeapps org.kde.neochat
|
flatpak install kdeapps org.kde.neochat
|
||||||
```
|
```
|
||||||
|
|
||||||
A nigthly build is also available for Android in the [KDE nightly F-Droid repo](https://community.kde.org/Android/FDroid)
|
A nightly build is also available for Android in the [KDE nightly F-Droid repo](https://community.kde.org/Android/FDroid)
|
||||||
and can also directly be downloaded from the [binary factory](https://binary-factory.kde.org/view/Android/job/Neochat_android/).
|
and can also directly be downloaded from the [binary factory](https://binary-factory.kde.org/view/Android/job/NeoChat_Nightly_android-arm64/).
|
||||||
|
|
||||||

|
Nightly builds for [Windows](https://binary-factory.kde.org/job/NeoChat_Nightly_win64/), [MacOS](https://binary-factory.kde.org/job/NeoChat_Nightly_macos/) and [AppImages](https://binary-factory.kde.org/job/NeoChat_Nightly_appimage/) can also be downloaded from the [binary factory](https://binary-factory.kde.org/search/?q=neochat).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
android:versionName="0.0.1"
|
android:versionName="0.0.1"
|
||||||
android:versionCode="1604412458"
|
android:versionCode="1604412458"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="Neochat" android:icon="@drawable/neochat">
|
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat">
|
||||||
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
|
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
|
||||||
android:name="org.qtproject.qt5.android.bindings.QtActivity"
|
android:name="org.qtproject.qt5.android.bindings.QtActivity"
|
||||||
android:label="Neochat"
|
android:label="NeoChat"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:launchMode="singleTop">
|
android:launchMode="singleTop">
|
||||||
|
|
||||||
@@ -53,7 +53,6 @@
|
|||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
|
||||||
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
|
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
#
|
#
|
||||||
# CMake module to search for the cmark library
|
# CMake module to search for the cmark library
|
||||||
#
|
#
|
||||||
|
|
||||||
# first try to find cmark-config.cmake
|
# first try to find cmark-config.cmake
|
||||||
# path to a file not in the search path can be set with 'cmake -Dcmark_DIR=some/path/'
|
# path to a file not in the search path can be set with 'cmake -Dcmark_DIR=some/path/'
|
||||||
find_package(cmark CONFIG)
|
find_package(cmark CONFIG QUIET)
|
||||||
if(cmark_FOUND AND TARGET cmark::cmark)
|
if(cmark_FOUND AND TARGET cmark::cmark)
|
||||||
# found it!
|
# found it!
|
||||||
return()
|
return()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include(FindPkgConfig)
|
find_package(PkgConfig QUIET)
|
||||||
pkg_check_modules(PC_CMARK QUIET cmark)
|
if(PKG_CONFIG_FOUND)
|
||||||
|
pkg_check_modules(PC_CMARK QUIET cmark)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(NOT CMARK_INCLUDE_DIR)
|
if(NOT CMARK_INCLUDE_DIR)
|
||||||
find_path(CMARK_INCLUDE_DIR
|
find_path(CMARK_INCLUDE_DIR
|
||||||
|
|||||||
14
cmake/Flatpak.cmake
Normal file
14
cmake/Flatpak.cmake
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
# Include FontConfig config which uses the Emoji One font from the
|
||||||
|
# KDE Flatpak SDK.
|
||||||
|
install(
|
||||||
|
FILES
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/cmake/Flatpak/99-noto-mono-color-emoji.conf
|
||||||
|
DESTINATION
|
||||||
|
${CMAKE_INSTALL_SYSCONFDIR}/fonts/conf.d/
|
||||||
|
)
|
||||||
|
|
||||||
23
cmake/Flatpak/99-noto-mono-color-emoji.conf
Normal file
23
cmake/Flatpak/99-noto-mono-color-emoji.conf
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||||
|
<fontconfig>
|
||||||
|
<alias>
|
||||||
|
<family>serif</family>
|
||||||
|
<prefer>
|
||||||
|
<family>Noto Color Emoji</family>
|
||||||
|
</prefer>
|
||||||
|
</alias>
|
||||||
|
<alias>
|
||||||
|
<family>sans-serif</family>
|
||||||
|
<prefer>
|
||||||
|
<family>Noto Color Emoji</family>
|
||||||
|
</prefer>
|
||||||
|
</alias>
|
||||||
|
<alias>
|
||||||
|
<family>monospace</family>
|
||||||
|
<prefer>
|
||||||
|
<family>Noto Color Emoji</family>
|
||||||
|
</prefer>
|
||||||
|
</alias>
|
||||||
|
</fontconfig>
|
||||||
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
|
|
||||||
import NeoChat.Setting 1.0
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
signal primaryClicked()
|
|
||||||
signal secondaryClicked()
|
|
||||||
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
|
|
||||||
onClicked: mouse.button == Qt.RightButton ? secondaryClicked() : primaryClicked()
|
|
||||||
onPressAndHold: secondaryClicked()
|
|
||||||
}
|
|
||||||
197
imports/NeoChat/Component/ChatBox/AttachmentPane.qml
Normal file
197
imports/NeoChat/Component/ChatBox/AttachmentPane.qml
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||||
|
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Page 1.0
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var attachmentMimetype: FileType.mimeTypeForUrl(ChatBoxHelper.attachmentPath)
|
||||||
|
readonly property bool hasImage: attachmentMimetype.valid && FileType.supportedImageFormats.includes(attachmentMimetype.preferredSuffix)
|
||||||
|
|
||||||
|
active: visible
|
||||||
|
sourceComponent: Component {
|
||||||
|
Pane {
|
||||||
|
id: attachmentPane
|
||||||
|
property string baseFileName: ChatBoxHelper.attachmentPath.toString().substring(ChatBoxHelper.attachmentPath.toString().lastIndexOf('/') + 1, ChatBoxHelper.attachmentPath.length)
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
property real spacing: attachmentPane.spacing > 0 ? attachmentPane.spacing : toolBar.spacing
|
||||||
|
implicitWidth: Math.max(image.implicitWidth, imageBusyIndicator.implicitWidth, fileInfoLayout.implicitWidth, toolBar.implicitWidth)
|
||||||
|
implicitHeight: Math.max(
|
||||||
|
(hasImage ? Math.max(image.preferredHeight, imageBusyIndicator.implicitHeight) + spacing : 0)
|
||||||
|
+ fileInfoLayout.implicitHeight,
|
||||||
|
toolBar.implicitHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: image
|
||||||
|
property real preferredHeight: Math.min(implicitHeight, Kirigami.Units.gridUnit * 8)
|
||||||
|
height: preferredHeight
|
||||||
|
anchors {
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
bottom: fileInfoLayout.top
|
||||||
|
bottomMargin: parent.spacing
|
||||||
|
}
|
||||||
|
width: Math.min(implicitWidth, attachmentPane.availableWidth)
|
||||||
|
asynchronous: true
|
||||||
|
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
|
||||||
|
smooth: height == preferredHeight && parent.height == parent.implicitHeight // Don't smooth until height animation stops
|
||||||
|
source: hasImage ? ChatBoxHelper.attachmentPath : ""
|
||||||
|
visible: hasImage
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
|
onSourceChanged: {
|
||||||
|
// Reset source size height, which affect implicitHeight
|
||||||
|
sourceSize.height = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
onSourceSizeChanged: {
|
||||||
|
if (implicitHeight > Kirigami.Units.gridUnit * 8) {
|
||||||
|
// This can save a lot of RAM when loading large images.
|
||||||
|
// It also improves visual quality for large images.
|
||||||
|
sourceSize.height = Kirigami.Units.gridUnit * 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
property: "height"
|
||||||
|
duration: Kirigami.Units.shortDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
id: imageBusyIndicator
|
||||||
|
anchors {
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
top: parent.top
|
||||||
|
bottom: fileInfoLayout.top
|
||||||
|
bottomMargin: parent.spacing
|
||||||
|
}
|
||||||
|
visible: running
|
||||||
|
running: image.visible && image.progress < 1
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: fileInfoLayout
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.verticalCenter: undefined
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
spacing: parent.spacing
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
id: mimetypeIcon
|
||||||
|
implicitHeight: Kirigami.Units.fontMetrics.roundedIconSize(fileLabel.implicitHeight)
|
||||||
|
implicitWidth: implicitHeight
|
||||||
|
source: attachmentMimetype.iconName
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: fileLabel
|
||||||
|
text: baseFileName
|
||||||
|
}
|
||||||
|
|
||||||
|
states: State {
|
||||||
|
when: !hasImage
|
||||||
|
AnchorChanges {
|
||||||
|
target: fileInfoLayout
|
||||||
|
anchors.bottom: undefined
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using a toolbar to get a button spacing consistent with what the QQC2 style normally has
|
||||||
|
// Also has some accessibility info
|
||||||
|
ToolBar {
|
||||||
|
id: toolBar
|
||||||
|
width: parent.width
|
||||||
|
anchors.top: parent.top
|
||||||
|
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
topPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
|
||||||
|
Kirigami.Theme.inherit: true
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: parent.spacing
|
||||||
|
Label {
|
||||||
|
Layout.leftMargin: -attachmentPane.leftPadding
|
||||||
|
Layout.topMargin: -attachmentPane.topPadding
|
||||||
|
leftPadding: cancelAttachmentButton.leftPadding + 1 + attachmentPane.leftPadding
|
||||||
|
rightPadding: cancelAttachmentButton.rightPadding + 1
|
||||||
|
topPadding: cancelAttachmentButton.topPadding + attachmentPane.topPadding
|
||||||
|
bottomPadding: cancelAttachmentButton.bottomPadding
|
||||||
|
text: i18n("Attachment:")
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
background: Kirigami.ShadowedRectangle {
|
||||||
|
property real cornerRadius: cancelAttachmentButton.background.hasOwnProperty("radius") ?
|
||||||
|
Math.min(cancelAttachmentButton.background.radius, height/2) : 0
|
||||||
|
corners.bottomLeftRadius: toolBar.mirrored ? cornerRadius : 0
|
||||||
|
corners.bottomRightRadius: toolBar.mirrored ? 0 : cornerRadius
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
opacity: 0.75
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
id: editImageButton
|
||||||
|
visible: hasImage
|
||||||
|
icon.name: "document-edit"
|
||||||
|
text: i18n("Edit")
|
||||||
|
display: AbstractButton.IconOnly
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: imageEditorPage
|
||||||
|
ImageEditorPage {
|
||||||
|
imagePath: ChatBoxHelper.attachmentPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClicked: {
|
||||||
|
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
|
||||||
|
imageEditor.newPathChanged.connect(function(newPath) {
|
||||||
|
applicationWindow().pageStack.layers.pop();
|
||||||
|
ChatBoxHelper.attachmentPath = newPath;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ToolTip.text: text
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
id: cancelAttachmentButton
|
||||||
|
icon.name: "dialog-cancel"
|
||||||
|
text: i18n("Cancel")
|
||||||
|
display: AbstractButton.IconOnly
|
||||||
|
onClicked: ChatBoxHelper.clearAttachment();
|
||||||
|
ToolTip.text: text
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
background: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
520
imports/NeoChat/Component/ChatBox/ChatBar.qml
Normal file
520
imports/NeoChat/Component/ChatBox/ChatBar.qml
Normal file
@@ -0,0 +1,520 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||||
|
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Templates 2.15 as T
|
||||||
|
import Qt.labs.platform 1.1 as Platform
|
||||||
|
import QtQuick.Window 2.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
ToolBar {
|
||||||
|
id: chatBar
|
||||||
|
property string replyEventId: ""
|
||||||
|
property string editEventId: ""
|
||||||
|
property alias inputFieldText: inputField.text
|
||||||
|
property alias textField: inputField
|
||||||
|
property alias emojiPaneOpened: emojiButton.checked
|
||||||
|
|
||||||
|
// store each user we autoComplete here, this will be helpful later to generate
|
||||||
|
// the matrix.to links.
|
||||||
|
// This use an hack to define: https://doc.qt.io/qt-5/qml-var.html#property-value-initialization-semantics
|
||||||
|
property var userAutocompleted: ({})
|
||||||
|
|
||||||
|
signal closeAllTriggered()
|
||||||
|
signal inputFieldForceActiveFocusTriggered()
|
||||||
|
signal messageSent()
|
||||||
|
signal pasteImageTriggered()
|
||||||
|
signal editLastUserMessage()
|
||||||
|
signal replyPreviousUserMessage()
|
||||||
|
|
||||||
|
property alias isCompleting: completionMenu.visible
|
||||||
|
|
||||||
|
onInputFieldForceActiveFocusTriggered: {
|
||||||
|
inputField.forceActiveFocus();
|
||||||
|
// set the cursor to the end of the text
|
||||||
|
inputField.cursorPosition = inputField.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
position: ToolBar.Footer
|
||||||
|
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
|
||||||
|
// Using a custom background because some styles like Material
|
||||||
|
// or Fusion might have ugly colors for a TextArea placed inside
|
||||||
|
// of a toolbar. ToolBar is otherwise the closest QQC2 component
|
||||||
|
// to what we want because of the padding and spacing values.
|
||||||
|
background: Rectangle {
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: chatBar.spacing
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.minimumHeight: inputField.implicitHeight
|
||||||
|
// lineSpacing is height+leading, so subtract leading once since leading only exists between lines.
|
||||||
|
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
|
||||||
|
+ inputField.topPadding + inputField.bottomPadding
|
||||||
|
|
||||||
|
FontMetrics {
|
||||||
|
id: fontMetrics
|
||||||
|
font: inputField.font
|
||||||
|
}
|
||||||
|
|
||||||
|
T.TextArea {
|
||||||
|
id: inputField
|
||||||
|
focus: true
|
||||||
|
/* Some QQC2 styles will have their own predefined backgrounds for TextAreas.
|
||||||
|
* Make sure there is no background since we are using the ToolBar background.
|
||||||
|
*
|
||||||
|
* This could cause a problem if the QQC2 style was designed around TextArea
|
||||||
|
* background colors being very different from the QPalette::Base color.
|
||||||
|
* Luckily, none of the Qt QQC2 styles do that and neither do KDE's QQC2 styles.
|
||||||
|
*/
|
||||||
|
background: MouseArea {
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
cursorShape: Qt.IBeamCursor
|
||||||
|
z: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
leftPadding: mirrored ? 0 : Kirigami.Units.largeSpacing
|
||||||
|
rightPadding: !mirrored ? 0 : Kirigami.Units.largeSpacing
|
||||||
|
topPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
|
||||||
|
property real progress: 0
|
||||||
|
property bool autoAppeared: false
|
||||||
|
//property int lineHeight: contentHeight / lineCount
|
||||||
|
|
||||||
|
text: inputFieldText
|
||||||
|
placeholderText: currentRoom.usesEncryption ? i18n("This room is encrypted. Sending encrypted messages is not yet supported.") : editEventId.length > 0 ? i18n("Edit Message") : i18n("Write your message...")
|
||||||
|
verticalAlignment: TextEdit.AlignVCenter
|
||||||
|
horizontalAlignment: TextEdit.AlignLeft
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
readOnly: currentRoom.usesEncryption
|
||||||
|
|
||||||
|
palette: Kirigami.Theme.palette
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
Kirigami.Theme.inherit: false
|
||||||
|
|
||||||
|
implicitWidth: Math.max(contentWidth + leftPadding + rightPadding,
|
||||||
|
implicitBackgroundWidth + leftInset + rightInset,
|
||||||
|
placeholder.implicitWidth + leftPadding + rightPadding)
|
||||||
|
implicitHeight: Math.max(contentHeight + topPadding + bottomPadding,
|
||||||
|
implicitBackgroundHeight + topInset + bottomInset,
|
||||||
|
placeholder.implicitHeight + topPadding + bottomPadding)
|
||||||
|
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
selectionColor: Kirigami.Theme.highlightColor
|
||||||
|
selectedTextColor: Kirigami.Theme.highlightedTextColor
|
||||||
|
hoverEnabled: !Kirigami.Settings.tabletMode
|
||||||
|
|
||||||
|
selectByMouse: !Kirigami.Settings.tabletMode
|
||||||
|
|
||||||
|
cursorDelegate: Loader {
|
||||||
|
visible: inputField.activeFocus && !inputField.readOnly && inputField.selectionStart === inputField.selectionEnd
|
||||||
|
active: visible
|
||||||
|
sourceComponent: CursorDelegate { target: inputField }
|
||||||
|
}
|
||||||
|
|
||||||
|
CursorHandle {
|
||||||
|
id: selectionStartHandle
|
||||||
|
target: inputField
|
||||||
|
}
|
||||||
|
|
||||||
|
CursorHandle {
|
||||||
|
id: selectionEndHandle
|
||||||
|
target: inputField
|
||||||
|
isSelectionEnd: true
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
|
||||||
|
// unfortunately, taphandler's pressed event only triggers when the press is lifted
|
||||||
|
// we need to use the longpress signal since it triggers when the button is first pressed
|
||||||
|
longPressThreshold: 0
|
||||||
|
onLongPressed: TextFieldContextMenu.targetClick(point, inputField, spellcheckhighlighter, inputField.positionAt(point.position.x, point.position.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressAndHold: {
|
||||||
|
if (!Kirigami.Settings.tabletMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
forceActiveFocus();
|
||||||
|
cursorPosition = positionAt(event.x, event.y);
|
||||||
|
selectWord();
|
||||||
|
}
|
||||||
|
|
||||||
|
onFocusChanged: {
|
||||||
|
if (focus) {
|
||||||
|
MobileTextActionsToolBar.controlRoot = inputField;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: placeholder
|
||||||
|
x: inputField.leftPadding
|
||||||
|
y: inputField.topPadding
|
||||||
|
width: inputField.width - (inputField.leftPadding + inputField.rightPadding)
|
||||||
|
height: inputField.height - (inputField.topPadding + inputField.bottomPadding)
|
||||||
|
|
||||||
|
text: inputField.placeholderText
|
||||||
|
font: inputField.font
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
horizontalAlignment: inputField.horizontalAlignment
|
||||||
|
verticalAlignment: inputField.verticalAlignment
|
||||||
|
visible: !inputField.length && !inputField.preeditText && (!inputField.activeFocus || inputField.horizontalAlignment !== Qt.AlignHCenter)
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ChatDocumentHandler {
|
||||||
|
id: documentHandler
|
||||||
|
document: inputField.textDocument
|
||||||
|
cursorPosition: inputField.cursorPosition
|
||||||
|
selectionStart: inputField.selectionStart
|
||||||
|
selectionEnd: inputField.selectionEnd
|
||||||
|
room: currentRoom ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
SpellcheckHighlighter {
|
||||||
|
id: spellcheckhighlighter
|
||||||
|
document: inputField.textDocument
|
||||||
|
cursorPosition: inputField.cursorPosition
|
||||||
|
selectionStart: inputField.selectionStart
|
||||||
|
selectionEnd: inputField.selectionEnd
|
||||||
|
onChangeCursorPosition: {
|
||||||
|
inputField.cursorPosition = start;
|
||||||
|
inputField.moveCursorSelection(end, TextEdit.SelectCharacters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: timeoutTimer
|
||||||
|
repeat: false
|
||||||
|
interval: 2000
|
||||||
|
onTriggered: {
|
||||||
|
repeatTimer.stop()
|
||||||
|
currentRoom.sendTypingNotification(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: repeatTimer
|
||||||
|
repeat: true
|
||||||
|
interval: 5000
|
||||||
|
triggeredOnStart: true
|
||||||
|
onTriggered: currentRoom.sendTypingNotification(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMessage(event) {
|
||||||
|
if (isCompleting) {
|
||||||
|
chatBar.complete();
|
||||||
|
|
||||||
|
isCompleting = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.modifiers & Qt.ShiftModifier) {
|
||||||
|
inputField.insert(cursorPosition, "\n")
|
||||||
|
} else {
|
||||||
|
chatBar.postMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onReturnPressed: { sendMessage(event) }
|
||||||
|
Keys.onEnterPressed: { sendMessage(event) }
|
||||||
|
|
||||||
|
Keys.onEscapePressed: {
|
||||||
|
closeAllTriggered()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onPressed: {
|
||||||
|
// trigger if context menu button is pressed
|
||||||
|
TextFieldContextMenu.targetKeyPressed(event, inputField)
|
||||||
|
|
||||||
|
if (event.key === Qt.Key_PageDown) {
|
||||||
|
switchRoomDown();
|
||||||
|
} else if (event.key === Qt.Key_PageUp) {
|
||||||
|
switchRoomUp();
|
||||||
|
} else if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
|
||||||
|
chatBar.pasteImage();
|
||||||
|
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
|
||||||
|
replyPreviousUserMessage();
|
||||||
|
} else if (event.key === Qt.Key_Up && inputField.text.length === 0) {
|
||||||
|
editLastUserMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onBacktabPressed: {
|
||||||
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
|
switchRoomUp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isCompleting) {
|
||||||
|
nextItemInFocusChain(false).forceActiveFocus(Qt.TabFocusReason)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!autoAppeared) {
|
||||||
|
let decrementedIndex = completionMenu.currentIndex - 1
|
||||||
|
// Wrap around to the last item
|
||||||
|
if (decrementedIndex < 0) {
|
||||||
|
decrementedIndex = Math.max(completionMenu.count - 1, 0) // 0 if count == 0
|
||||||
|
}
|
||||||
|
completionMenu.currentIndex = decrementedIndex
|
||||||
|
} else {
|
||||||
|
autoAppeared = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
chatBar.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// yes, decrement goes up and increment goes down visually.
|
||||||
|
Keys.onUpPressed: (event) => {
|
||||||
|
if (chatBar.isCompleting) {
|
||||||
|
event.accepted = true
|
||||||
|
completionMenu.listView.decrementCurrentIndex()
|
||||||
|
autoAppeared = true;
|
||||||
|
}
|
||||||
|
event.accepted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onDownPressed: (event) => {
|
||||||
|
if (chatBar.isCompleting) {
|
||||||
|
event.accepted = true
|
||||||
|
completionMenu.listView.incrementCurrentIndex()
|
||||||
|
autoAppeared = true;
|
||||||
|
}
|
||||||
|
event.accepted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onTabPressed: {
|
||||||
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
|
switchRoomDown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isCompleting) {
|
||||||
|
nextItemInFocusChain().forceActiveFocus(Qt.TabFocusReason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO detect moved cursor
|
||||||
|
|
||||||
|
// ignore first time tab was clicked so that user can select
|
||||||
|
// first emoji/user
|
||||||
|
if (!autoAppeared) {
|
||||||
|
let incrementedIndex = completionMenu.currentIndex + 1;
|
||||||
|
// Wrap around to the first item
|
||||||
|
if (incrementedIndex > completionMenu.count - 1) {
|
||||||
|
incrementedIndex = 0
|
||||||
|
}
|
||||||
|
completionMenu.currentIndex = incrementedIndex;
|
||||||
|
} else {
|
||||||
|
autoAppeared = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
chatBar.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressed: MobileTextActionsToolBar.shouldBeVisible = true;
|
||||||
|
|
||||||
|
onTextChanged: {
|
||||||
|
MobileTextActionsToolBar.shouldBeVisible = false;
|
||||||
|
timeoutTimer.restart()
|
||||||
|
repeatTimer.start()
|
||||||
|
currentRoom.cachedInput = text
|
||||||
|
autoAppeared = false;
|
||||||
|
|
||||||
|
const completionInfo = documentHandler.getAutocompletionInfo(isCompleting);
|
||||||
|
|
||||||
|
if (completionInfo.type === ChatDocumentHandler.Ignore) {
|
||||||
|
if (completionInfo.keyword) {
|
||||||
|
// custom emojis
|
||||||
|
const idx = completionMenu.currentIndex;
|
||||||
|
completionMenu.model = Array.from(chatBar.customEmojiModel.filterModel(completionInfo.keyword)).concat(EmojiModel.filterModel(completionInfo.keyword))
|
||||||
|
completionMenu.currentIndex = idx;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completionInfo.type === ChatDocumentHandler.None) {
|
||||||
|
isCompleting = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
completionMenu.completionType = completionInfo.type
|
||||||
|
if (completionInfo.type === ChatDocumentHandler.User) {
|
||||||
|
completionMenu.model = currentRoom.getUsers(completionInfo.keyword);
|
||||||
|
} else if (completionInfo.type === ChatDocumentHandler.Command) {
|
||||||
|
completionMenu.model = CommandModel.filterModel(completionInfo.keyword);
|
||||||
|
} else {
|
||||||
|
completionMenu.model = Array.from(chatBar.customEmojiModel.filterModel(completionInfo.keyword)).concat(EmojiModel.filterModel(completionInfo.keyword))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completionMenu.model.length === 0) {
|
||||||
|
isCompleting = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCompleting) {
|
||||||
|
isCompleting = true
|
||||||
|
autoAppeared = true;
|
||||||
|
completionMenu.endPosition = cursorPosition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
visible: !ChatBoxHelper.isReplying && (!ChatBoxHelper.hasAttachment || uploadingBusySpinner.running)
|
||||||
|
implicitWidth: uploadButton.implicitWidth
|
||||||
|
implicitHeight: uploadButton.implicitHeight
|
||||||
|
ToolButton {
|
||||||
|
id: uploadButton
|
||||||
|
anchors.fill: parent
|
||||||
|
// Matrix does not allow sending attachments in replies
|
||||||
|
visible: !ChatBoxHelper.isReplying && !ChatBoxHelper.hasAttachment && !uploadingBusySpinner.running
|
||||||
|
icon.name: "mail-attachment"
|
||||||
|
text: i18n("Attach an image or file")
|
||||||
|
display: AbstractButton.IconOnly
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (Clipboard.hasImage) {
|
||||||
|
attachDialog.open()
|
||||||
|
} else {
|
||||||
|
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
|
||||||
|
fileDialog.chosen.connect((path) => {
|
||||||
|
if (!path) { return }
|
||||||
|
ChatBoxHelper.attachmentPath = path;
|
||||||
|
})
|
||||||
|
fileDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolTip.text: text
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
}
|
||||||
|
BusyIndicator {
|
||||||
|
id: uploadingBusySpinner
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: running
|
||||||
|
running: currentRoom && currentRoom.hasFileUploading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
id: emojiButton
|
||||||
|
icon.name: "preferences-desktop-emoticons"
|
||||||
|
text: i18n("Add an Emoji")
|
||||||
|
display: AbstractButton.IconOnly
|
||||||
|
checkable: true
|
||||||
|
|
||||||
|
ToolTip.text: text
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
id: sendButton
|
||||||
|
icon.name: "document-send"
|
||||||
|
text: i18n("Send message")
|
||||||
|
display: AbstractButton.IconOnly
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
chatBar.postMessage()
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolTip.text: text
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Action {
|
||||||
|
id: pasteAction
|
||||||
|
shortcut: StandardKey.Paste
|
||||||
|
onTriggered: {
|
||||||
|
if (Clipboard.hasImage) {
|
||||||
|
pasteImageTriggered();
|
||||||
|
}
|
||||||
|
activeFocusItem.paste();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletionMenu {
|
||||||
|
id: completionMenu
|
||||||
|
width: parent.width
|
||||||
|
//height: 80 //Math.min(implicitHeight, delegate.implicitHeight * 6)
|
||||||
|
height: implicitHeight
|
||||||
|
y: -height - 1
|
||||||
|
z: 1
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
property: "height"
|
||||||
|
duration: Kirigami.Units.shortDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onCompleteTriggered: {
|
||||||
|
complete()
|
||||||
|
isCompleting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property CustomEmojiModel customEmojiModel: CustomEmojiModel {
|
||||||
|
connection: Controller.activeConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
function pasteImage() {
|
||||||
|
let localPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png";
|
||||||
|
if (!Clipboard.saveImage(localPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ChatBoxHelper.attachmentPath = localPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function postMessage() {
|
||||||
|
checkForFancyEffectsReason();
|
||||||
|
|
||||||
|
if (ChatBoxHelper.hasAttachment) {
|
||||||
|
// send attachment but don't reset the text
|
||||||
|
actionsHandler.postMessage("", ChatBoxHelper.attachmentPath,
|
||||||
|
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, {}, this.customEmojiModel);
|
||||||
|
currentRoom.markAllMessagesAsRead();
|
||||||
|
messageSent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const re = /^s\/([^\/]*)\/([^\/]*)/;
|
||||||
|
if (Config.allowQuickEdit && re.test(inputField.text)) {
|
||||||
|
// send edited messages
|
||||||
|
actionsHandler.postEdit(inputField.text);
|
||||||
|
} else {
|
||||||
|
// send normal message
|
||||||
|
actionsHandler.postMessage(inputField.text.trim(), ChatBoxHelper.attachmentPath,
|
||||||
|
ChatBoxHelper.replyEventId, ChatBoxHelper.editEventId, userAutocompleted, this.customEmojiModel);
|
||||||
|
}
|
||||||
|
currentRoom.markAllMessagesAsRead();
|
||||||
|
inputField.clear();
|
||||||
|
inputField.text = Qt.binding(function() {
|
||||||
|
return currentRoom ? currentRoom.cachedInput : "";
|
||||||
|
});
|
||||||
|
messageSent()
|
||||||
|
}
|
||||||
|
|
||||||
|
function complete() {
|
||||||
|
documentHandler.replaceAutoComplete(completionMenu.currentDisplayText);
|
||||||
|
if (completionMenu.completionType === ChatDocumentHandler.User
|
||||||
|
&& completionMenu.currentDisplayText.length > 0
|
||||||
|
&& completionMenu.currentItem.userId.length > 0) {
|
||||||
|
userAutocompleted[completionMenu.currentDisplayText] = completionMenu.currentItem.userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
276
imports/NeoChat/Component/ChatBox/ChatBox.qml
Normal file
276
imports/NeoChat/Component/ChatBox/ChatBox.qml
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||||
|
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import Qt.labs.platform 1.1 as Platform
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component.ChatBox 1.0
|
||||||
|
import NeoChat.Component.Emoji 1.0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property alias inputFieldText: chatBar.inputFieldText
|
||||||
|
|
||||||
|
signal fancyEffectsReasonFound(string fancyEffect)
|
||||||
|
signal messageSent()
|
||||||
|
signal editLastUserMessage()
|
||||||
|
signal replyPreviousUserMessage()
|
||||||
|
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
|
||||||
|
implicitWidth: {
|
||||||
|
let w = 0
|
||||||
|
for(let i = 0; i < visibleChildren.length; ++i) {
|
||||||
|
w = Math.max(w, Math.ceil(visibleChildren[i].implicitWidth))
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
implicitHeight: {
|
||||||
|
let h = 0
|
||||||
|
for(let i = 0; i < visibleChildren.length; ++i) {
|
||||||
|
h += Math.ceil(visibleChildren[i].implicitHeight)
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// For some reason, this is needed to make the height animation work even though
|
||||||
|
// it used to work and height should be directly affected by implicitHeight
|
||||||
|
height: implicitHeight
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
property: "height"
|
||||||
|
duration: Kirigami.Units.shortDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
id: connectionPaneSeparator
|
||||||
|
visible: connectionPane.visible
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
anchors.bottom: connectionPane.top
|
||||||
|
z: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Pane {
|
||||||
|
id: connectionPane
|
||||||
|
padding: fontMetrics.lineSpacing * 0.25
|
||||||
|
FontMetrics {
|
||||||
|
id: fontMetrics
|
||||||
|
font: networkLabel.font
|
||||||
|
}
|
||||||
|
spacing: 0
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
background: Rectangle {
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
}
|
||||||
|
visible: !Controller.isOnline
|
||||||
|
width: parent.width
|
||||||
|
QQC2.Label {
|
||||||
|
id: networkLabel
|
||||||
|
text: i18n("NeoChat is offline. Please check your network connection.")
|
||||||
|
}
|
||||||
|
anchors.bottom: emojiPickerLoaderSeparator.top
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
id: emojiPickerLoaderSeparator
|
||||||
|
visible: emojiPickerLoader.visible
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
anchors.bottom: emojiPickerLoader.top
|
||||||
|
z: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: emojiPickerLoader
|
||||||
|
active: visible
|
||||||
|
visible: chatBar.emojiPaneOpened
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
anchors.bottom: replySeparator.top
|
||||||
|
sourceComponent: EmojiPicker{
|
||||||
|
textArea: chatBar.textField
|
||||||
|
onChosen: addText(emoji)
|
||||||
|
}
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
property: "height"
|
||||||
|
duration: Kirigami.Units.shortDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
id: replySeparator
|
||||||
|
visible: replyPane.visible
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
anchors.bottom: replyPane.top
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ReplyPane {
|
||||||
|
id: replyPane
|
||||||
|
visible: ChatBoxHelper.isReplying || ChatBoxHelper.isEditing
|
||||||
|
user: ChatBoxHelper.replyUser
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
anchors.bottom: attachmentSeparator.top
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
property: "height"
|
||||||
|
duration: Kirigami.Units.shortDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onReplyCancelled: {
|
||||||
|
root.focusInputField()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
id: attachmentSeparator
|
||||||
|
visible: attachmentPane.visible
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
anchors.bottom: attachmentPane.top
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentPane {
|
||||||
|
id: attachmentPane
|
||||||
|
visible: ChatBoxHelper.hasAttachment
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
anchors.bottom: chatBarSeparator.top
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
property: "height"
|
||||||
|
duration: Kirigami.Units.shortDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
id: chatBarSeparator
|
||||||
|
visible: chatBar.visible
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
anchors.bottom: chatBar.top
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatBar {
|
||||||
|
id: chatBar
|
||||||
|
visible: currentRoom.canSendEvent("m.room.message")
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
property: "height"
|
||||||
|
duration: Kirigami.Units.shortDuration
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloseAllTriggered: closeAll()
|
||||||
|
onMessageSent: {
|
||||||
|
closeAll()
|
||||||
|
checkForFancyEffectsReason()
|
||||||
|
root.messageSent();
|
||||||
|
}
|
||||||
|
onEditLastUserMessage: {
|
||||||
|
root.editLastUserMessage();
|
||||||
|
}
|
||||||
|
onReplyPreviousUserMessage: {
|
||||||
|
root.replyPreviousUserMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkForFancyEffectsReason() {
|
||||||
|
if (!Config.showFancyEffects) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = root.inputFieldText.trim()
|
||||||
|
if (text.includes('\u{2744}')) {
|
||||||
|
root.fancyEffectsReasonFound("snowflake")
|
||||||
|
}
|
||||||
|
if (text.includes('\u{1F386}')) {
|
||||||
|
root.fancyEffectsReasonFound("fireworks")
|
||||||
|
}
|
||||||
|
if (text.includes('\u{1F387}')) {
|
||||||
|
root.fancyEffectsReasonFound("fireworks")
|
||||||
|
}
|
||||||
|
if (text.includes('\u{1F389}')) {
|
||||||
|
root.fancyEffectsReasonFound("confetti")
|
||||||
|
}
|
||||||
|
if (text.includes('\u{1F38A}')) {
|
||||||
|
root.fancyEffectsReasonFound("confetti")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addText(text) {
|
||||||
|
root.inputFieldText = inputFieldText + text
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertText(str) {
|
||||||
|
root.inputFieldText = inputFieldText.substr(0, inputField.cursorPosition) + str + inputFieldText.substr(inputField.cursorPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusInputField() {
|
||||||
|
chatBar.inputFieldForceActiveFocusTriggered()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: RoomManager
|
||||||
|
|
||||||
|
function onCurrentRoomChanged() {
|
||||||
|
chatBar.userAutocompleted = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: ChatBoxHelper
|
||||||
|
|
||||||
|
function onShouldClearText() {
|
||||||
|
root.inputFieldText = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function onEditing(editContent, editFormatedContent) {
|
||||||
|
// Set the input field in edit mode
|
||||||
|
root.inputFieldText = editContent;
|
||||||
|
|
||||||
|
// clean autocompletion list
|
||||||
|
chatBar.userAutocompleted = {};
|
||||||
|
|
||||||
|
// Fill autocompletion list with values extracted from message.
|
||||||
|
// We can't just iterate on every user in the list and try to
|
||||||
|
// find matching display name since some users have display name
|
||||||
|
// matching frequent words and this will marks too many words as
|
||||||
|
// mentions.
|
||||||
|
const regex = /<a href=\"https:\/\/matrix.to\/#\/(@[a-zA-Z09]*:[a-zA-Z09.]*)\">([^<]*)<\/a>/g;
|
||||||
|
|
||||||
|
let match;
|
||||||
|
while ((match = regex.exec(editFormatedContent.toString())) !== null) {
|
||||||
|
chatBar.userAutocompleted[match[2]] = match[1];
|
||||||
|
}
|
||||||
|
chatBox.forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAll() {
|
||||||
|
ChatBoxHelper.clear();
|
||||||
|
chatBar.emojiPaneOpened = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
161
imports/NeoChat/Component/ChatBox/CompletionMenu.qml
Normal file
161
imports/NeoChat/Component/ChatBox/CompletionMenu.qml
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||||
|
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import Qt.labs.qmlmodels 1.0
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component 1.0
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
id: control
|
||||||
|
|
||||||
|
// Expose internal ListView properties.
|
||||||
|
property alias model: completionListView.model
|
||||||
|
property alias listView: completionListView
|
||||||
|
property alias currentIndex: completionListView.currentIndex
|
||||||
|
property alias currentItem: completionListView.currentItem
|
||||||
|
property alias count: completionListView.count
|
||||||
|
property alias delegate: completionListView.delegate
|
||||||
|
|
||||||
|
// Autocomplee text
|
||||||
|
property string currentDisplayText: currentItem && (currentItem.displayName ?? "")
|
||||||
|
|
||||||
|
property int completionType: ChatDocumentHandler.Emoji
|
||||||
|
property int beginPosition: 0
|
||||||
|
property int endPosition: 0
|
||||||
|
|
||||||
|
signal completeTriggered()
|
||||||
|
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
|
||||||
|
bottomPadding: 0
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
topPadding: 0
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
onVisibleChanged: if (!visible) {
|
||||||
|
completionListView.currentIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitHeight: Math.min(completionListView.contentHeight, Kirigami.Units.gridUnit * 10)
|
||||||
|
|
||||||
|
contentItem: ScrollView {
|
||||||
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
|
ListView {
|
||||||
|
id: completionListView
|
||||||
|
implicitWidth: contentWidth
|
||||||
|
delegate: {
|
||||||
|
if (completionType === ChatDocumentHandler.Emoji) {
|
||||||
|
emojiDelegate
|
||||||
|
} else if (completionType === ChatDocumentHandler.Command) {
|
||||||
|
commandDelegate
|
||||||
|
} else if (completionType === ChatDocumentHandler.User) {
|
||||||
|
usernameDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyNavigationWraps: true
|
||||||
|
|
||||||
|
//interactive: Window.window ? contentHeight + control.topPadding + control.bottomPadding > Window.window.height : false
|
||||||
|
clip: true
|
||||||
|
currentIndex: control.currentIndex || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: usernameDelegate
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
id: usernameItem
|
||||||
|
width: ListView.view.width ?? implicitWidth
|
||||||
|
property string displayName: modelData.displayName
|
||||||
|
property string userId: modelData.id
|
||||||
|
leading: Kirigami.Avatar {
|
||||||
|
implicitHeight: Kirigami.Units.gridUnit
|
||||||
|
implicitWidth: implicitHeight
|
||||||
|
source: modelData.avatarMediaId ? ("image://mxc/" + modelData.avatarMediaId) : ""
|
||||||
|
color: modelData.color ? Qt.darker(modelData.color, 1.1) : null
|
||||||
|
}
|
||||||
|
text: modelData.displayName
|
||||||
|
onClicked: completeTriggered();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: emojiDelegate
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
id: emojiItem
|
||||||
|
width: ListView.view.width ?? implicitWidth
|
||||||
|
property string displayName: modelData.isCustom ? modelData.shortname : modelData.unicode
|
||||||
|
text: modelData.shortname
|
||||||
|
height: Kirigami.Units.gridUnit * 2
|
||||||
|
|
||||||
|
leading: Image {
|
||||||
|
source: modelData.isCustom ? modelData.unicode : ""
|
||||||
|
|
||||||
|
width: height
|
||||||
|
sourceSize.width: width
|
||||||
|
sourceSize.height: height
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: parent.status === Image.Loading
|
||||||
|
radius: height/2
|
||||||
|
gradient: ShimmerGradient { }
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: unicodeLabel
|
||||||
|
|
||||||
|
visible: !modelData.isCustom
|
||||||
|
|
||||||
|
font.family: 'emoji'
|
||||||
|
font.pixelSize: height - 2
|
||||||
|
|
||||||
|
text: modelData.unicode
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: completeTriggered();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: commandDelegate
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
id: commandItem
|
||||||
|
width: ListView.view.width ?? implicitWidth
|
||||||
|
text: "<i>" + modelData.parameter.replace("<", "<").replace(">", ">") + "</i> " + modelData.help
|
||||||
|
property string displayName: modelData.command
|
||||||
|
|
||||||
|
leading: Label {
|
||||||
|
id: commandLabel
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit
|
||||||
|
Layout.preferredWidth: textMetrics.tightBoundingRect.width
|
||||||
|
text: modelData.command
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
TextMetrics {
|
||||||
|
id: textMetrics
|
||||||
|
text: modelData.command
|
||||||
|
font: commandLabel.font
|
||||||
|
}
|
||||||
|
onClicked: completeTriggered();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
65
imports/NeoChat/Component/ChatBox/CursorDelegate.qml
Normal file
65
imports/NeoChat/Component/ChatBox/CursorDelegate.qml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
|
||||||
|
* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
|
||||||
|
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Window 2.15
|
||||||
|
import QtQuick.Templates 2.15
|
||||||
|
import org.kde.kirigami 2.14 as Kirigami
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property alias target: root.parent
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: cursorLine
|
||||||
|
property real previousX: 0
|
||||||
|
property real previousY: 0
|
||||||
|
parent: target
|
||||||
|
implicitWidth: target.cursorRectangle.width
|
||||||
|
implicitHeight: target.cursorRectangle.height
|
||||||
|
x: Math.floor(target.cursorRectangle.x)
|
||||||
|
y: Math.floor(target.cursorRectangle.y)
|
||||||
|
|
||||||
|
color: target.color
|
||||||
|
SequentialAnimation {
|
||||||
|
id: blinkAnimation
|
||||||
|
running: root.visible && Qt.styleHints.cursorFlashTime != 0 && target.selectionStart === target.selectionEnd
|
||||||
|
PropertyAction {
|
||||||
|
target: cursorLine
|
||||||
|
property: "opacity"
|
||||||
|
value: 1
|
||||||
|
}
|
||||||
|
PauseAnimation {
|
||||||
|
duration: Qt.styleHints.cursorFlashTime/2
|
||||||
|
}
|
||||||
|
SequentialAnimation {
|
||||||
|
loops: Animation.Infinite
|
||||||
|
OpacityAnimator {
|
||||||
|
target: cursorLine
|
||||||
|
from: 1
|
||||||
|
to: 0
|
||||||
|
duration: Qt.styleHints.cursorFlashTime/2
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
OpacityAnimator {
|
||||||
|
target: cursorLine
|
||||||
|
from: 0
|
||||||
|
to: 1
|
||||||
|
duration: Qt.styleHints.cursorFlashTime/2
|
||||||
|
easing.type: Easing.OutCubic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.target
|
||||||
|
function onCursorPositionChanged() {
|
||||||
|
blinkAnimation.restart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
98
imports/NeoChat/Component/ChatBox/CursorHandle.qml
Normal file
98
imports/NeoChat/Component/ChatBox/CursorHandle.qml
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
|
||||||
|
* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
|
||||||
|
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Window 2.15
|
||||||
|
import QtQuick.Templates 2.15
|
||||||
|
import org.kde.kirigami 2.14 as Kirigami
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: root
|
||||||
|
property Item target
|
||||||
|
property bool isSelectionEnd: false
|
||||||
|
visible: Kirigami.Settings.tabletMode && target.activeFocus && (isSelectionEnd ? target.selectionStart !== target.selectionEnd : true)
|
||||||
|
active: visible
|
||||||
|
sourceComponent: Kirigami.ShadowedRectangle {
|
||||||
|
id: handle
|
||||||
|
property real selectionStartX: Math.floor(Qt.inputMethod.anchorRectangle.x + (Qt.inputMethod.cursorRectangle.width - width)/2)
|
||||||
|
property real selectionStartY: Math.floor(Qt.inputMethod.anchorRectangle.y + Qt.inputMethod.cursorRectangle.height + pointyBitVerticalOffset)
|
||||||
|
property real selectionEndX: Math.floor(Qt.inputMethod.cursorRectangle.x + (Qt.inputMethod.cursorRectangle.width - width)/2)
|
||||||
|
property real selectionEndY: Math.floor(Qt.inputMethod.cursorRectangle.y + Qt.inputMethod.cursorRectangle.height + pointyBitVerticalOffset)
|
||||||
|
property real pointyBitVerticalOffset: Math.abs(pointyBit.y*2)
|
||||||
|
parent: Overlay.overlay
|
||||||
|
x: isSelectionEnd ? selectionEndX : selectionStartX
|
||||||
|
y: isSelectionEnd ? selectionEndY : selectionStartY
|
||||||
|
|
||||||
|
// HACK: make it appear above most popups that show up in the
|
||||||
|
// overlay in case any of them use TextField or TextArea
|
||||||
|
z: 999
|
||||||
|
|
||||||
|
//opacity: target.activeFocus ? 1 : 0
|
||||||
|
implicitHeight: {
|
||||||
|
let h = Kirigami.Units.gridUnit
|
||||||
|
return h - (h % 2 == 0 ? 1 : 0)
|
||||||
|
}
|
||||||
|
implicitWidth: implicitHeight
|
||||||
|
radius: width/2
|
||||||
|
|
||||||
|
color: target.selectionColor
|
||||||
|
|
||||||
|
shadow {
|
||||||
|
color: Qt.rgba(0,0,0,0.2)
|
||||||
|
size: 3
|
||||||
|
yOffset: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: pointyBit
|
||||||
|
x: (parent.width - width)/2
|
||||||
|
y: -height/4 + 0.2 // magic number to get it to line up with the edge of the circle
|
||||||
|
implicitHeight: parent.implicitHeight/2
|
||||||
|
implicitWidth: implicitHeight
|
||||||
|
antialiasing: true
|
||||||
|
rotation: 45
|
||||||
|
color: parent.color
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.ShadowedRectangle {
|
||||||
|
id: inner
|
||||||
|
visible: target.selectionStart !== target.selectionEnd && (handle.y < selectionStartY || handle.y < selectionEndY)
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Kirigami.Units.smallBorder
|
||||||
|
color: target.selectedTextColor
|
||||||
|
radius: height/2
|
||||||
|
Rectangle {
|
||||||
|
id: innerPointyBit
|
||||||
|
x: (parent.width - width)/2
|
||||||
|
y: -height/4 + 0.8 // magic number to get it to line up with the edge of the circle
|
||||||
|
implicitHeight: pointyBit.implicitHeight
|
||||||
|
implicitWidth: implicitHeight
|
||||||
|
antialiasing: true
|
||||||
|
rotation: 45
|
||||||
|
color: parent.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
enabled: handle.visible
|
||||||
|
anchors.fill: parent
|
||||||
|
// preventStealing: true
|
||||||
|
onPositionChanged: {
|
||||||
|
let pos = mapToItem(root.target, mouse.x, mouse.y);
|
||||||
|
pos = root.target.positionAt(pos.x, pos.y - handle.height - handle.pointyBitVerticalOffset);
|
||||||
|
|
||||||
|
if (target.selectionStart !== target.selectionEnd) {
|
||||||
|
if (!isSelectionEnd) {
|
||||||
|
root.target.select(Math.min(pos, root.target.selectionEnd - 1), root.target.selectionEnd);
|
||||||
|
} else {
|
||||||
|
root.target.select(root.target.selectionStart, Math.max(pos, root.target.selectionStart + 1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
root.target.cursorPosition = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick 2.1
|
||||||
|
import QtQuick.Layouts 1.2
|
||||||
|
import QtQuick.Window 2.2
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import org.kde.kirigami 2.5 as Kirigami
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property Item controlRoot
|
||||||
|
parent: controlRoot ? controlRoot.Window.contentItem : undefined
|
||||||
|
modal: false
|
||||||
|
focus: false
|
||||||
|
closePolicy: Popup.NoAutoClose
|
||||||
|
property bool shouldBeVisible: false
|
||||||
|
|
||||||
|
x: {
|
||||||
|
if (!controlRoot || !controlRoot.Window.contentItem) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Math.min(Math.max(0, controlRoot.mapToItem(root.parent, controlRoot.positionToRectangle(controlRoot.selectionStart).x, 0).x - root.width/2), controlRoot.Window.contentItem.width - root.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
y: {
|
||||||
|
if (!controlRoot || !controlRoot.Window.contentItem) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
var desiredY = controlRoot.mapToItem(root.parent, 0, controlRoot.positionToRectangle(controlRoot.selectionStart).y).y - root.height;
|
||||||
|
|
||||||
|
if (desiredY >= 0) {
|
||||||
|
return Math.min(desiredY, controlRoot.Window.contentItem.height - root.height);
|
||||||
|
} else {
|
||||||
|
return Math.min(Math.max(0, controlRoot.mapToItem(root.parent, 0, controlRoot.positionToRectangle(controlRoot.selectionEnd).y + Math.round(Kirigami.Units.gridUnit*1.5)).y), controlRoot.Window.contentItem.height - root.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
visible: controlRoot ? shouldBeVisible && Qt.platform.os !== "android" && Kirigami.Settings.tabletMode && (controlRoot.selectedText.length > 0 || controlRoot.canPaste) : false
|
||||||
|
|
||||||
|
width: contentItem.implicitWidth + leftPadding + rightPadding
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
ToolButton {
|
||||||
|
focusPolicy: Qt.NoFocus
|
||||||
|
icon.name: "edit-cut"
|
||||||
|
visible: controlRoot && controlRoot.selectedText.length > 0 && (!controlRoot.hasOwnProperty("echoMode") || controlRoot.echoMode === TextInput.Normal)
|
||||||
|
onClicked: {
|
||||||
|
controlRoot.cut();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolButton {
|
||||||
|
focusPolicy: Qt.NoFocus
|
||||||
|
icon.name: "edit-copy"
|
||||||
|
visible: controlRoot && controlRoot.selectedText.length > 0 && (!controlRoot.hasOwnProperty("echoMode") || controlRoot.echoMode === TextInput.Normal)
|
||||||
|
onClicked: {
|
||||||
|
controlRoot.copy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolButton {
|
||||||
|
focusPolicy: Qt.NoFocus
|
||||||
|
icon.name: "edit-paste"
|
||||||
|
visible: controlRoot && controlRoot.canPaste
|
||||||
|
onClicked: {
|
||||||
|
controlRoot.paste();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
117
imports/NeoChat/Component/ChatBox/ReplyPane.qml
Normal file
117
imports/NeoChat/Component/ChatBox/ReplyPane.qml
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||||
|
// SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.14 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: root
|
||||||
|
readonly property bool isEdit: ChatBoxHelper.isEditing
|
||||||
|
property var user: null
|
||||||
|
property string avatarMediaUrl: user ? "image://mxc/" + user.avatarMediaId : ""
|
||||||
|
|
||||||
|
signal replyCancelled()
|
||||||
|
|
||||||
|
active: visible
|
||||||
|
sourceComponent: Pane {
|
||||||
|
id: replyPane
|
||||||
|
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
|
||||||
|
spacing: leftPadding
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: replyPane.spacing
|
||||||
|
|
||||||
|
FontMetrics {
|
||||||
|
id: fontMetrics
|
||||||
|
font: textArea.font
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Avatar {
|
||||||
|
id: avatar
|
||||||
|
Layout.alignment: textContentLayout.height > avatar.height ? Qt.AlignHCenter | Qt.AlignTop : Qt.AlignCenter
|
||||||
|
Layout.preferredWidth: Layout.preferredHeight
|
||||||
|
Layout.preferredHeight: fontMetrics.lineSpacing * 2 - fontMetrics.leading
|
||||||
|
source: root.avatarMediaUrl
|
||||||
|
name: user ? user.displayName : ""
|
||||||
|
color: user ? user.color : "transparent"
|
||||||
|
visible: Boolean(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: textContentLayout
|
||||||
|
Layout.alignment: Qt.AlignCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: fontMetrics.leading
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
textFormat: Text.StyledText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: {
|
||||||
|
let heading = "<b>%1</b>"
|
||||||
|
let userName = user ? "<font color=\""+ user.color +"\">" + currentRoom.htmlSafeMemberName(user.id) + "</font>" : ""
|
||||||
|
if (isEdit) {
|
||||||
|
heading = heading.arg(i18n("Editing message:")) + "<br/>"
|
||||||
|
} else {
|
||||||
|
heading = heading.arg(i18n("Replying to %1:", userName))
|
||||||
|
}
|
||||||
|
|
||||||
|
return heading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ScrollView {
|
||||||
|
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumHeight: fontMetrics.lineSpacing * 8 - fontMetrics.leading
|
||||||
|
TextArea {
|
||||||
|
id: textArea
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
topPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
text: {
|
||||||
|
const stylesheet = "<style> a{color:"+Kirigami.Theme.linkColor+";}.user-pill{}</style>";
|
||||||
|
const content = ChatBoxHelper.isReplying ? ChatBoxHelper.replyEventContent : ChatBoxHelper.editContent;
|
||||||
|
return stylesheet + content;
|
||||||
|
}
|
||||||
|
selectByMouse: true
|
||||||
|
selectByKeyboard: true
|
||||||
|
readOnly: true
|
||||||
|
wrapMode: Label.Wrap
|
||||||
|
textFormat: TextEdit.RichText
|
||||||
|
background: null
|
||||||
|
HoverHandler {
|
||||||
|
cursorShape: textArea.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: cancelReplyButton
|
||||||
|
Layout.alignment: avatar.Layout.alignment
|
||||||
|
icon.name: "dialog-cancel"
|
||||||
|
text: i18n("Cancel")
|
||||||
|
display: AbstractButton.IconOnly
|
||||||
|
onClicked: {
|
||||||
|
ChatBoxHelper.clearEditReply();
|
||||||
|
root.replyCancelled();
|
||||||
|
}
|
||||||
|
ToolTip.text: text
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
259
imports/NeoChat/Component/ChatBox/TextFieldContextMenu.qml
Normal file
259
imports/NeoChat/Component/ChatBox/TextFieldContextMenu.qml
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
/*
|
||||||
|
SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com>
|
||||||
|
SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick 2.6
|
||||||
|
import QtQml 2.2
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import org.kde.kirigami 2.5 as Kirigami
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: contextMenu
|
||||||
|
|
||||||
|
property Item target
|
||||||
|
property bool deselectWhenMenuClosed: true
|
||||||
|
property int restoredCursorPosition: 0
|
||||||
|
property int restoredSelectionStart
|
||||||
|
property int restoredSelectionEnd
|
||||||
|
property bool persistentSelectionSetting
|
||||||
|
property var spellcheckhighlighter: null
|
||||||
|
property var suggestions: ([])
|
||||||
|
Component.onCompleted: persistentSelectionSetting = persistentSelectionSetting // break binding
|
||||||
|
|
||||||
|
property var runOnMenuClose
|
||||||
|
|
||||||
|
parent: Overlay.overlay
|
||||||
|
|
||||||
|
function storeCursorAndSelection() {
|
||||||
|
contextMenu.restoredCursorPosition = target.cursorPosition;
|
||||||
|
contextMenu.restoredSelectionStart = target.selectionStart;
|
||||||
|
contextMenu.restoredSelectionEnd = target.selectionEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// target is pressed with mouse
|
||||||
|
function targetClick(handlerPoint, newTarget, spellcheckhighlighter, mousePosition) {
|
||||||
|
if (handlerPoint.pressedButtons === Qt.RightButton) { // only accept just right click
|
||||||
|
if (contextMenu.visible) {
|
||||||
|
deselectWhenMenuClosed = false; // don't deselect text if menu closed by right click on textfield
|
||||||
|
dismiss();
|
||||||
|
} else {
|
||||||
|
contextMenu.target = newTarget;
|
||||||
|
contextMenu.target.persistentSelection = true; // persist selection when menu is opened
|
||||||
|
contextMenu.spellcheckhighlighter = spellcheckhighlighter
|
||||||
|
contextMenu.suggestions = spellcheckhighlighter.suggestions(mousePosition);
|
||||||
|
storeCursorAndSelection();
|
||||||
|
popup(contextMenu.target);
|
||||||
|
// slightly locate context menu away from mouse so no item is selected when menu is opened
|
||||||
|
x += 1
|
||||||
|
y += 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// context menu keyboard key
|
||||||
|
function targetKeyPressed(event, newTarget) {
|
||||||
|
if (event.modifiers === Qt.NoModifier && event.key === Qt.Key_Menu) {
|
||||||
|
contextMenu.target = newTarget;
|
||||||
|
target.persistentSelection = true; // persist selection when menu is opened
|
||||||
|
storeCursorAndSelection();
|
||||||
|
popup(contextMenu.target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property bool targetIsPassword: target !== null && (target.echoMode === TextInput.PasswordEchoOnEdit || target.echoMode === TextInput.Password)
|
||||||
|
|
||||||
|
onAboutToShow: {
|
||||||
|
if (Overlay.overlay) {
|
||||||
|
let tempZ = 0
|
||||||
|
for (let i in Overlay.overlay.visibleChildren) {
|
||||||
|
tempZ = Math.max(tempZ, Overlay.overlay.visibleChildren[i].z)
|
||||||
|
}
|
||||||
|
z = tempZ + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deal with whether or not text should be deselected
|
||||||
|
onClosed: {
|
||||||
|
// restore text field's original persistent selection setting
|
||||||
|
target.persistentSelection = persistentSelectionSetting
|
||||||
|
// deselect text field text if menu is closed not because of a right click on the text field
|
||||||
|
if (deselectWhenMenuClosed) {
|
||||||
|
target.deselect();
|
||||||
|
}
|
||||||
|
deselectWhenMenuClosed = true;
|
||||||
|
|
||||||
|
// restore cursor position
|
||||||
|
target.forceActiveFocus();
|
||||||
|
target.cursorPosition = restoredCursorPosition;
|
||||||
|
target.select(restoredSelectionStart, restoredSelectionEnd);
|
||||||
|
|
||||||
|
// run action
|
||||||
|
runOnMenuClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpened: {
|
||||||
|
runOnMenuClose = function() {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Instantiator {
|
||||||
|
active: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
||||||
|
model: suggestions
|
||||||
|
delegate: MenuItem {
|
||||||
|
text: modelData
|
||||||
|
onClicked: {
|
||||||
|
deselectWhenMenuClosed = false;
|
||||||
|
runOnMenuClose = function() {
|
||||||
|
spellcheckhighlighter.replaceWord(modelData);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onObjectAdded: contextMenu.insertItem(0, object)
|
||||||
|
onObjectRemoved: contextMenu.removeItem(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled && suggestions.length === 0
|
||||||
|
action: Action {
|
||||||
|
text: spellcheckhighlighter ? i18nc("@action:inmenu", "No suggestions for %1", spellcheckhighlighter.wordUnderMouse) : ""
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuSeparator {
|
||||||
|
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
||||||
|
action: Action {
|
||||||
|
text: i18n("Add to dictionary")
|
||||||
|
onTriggered: {
|
||||||
|
deselectWhenMenuClosed = false;
|
||||||
|
runOnMenuClose = function() {
|
||||||
|
spellcheckhighlighter.addWordToDictionary(spellcheckhighlighter.wordUnderMouse)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
||||||
|
action: Action {
|
||||||
|
text: i18n("Ignore")
|
||||||
|
onTriggered: {
|
||||||
|
deselectWhenMenuClosed = false;
|
||||||
|
runOnMenuClose = function() {
|
||||||
|
spellcheckhighlighter.ignoreWord(spellcheckhighlighter.wordUnderMouse)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuSeparator {
|
||||||
|
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
visible: target !== null && !target.readOnly
|
||||||
|
action: Action {
|
||||||
|
icon.name: "edit-undo-symbolic"
|
||||||
|
text: i18nc("@action:inmenu", "Undo")
|
||||||
|
shortcut: StandardKey.Undo
|
||||||
|
}
|
||||||
|
enabled: target !== null && target.canUndo
|
||||||
|
onTriggered: {
|
||||||
|
deselectWhenMenuClosed = false;
|
||||||
|
runOnMenuClose = function() {target.undo()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
visible: target !== null && !target.readOnly
|
||||||
|
action: Action {
|
||||||
|
icon.name: "edit-redo-symbolic"
|
||||||
|
text: i18nc("@action:inmenu", "Redo")
|
||||||
|
shortcut: StandardKey.Redo
|
||||||
|
}
|
||||||
|
enabled: target !== null && target.canRedo
|
||||||
|
onTriggered: {
|
||||||
|
deselectWhenMenuClosed = false;
|
||||||
|
runOnMenuClose = function() {target.redo()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuSeparator {
|
||||||
|
visible: target !== null && !target.readOnly
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
visible: target !== null && !target.readOnly && !targetIsPassword
|
||||||
|
action: Action {
|
||||||
|
icon.name: "edit-cut-symbolic"
|
||||||
|
text: i18nc("@action:inmenu", "Cut")
|
||||||
|
shortcut: StandardKey.Cut
|
||||||
|
}
|
||||||
|
enabled: target !== null && target.selectedText
|
||||||
|
onTriggered: {
|
||||||
|
deselectWhenMenuClosed = false;
|
||||||
|
runOnMenuClose = function() {target.cut()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
action: Action {
|
||||||
|
icon.name: "edit-copy-symbolic"
|
||||||
|
text: i18nc("@action:inmenu", "Copy")
|
||||||
|
shortcut: StandardKey.Copy
|
||||||
|
}
|
||||||
|
enabled: target !== null && target.selectedText
|
||||||
|
visible: !targetIsPassword
|
||||||
|
onTriggered: {
|
||||||
|
deselectWhenMenuClosed = false;
|
||||||
|
runOnMenuClose = function() {target.copy()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
visible: target !== null && !target.readOnly
|
||||||
|
action: Action {
|
||||||
|
icon.name: "edit-paste-symbolic"
|
||||||
|
text: i18nc("@action:inmenu", "Paste")
|
||||||
|
shortcut: StandardKey.Paste
|
||||||
|
}
|
||||||
|
enabled: target !== null && target.canPaste
|
||||||
|
onTriggered: {
|
||||||
|
deselectWhenMenuClosed = false;
|
||||||
|
runOnMenuClose = function() {target.paste()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
visible: target !== null && !target.readOnly
|
||||||
|
action: Action {
|
||||||
|
icon.name: "edit-delete-symbolic"
|
||||||
|
text: i18nc("@action:inmenu", "Delete")
|
||||||
|
shortcut: StandardKey.Delete
|
||||||
|
}
|
||||||
|
enabled: target !== null && target.selectedText
|
||||||
|
onTriggered: {
|
||||||
|
deselectWhenMenuClosed = false;
|
||||||
|
runOnMenuClose = function() {target.remove(target.selectionStart, target.selectionEnd)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuSeparator {
|
||||||
|
visible: !targetIsPassword
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
action: Action {
|
||||||
|
icon.name: "edit-select-all-symbolic"
|
||||||
|
text: i18nc("@action:inmenu", "Select All")
|
||||||
|
shortcut: StandardKey.SelectAll
|
||||||
|
}
|
||||||
|
visible: !targetIsPassword
|
||||||
|
onTriggered: {
|
||||||
|
deselectWhenMenuClosed = false;
|
||||||
|
runOnMenuClose = function() {target.selectAll()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
imports/NeoChat/Component/ChatBox/qmldir
Normal file
11
imports/NeoChat/Component/ChatBox/qmldir
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module NeoChat.Component.ChatBox
|
||||||
|
ChatBox 1.0 ChatBox.qml
|
||||||
|
ChatBar 1.0 ChatBar.qml
|
||||||
|
ReplyPane 1.0 ReplyPane.qml
|
||||||
|
AttachmentPane 1.0 AttachmentPane.qml
|
||||||
|
CompletionMenu 1.0 CompletionMenu.qml
|
||||||
|
EmojiPickerPane 1.0 EmojiPickerPane.qml
|
||||||
|
singleton TextFieldContextMenu 1.0 TextFieldContextMenu.qml
|
||||||
|
CursorDelegate 1.0 CursorDelegate.qml
|
||||||
|
CursorHandle 1.0 CursorHandle.qml
|
||||||
|
singleton MobileTextActionsToolBar 1.0 MobileTextActionsToolBar.qml
|
||||||
@@ -1,566 +0,0 @@
|
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
import Qt.labs.platform 1.0 as Platform
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
|
|
||||||
import NeoChat.Component 1.0
|
|
||||||
import NeoChat.Component.Emoji 1.0
|
|
||||||
import NeoChat.Dialog 1.0
|
|
||||||
import NeoChat.Page 1.0
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
ToolBar {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property alias isReply: replyItem.visible
|
|
||||||
property bool isReaction: false
|
|
||||||
property var replyUser
|
|
||||||
property string replyEventID
|
|
||||||
property string replyContent
|
|
||||||
|
|
||||||
property alias isAutoCompleting: autoCompleteListView.visible
|
|
||||||
property var autoCompleteModel
|
|
||||||
property int autoCompleteBeginPosition
|
|
||||||
property int autoCompleteEndPosition
|
|
||||||
|
|
||||||
property bool hasAttachment: false
|
|
||||||
property url attachmentPath
|
|
||||||
|
|
||||||
position: ToolBar.Footer
|
|
||||||
|
|
||||||
function addText(text) {
|
|
||||||
inputField.insert(inputField.length, text)
|
|
||||||
}
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
|
||||||
|
|
||||||
Action {
|
|
||||||
id: pasteAction
|
|
||||||
shortcut: StandardKey.Paste
|
|
||||||
onTriggered: {
|
|
||||||
if (Clipboard.hasImage) {
|
|
||||||
root.pasteImage();
|
|
||||||
}
|
|
||||||
activeFocusItem.paste();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
id: layout
|
|
||||||
spacing: 0
|
|
||||||
EmojiPicker {
|
|
||||||
id: emojiPicker
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
textArea: inputField
|
|
||||||
emojiModel: EmojiModel { id: emojiModel }
|
|
||||||
onChosen: {
|
|
||||||
textArea.insert(textArea.cursorPosition, emoji);
|
|
||||||
textArea.forceActiveFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.margins: 8
|
|
||||||
|
|
||||||
id: replyItem
|
|
||||||
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Control {
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
|
|
||||||
padding: 4
|
|
||||||
|
|
||||||
contentItem: RowLayout {
|
|
||||||
Kirigami.Avatar {
|
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit
|
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit
|
|
||||||
|
|
||||||
source: replyUser ? "image://mxc/" + replyUser.avatarMediaId: ""
|
|
||||||
name: replyUser ? replyUser.displayName : i18n("No name")
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
text: replyUser ? replyUser.displayName : i18n("No name")
|
|
||||||
rightPadding: 8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextEdit {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: "<style>a{color: " + color + ";} .user-pill{}</style>" + replyContent
|
|
||||||
color: Kirigami.Theme.textColor
|
|
||||||
|
|
||||||
selectByMouse: true
|
|
||||||
readOnly: true
|
|
||||||
wrapMode: Label.Wrap
|
|
||||||
textFormat: Text.RichText
|
|
||||||
selectedTextColor: "white"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ListView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 36
|
|
||||||
Layout.margins: 8
|
|
||||||
|
|
||||||
id: autoCompleteListView
|
|
||||||
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
model: autoCompleteModel
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
spacing: 4
|
|
||||||
orientation: ListView.Horizontal
|
|
||||||
highlightFollowsCurrentItem: true
|
|
||||||
keyNavigationWraps: true
|
|
||||||
|
|
||||||
delegate: Control {
|
|
||||||
property string displayText: modelData.displayName ?? modelData.unicode
|
|
||||||
property bool isEmoji: modelData.unicode != null
|
|
||||||
readonly property bool highlighted: autoCompleteListView.currentIndex == index
|
|
||||||
|
|
||||||
padding: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
contentItem: RowLayout {
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
Label {
|
|
||||||
width: Kirigami.Units.gridUnit
|
|
||||||
height: Kirigami.Units.gridUnit
|
|
||||||
visible: isEmoji
|
|
||||||
text: displayText
|
|
||||||
font.family: "Emoji"
|
|
||||||
font.pointSize: 20
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Avatar {
|
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit
|
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit
|
|
||||||
source: modelData.avatarMediaId ? "image://mxc/" + modelData.avatarMediaId : ""
|
|
||||||
color: modelData.color ? Qt.darker(modelData.color, 1.1) : null
|
|
||||||
visible: !isEmoji
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
visible: !isEmoji
|
|
||||||
text: displayText
|
|
||||||
color: highlighted ? Kirigami.Theme.highlightTextColor : Kirigami.Theme.textColor
|
|
||||||
font.underline: highlighted
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
rightPadding: Kirigami.Units.largeSpacing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
autoCompleteListView.currentIndex = index
|
|
||||||
documentHandler.replaceAutoComplete(displayText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Separator {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 1
|
|
||||||
visible: emojiPicker.visible || replyItem.visible || autoCompleteListView.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 10
|
|
||||||
source: attachmentPath
|
|
||||||
visible: hasAttachment && (attachmentPath.toString().endsWith('.png') || attachmentPath.toString().endsWith('.jpg'))
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
Layout.preferredWidth: paintedWidth
|
|
||||||
RowLayout {
|
|
||||||
anchors.right: parent.right
|
|
||||||
Button {
|
|
||||||
visible: isImage
|
|
||||||
icon.name: "document-edit"
|
|
||||||
|
|
||||||
// HACK: Use a component because an url doesn't work
|
|
||||||
Component {
|
|
||||||
id: imageEditorPage
|
|
||||||
ImageEditorPage {
|
|
||||||
imagePath: attachmentPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onClicked: {
|
|
||||||
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage, {
|
|
||||||
imagePath: attachmentPath
|
|
||||||
});
|
|
||||||
imageEditor.newPathChanged.connect(function(newPath) {
|
|
||||||
applicationWindow().pageStack.layers.pop();
|
|
||||||
attachmentPath = newPath;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ToolTip {
|
|
||||||
text: i18n("Edit")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
icon.name: "dialog-cancel"
|
|
||||||
onClicked: {
|
|
||||||
hasAttachment = false;
|
|
||||||
attachmentPath = "";
|
|
||||||
}
|
|
||||||
ToolTip {
|
|
||||||
text: i18n("Cancel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
color: rgba(255, 255, 255, 40)
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
implicitHeight: fileLabel.implicitHeight
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: fileLabel
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
text: attachmentPath !== "" ? attachmentPath.toString().substring(attachmentPath.toString().lastIndexOf('/') + 1, attachmentPath.length) : ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
visible: hasAttachment && !(attachmentPath.toString().endsWith('.png') || attachmentPath.toString().endsWith('.jpg'))
|
|
||||||
ToolButton {
|
|
||||||
icon.name: "dialog-cancel"
|
|
||||||
onClicked: {
|
|
||||||
hasAttachment = false;
|
|
||||||
attachmentPath = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
text: attachmentPath !== "" ? attachmentPath.toString().substring(attachmentPath.toString().lastIndexOf('/') + 1, attachmentPath.length) : ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Separator {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 1
|
|
||||||
visible: hasAttachment
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
spacing: 0 //Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: cancelReplyButton
|
|
||||||
|
|
||||||
visible: isReply
|
|
||||||
|
|
||||||
icon.name: "dialog-cancel"
|
|
||||||
|
|
||||||
onClicked: clearReply()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
TextArea {
|
|
||||||
id: inputField
|
|
||||||
property real progress: 0
|
|
||||||
property bool autoAppeared: false
|
|
||||||
|
|
||||||
ChatDocumentHandler {
|
|
||||||
id: documentHandler
|
|
||||||
document: inputField.textDocument
|
|
||||||
cursorPosition: inputField.cursorPosition
|
|
||||||
selectionStart: inputField.selectionStart
|
|
||||||
selectionEnd: inputField.selectionEnd
|
|
||||||
room: currentRoom ?? null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
placeholderText: i18n("Write your message...")
|
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
leftPadding: Kirigami.Units.smallSpacing
|
|
||||||
selectByMouse: true
|
|
||||||
verticalAlignment: TextEdit.AlignVCenter
|
|
||||||
|
|
||||||
text: currentRoom != null ? currentRoom.cachedInput : ""
|
|
||||||
|
|
||||||
background: Item {}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: currentRoom && currentRoom.hasFileUploading ? parent.width * currentRoom.fileUploadingProgress / 100 : 0
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
opacity: 0.2
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: timeoutTimer
|
|
||||||
|
|
||||||
repeat: false
|
|
||||||
interval: 2000
|
|
||||||
onTriggered: {
|
|
||||||
repeatTimer.stop()
|
|
||||||
currentRoom.sendTypingNotification(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: repeatTimer
|
|
||||||
|
|
||||||
repeat: true
|
|
||||||
interval: 5000
|
|
||||||
triggeredOnStart: true
|
|
||||||
onTriggered: currentRoom.sendTypingNotification(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onReturnPressed: {
|
|
||||||
if (isAutoCompleting) {
|
|
||||||
documentHandler.replaceAutoComplete(autoCompleteListView.currentItem.displayText)
|
|
||||||
isAutoCompleting = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.modifiers & Qt.ShiftModifier) {
|
|
||||||
insert(cursorPosition, "\n")
|
|
||||||
} else {
|
|
||||||
postMessage()
|
|
||||||
text = ""
|
|
||||||
clearReply()
|
|
||||||
closeAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onEscapePressed: closeAll()
|
|
||||||
|
|
||||||
Keys.onPressed: {
|
|
||||||
if (event.key === Qt.Key_PageDown) {
|
|
||||||
switchRoomDown();
|
|
||||||
} else if (event.key === Qt.Key_PageUp) {
|
|
||||||
switchRoomUp();
|
|
||||||
} else if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
|
|
||||||
root.pasteImage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onBacktabPressed: {
|
|
||||||
if (event.modifiers & Qt.ControlModifier) {
|
|
||||||
switchRoomUp();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isAutoCompleting) {
|
|
||||||
autoCompleteListView.decrementCurrentIndex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onTabPressed: {
|
|
||||||
if (event.modifiers & Qt.ControlModifier) {
|
|
||||||
switchRoomDown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isAutoCompleting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO detect moved cursor
|
|
||||||
|
|
||||||
// ignore first time tab was clicked so that user can select
|
|
||||||
// first emoji/user
|
|
||||||
if (autoAppeared === false) {
|
|
||||||
autoCompleteListView.incrementCurrentIndex()
|
|
||||||
} else {
|
|
||||||
autoAppeared = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
documentHandler.replaceAutoComplete(autoCompleteListView.currentItem.displayText)
|
|
||||||
}
|
|
||||||
|
|
||||||
onTextChanged: {
|
|
||||||
timeoutTimer.restart()
|
|
||||||
repeatTimer.start()
|
|
||||||
currentRoom.cachedInput = text
|
|
||||||
autoAppeared = false;
|
|
||||||
|
|
||||||
const autocompletionInfo = documentHandler.getAutocompletionInfo();
|
|
||||||
|
|
||||||
if (autocompletionInfo.type === ChatDocumentHandler.Ignore) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (autocompletionInfo.type === ChatDocumentHandler.None) {
|
|
||||||
isAutoCompleting = false;
|
|
||||||
autoCompleteListView.currentIndex = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autocompletionInfo.type === ChatDocumentHandler.User) {
|
|
||||||
autoCompleteModel = currentRoom.getUsers(autocompletionInfo.keyword);
|
|
||||||
} else {
|
|
||||||
autoCompleteModel = emojiModel.filterModel(autocompletionInfo.keyword);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autoCompleteModel.length === 0) {
|
|
||||||
isAutoCompleting = false;
|
|
||||||
autoCompleteListView.currentIndex = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
isAutoCompleting = true
|
|
||||||
autoAppeared = true;
|
|
||||||
autoCompleteEndPosition = cursorPosition
|
|
||||||
}
|
|
||||||
|
|
||||||
function postMessage() {
|
|
||||||
documentHandler.postMessage(inputField.text, attachmentPath, replyEventID);
|
|
||||||
clearAttachment();
|
|
||||||
currentRoom.markAllMessagesAsRead();
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ToolButton {
|
|
||||||
id: emojiButton
|
|
||||||
icon.name: "preferences-desktop-emoticons"
|
|
||||||
icon.color: "transparent"
|
|
||||||
|
|
||||||
checkable: true
|
|
||||||
checked: emojiPicker.visible
|
|
||||||
onToggled: emojiPicker.visible = !emojiPicker.visible
|
|
||||||
|
|
||||||
ToolTip {
|
|
||||||
text: i18n("Add an Emoji")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolButton {
|
|
||||||
id: uploadButton
|
|
||||||
|
|
||||||
visible: !isReply && !hasAttachment
|
|
||||||
|
|
||||||
icon.name: "mail-attachment"
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (Clipboard.hasImage) {
|
|
||||||
attachDialog.open()
|
|
||||||
} else {
|
|
||||||
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
|
|
||||||
|
|
||||||
fileDialog.chosen.connect(function(path) {
|
|
||||||
if (!path) return
|
|
||||||
|
|
||||||
root.attach(path)
|
|
||||||
})
|
|
||||||
|
|
||||||
fileDialog.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolTip {
|
|
||||||
text: i18n("Attach an image or file")
|
|
||||||
}
|
|
||||||
|
|
||||||
BusyIndicator {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
running: currentRoom && currentRoom.hasFileUploading
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolButton {
|
|
||||||
icon.name: "document-send"
|
|
||||||
icon.color: "transparent"
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
inputField.postMessage()
|
|
||||||
inputField.text = ""
|
|
||||||
root.clearReply()
|
|
||||||
root.closeAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolTip {
|
|
||||||
text: i18n("Send message")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
implicitHeight: 40
|
|
||||||
color: Kirigami.Theme.backgroundColor
|
|
||||||
Kirigami.Separator {
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
top: parent.top
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function insert(str) {
|
|
||||||
inputField.insert(inputField.cursorPosition, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear() {
|
|
||||||
inputField.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearReply() {
|
|
||||||
isReply = false
|
|
||||||
replyUser = null;
|
|
||||||
replyContent = "";
|
|
||||||
replyEventID = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function focus() {
|
|
||||||
inputField.forceActiveFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeAll() {
|
|
||||||
replyItem.visible = false
|
|
||||||
autoCompleteListView.visible = false
|
|
||||||
emojiPicker.visible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function attach(localPath) {
|
|
||||||
hasAttachment = true
|
|
||||||
attachmentPath = localPath
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearAttachment() {
|
|
||||||
hasAttachment = false
|
|
||||||
attachmentPath = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function pasteImage() {
|
|
||||||
var localPath = Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png";
|
|
||||||
if (!Clipboard.saveImage(localPath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
root.attach(localPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +1,24 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0 as NeoChat
|
||||||
import NeoChat.Component 1.0
|
import NeoChat.Component 1.0
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
id: _picker
|
||||||
|
|
||||||
property string emojiCategory: "history"
|
property string emojiCategory: "history"
|
||||||
property var textArea
|
property var textArea
|
||||||
property var emojiModel
|
readonly property var emojiModel: NeoChat.EmojiModel
|
||||||
|
|
||||||
|
property NeoChat.CustomEmojiModel customModel: NeoChat.CustomEmojiModel {
|
||||||
|
connection: NeoChat.Controller.activeConnection
|
||||||
|
}
|
||||||
|
|
||||||
signal chosen(string emoji)
|
signal chosen(string emoji)
|
||||||
|
|
||||||
@@ -32,6 +35,7 @@ ColumnLayout {
|
|||||||
orientation: ListView.Horizontal
|
orientation: ListView.Horizontal
|
||||||
|
|
||||||
model: ListModel {
|
model: ListModel {
|
||||||
|
ListElement { label: "custom"; category: "custom" }
|
||||||
ListElement { label: "⌛️"; category: "history" }
|
ListElement { label: "⌛️"; category: "history" }
|
||||||
ListElement { label: "😏"; category: "people" }
|
ListElement { label: "😏"; category: "people" }
|
||||||
ListElement { label: "🌲"; category: "nature" }
|
ListElement { label: "🌲"; category: "nature" }
|
||||||
@@ -44,16 +48,23 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delegate: ItemDelegate {
|
delegate: ItemDelegate {
|
||||||
width: Kirigami.Units.gridUnit * 2
|
id: del
|
||||||
|
|
||||||
|
required property string label
|
||||||
|
required property string category
|
||||||
|
|
||||||
|
width: contentItem.Layout.preferredWidth
|
||||||
height: Kirigami.Units.gridUnit * 2
|
height: Kirigami.Units.gridUnit * 2
|
||||||
|
|
||||||
contentItem: Kirigami.Heading {
|
contentItem: Kirigami.Heading {
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
level: 1
|
level: del.label === "custom" ? 4 : 1
|
||||||
|
|
||||||
font.family: 'emoji'
|
Layout.preferredWidth: del.label === "custom" ? implicitWidth + Kirigami.Units.largeSpacing : Kirigami.Units.gridUnit * 2
|
||||||
text: label
|
|
||||||
|
font.family: del.label === "custom" ? "" : 'emoji'
|
||||||
|
text: del.label === "custom" ? i18n("Custom") : del.label
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -90,6 +101,8 @@ ColumnLayout {
|
|||||||
|
|
||||||
model: {
|
model: {
|
||||||
switch (emojiCategory) {
|
switch (emojiCategory) {
|
||||||
|
case "custom":
|
||||||
|
return _picker.customModel
|
||||||
case "history":
|
case "history":
|
||||||
return emojiModel.history
|
return emojiModel.history
|
||||||
case "people":
|
case "people":
|
||||||
@@ -121,11 +134,32 @@ ColumnLayout {
|
|||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
font.family: 'emoji'
|
font.family: 'emoji'
|
||||||
text: modelData.unicode
|
text: modelData.isCustom ? "" : modelData.unicode
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
visible: modelData.isCustom
|
||||||
|
source: visible ? modelData.unicode : ""
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 2
|
||||||
|
|
||||||
|
sourceSize.width: width
|
||||||
|
sourceSize.height: height
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: parent.status === Image.Loading
|
||||||
|
radius: height/2
|
||||||
|
gradient: ShimmerGradient { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
chosen(modelData.unicode)
|
if (modelData.isCustom) {
|
||||||
|
chosen(modelData.shortname)
|
||||||
|
} else {
|
||||||
|
chosen(modelData.unicode)
|
||||||
|
}
|
||||||
emojiModel.emojiUsed(modelData)
|
emojiModel.emojiUsed(modelData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
296
imports/NeoChat/Component/FancyEffectsContainer.qml
Normal file
296
imports/NeoChat/Component/FancyEffectsContainer.qml
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Alexey Andreyev <aa13q@ya.ru>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as Controls
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Particles 2.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: item
|
||||||
|
property bool enabled: false
|
||||||
|
property int effectInterval: Kirigami.Units.veryLongDuration*10;
|
||||||
|
property color darkSnowColor: "grey"
|
||||||
|
property bool isThemeDark: Kirigami.Theme.backgroundColor.hslLightness <= darkSnowColor.hslLightness
|
||||||
|
|
||||||
|
function showConfettiEffect() {
|
||||||
|
confettiTimer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSnowEffect() {
|
||||||
|
snowTimer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
function showFireworksEffect() {
|
||||||
|
fireworksTimer.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confetti
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: confettiTimer
|
||||||
|
interval: item.effectInterval;
|
||||||
|
running: false;
|
||||||
|
repeat: false;
|
||||||
|
triggeredOnStart: true;
|
||||||
|
onTriggered: {
|
||||||
|
if (item.enabled) {
|
||||||
|
confettiSystem.running = !confettiSystem.running
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParticleSystem {
|
||||||
|
id: confettiSystem
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
running: false
|
||||||
|
onRunningChanged: {
|
||||||
|
if (running) {
|
||||||
|
opacity = 1
|
||||||
|
} else {
|
||||||
|
opacity = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
SequentialAnimation {
|
||||||
|
NumberAnimation { duration: Kirigami.Units.longDuration }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageParticle {
|
||||||
|
source: "qrc:/imports/NeoChat/Component/confetti.png"
|
||||||
|
entryEffect: ImageParticle.Scale
|
||||||
|
rotationVariation: 360
|
||||||
|
rotationVelocity: 90
|
||||||
|
color: Qt.hsla(Math.random(), 0.5, 0.6, 1)
|
||||||
|
colorVariation: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Emitter {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
top: parent.top
|
||||||
|
}
|
||||||
|
|
||||||
|
sizeVariation: Kirigami.Units.iconSizes.small/2
|
||||||
|
lifeSpan: Kirigami.Units.veryLongDuration*10
|
||||||
|
size: Kirigami.Units.iconSizes.small
|
||||||
|
|
||||||
|
velocity: AngleDirection {
|
||||||
|
angle: 90
|
||||||
|
angleVariation: 42
|
||||||
|
magnitude: 500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snow
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: snowTimer
|
||||||
|
interval: item.effectInterval;
|
||||||
|
running: false;
|
||||||
|
repeat: false;
|
||||||
|
triggeredOnStart: true;
|
||||||
|
onTriggered: {
|
||||||
|
if (item.enabled) {
|
||||||
|
snowSystem.running = !snowSystem.running
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParticleSystem {
|
||||||
|
id: snowSystem
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
running: false
|
||||||
|
onRunningChanged: {
|
||||||
|
if (running) {
|
||||||
|
opacity = 1
|
||||||
|
} else {
|
||||||
|
opacity = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
SequentialAnimation {
|
||||||
|
NumberAnimation { duration: Kirigami.Units.longDuration }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemParticle {
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: 10
|
||||||
|
height: width
|
||||||
|
radius: width
|
||||||
|
color: item.isThemeDark ? "white" : darkSnowColor
|
||||||
|
scale: Math.random()
|
||||||
|
opacity: Math.random()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Emitter {
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
top: parent.top
|
||||||
|
}
|
||||||
|
|
||||||
|
sizeVariation: Kirigami.Units.iconSizes.medium
|
||||||
|
lifeSpan: Kirigami.Units.veryLongDuration*10
|
||||||
|
size: Kirigami.Units.iconSizes.large
|
||||||
|
emitRate: 42
|
||||||
|
|
||||||
|
velocity: AngleDirection {
|
||||||
|
angle: 90
|
||||||
|
angleVariation: 10
|
||||||
|
magnitude: 300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fireworks
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: fireworksTimer
|
||||||
|
interval: item.effectInterval;
|
||||||
|
running: false;
|
||||||
|
repeat: false;
|
||||||
|
triggeredOnStart: true;
|
||||||
|
onTriggered: {
|
||||||
|
if (item.enabled) {
|
||||||
|
fireworksInternalTimer.running = !fireworksInternalTimer.running
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: fireworksInternalTimer
|
||||||
|
interval: 300
|
||||||
|
triggeredOnStart: true
|
||||||
|
running: false
|
||||||
|
repeat: true
|
||||||
|
onTriggered: {
|
||||||
|
var x = Math.random() * parent.width
|
||||||
|
var y = Math.random() * parent.height
|
||||||
|
customEmit(x, y)
|
||||||
|
customEmit(x, y)
|
||||||
|
customEmit(x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParticleSystem {
|
||||||
|
id: fireworksSystem
|
||||||
|
anchors.fill: parent
|
||||||
|
running: fireworksInternalTimer.running
|
||||||
|
onRunningChanged: {
|
||||||
|
if (running) {
|
||||||
|
opacity = 1
|
||||||
|
} else {
|
||||||
|
opacity = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
SequentialAnimation {
|
||||||
|
NumberAnimation { duration: Kirigami.Units.longDuration }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageParticle {
|
||||||
|
id: fireworksParticleA
|
||||||
|
system: fireworksSystem
|
||||||
|
source: "qrc:/imports/NeoChat/Component/glowdot.png"
|
||||||
|
alphaVariation: item.isThemeDark ? 0.1 : 0.1
|
||||||
|
alpha: item.isThemeDark ? 0.5 : 1
|
||||||
|
groups: ["a"]
|
||||||
|
opacity: fireworksSystem.opacity
|
||||||
|
entryEffect: ImageParticle.Scale
|
||||||
|
rotationVariation: 360
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageParticle {
|
||||||
|
system: fireworksSystem
|
||||||
|
source: "qrc:/imports/NeoChat/Component/glowdot.png"
|
||||||
|
color: item.isThemeDark ? "white" : "gold"
|
||||||
|
alphaVariation: item.isThemeDark ? 0.1 : 0.1
|
||||||
|
alpha: item.isThemeDark ? 0.5 : 1
|
||||||
|
groups: ["light"]
|
||||||
|
opacity: fireworksSystem.opacity
|
||||||
|
entryEffect: ImageParticle.Scale
|
||||||
|
rotationVariation: 360
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageParticle {
|
||||||
|
id: fireworksParticleB
|
||||||
|
system: fireworksSystem
|
||||||
|
source: "qrc:/imports/NeoChat/Component/glowdot.png"
|
||||||
|
alphaVariation: item.isThemeDark ? 0.1 : 0.1
|
||||||
|
alpha: item.isThemeDark ? 0.5 : 1
|
||||||
|
groups: ["b"]
|
||||||
|
opacity: fireworksSystem.opacity
|
||||||
|
entryEffect: ImageParticle.Scale
|
||||||
|
rotationVariation: 360
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: emitterComp
|
||||||
|
Emitter {
|
||||||
|
id: container
|
||||||
|
property int life: 23
|
||||||
|
property real targetX: 0
|
||||||
|
property real targetY: 0
|
||||||
|
width: 1
|
||||||
|
height: 1
|
||||||
|
system: fireworksSystem
|
||||||
|
size: 16
|
||||||
|
endSize: 8
|
||||||
|
sizeVariation: 5
|
||||||
|
Timer {
|
||||||
|
interval: life
|
||||||
|
running: true
|
||||||
|
onTriggered: {
|
||||||
|
container.destroy();
|
||||||
|
var randomHue = Math.random()
|
||||||
|
var lightness = item.isThemeDark ? 0.8 : 0.7
|
||||||
|
fireworksParticleA.color = Qt.hsla(randomHue, 0.8, lightness, 1)
|
||||||
|
fireworksParticleB.color = Qt.hsla(1-randomHue, 0.8, lightness, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
velocity: AngleDirection {angleVariation:360; magnitude: 200}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function customEmit(x,y) {
|
||||||
|
var currentSize = Math.round(Math.random() * 200) + 40
|
||||||
|
var currentLifeSpan = Math.round(Math.random() * 1000) + 100
|
||||||
|
for (var i=0; i<8; i++) {
|
||||||
|
var obj = emitterComp.createObject(parent);
|
||||||
|
obj.x = x
|
||||||
|
obj.y = y
|
||||||
|
obj.targetX = Math.random() * currentSize - currentSize/2 + obj.x
|
||||||
|
obj.targetY = Math.random() * currentSize - currentSize/2 + obj.y
|
||||||
|
obj.life = Math.round(Math.random() * 23) + 150
|
||||||
|
obj.emitRate = Math.round(Math.random() * 32) + 5
|
||||||
|
obj.lifeSpan = currentLifeSpan
|
||||||
|
const group = Math.round(Math.random() * 3);
|
||||||
|
switch (group) {
|
||||||
|
case 0:
|
||||||
|
obj.group = "light";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
obj.group = "a";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
obj.group = "b";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick 2.12
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
import org.kde.kirigami 2.12 as Kirigami
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string filename
|
property string filename
|
||||||
property url localPath
|
property url localPath
|
||||||
|
property string blurhash: ""
|
||||||
|
property int imageWidth: -1
|
||||||
|
property int imageHeight: -1
|
||||||
|
|
||||||
flags: Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
|
flags: Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
|
||||||
visibility: Qt.WindowFullScreen
|
visibility: Qt.WindowFullScreen
|
||||||
@@ -31,16 +31,30 @@ ApplicationWindow {
|
|||||||
onClicked: root.destroy()
|
onClicked: root.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
visible: image.status !== Image.Ready && root.blurhash === ""
|
||||||
|
anchors.centerIn: parent
|
||||||
|
running: visible
|
||||||
|
}
|
||||||
|
|
||||||
AnimatedImage {
|
AnimatedImage {
|
||||||
|
id: image
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
width: Math.min(sourceSize.width, root.width)
|
width: Math.min(root.imageWidth !== -1 ? root.imageWidth : sourceSize.width, root.width)
|
||||||
height: Math.min(sourceSize.height, root.height)
|
height: Math.min(root.imageHeight !== -1 ? root.imageWidth : sourceSize.height, root.height)
|
||||||
|
|
||||||
cache: false
|
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
source: localPath
|
source: localPath
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: image.width
|
||||||
|
height: image.height
|
||||||
|
source: root.blurhash !== "" ? ("image://blurhash/" + root.blurhash) : ""
|
||||||
|
visible: root.blurhash !== "" && parent.status !== Image.Ready
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
|||||||
61
imports/NeoChat/Component/Login/Homeserver.qml
Normal file
61
imports/NeoChat/Component/Login/Homeserver.qml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
LoginStep {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property var homeserver: customHomeserver.visible ? customHomeserver.text : serverCombo.currentText
|
||||||
|
property bool loading: false
|
||||||
|
|
||||||
|
title: i18nc("@title", "Select a Homeserver")
|
||||||
|
|
||||||
|
action: Kirigami.Action {
|
||||||
|
enabled: LoginHelper.homeserverReachable && !customHomeserver.visible || customHomeserver.acceptableInput
|
||||||
|
onTriggered: {
|
||||||
|
// TODO
|
||||||
|
console.log("register todo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onHomeserverChanged: {
|
||||||
|
LoginHelper.testHomeserver("@user:" + homeserver)
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
Component.onCompleted: Controller.testHomeserver(homeserver)
|
||||||
|
|
||||||
|
QQC2.ComboBox {
|
||||||
|
id: serverCombo
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Homeserver:")
|
||||||
|
model: ["matrix.org", "kde.org", "tchncs.de", i18n("Other...")]
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.TextField {
|
||||||
|
id: customHomeserver
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Url:")
|
||||||
|
visible: serverCombo.currentIndex === 3
|
||||||
|
onTextChanged: {
|
||||||
|
Controller.testHomeserver(text)
|
||||||
|
}
|
||||||
|
validator: RegularExpressionValidator {
|
||||||
|
regularExpression: /([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9]+(:[0-9]+)?/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
id: continueButton
|
||||||
|
text: i18nc("@action:button", "Continue")
|
||||||
|
action: root.action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
imports/NeoChat/Component/Login/Loading.qml
Normal file
27
imports/NeoChat/Component/Login/Loading.qml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component 1.0
|
||||||
|
|
||||||
|
Kirigami.PlaceholderMessage {
|
||||||
|
property var showContinueButton: false
|
||||||
|
property var showBackButton: false
|
||||||
|
property string title: i18n("Loading")
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
text: i18n("Please wait. This might take a little while.")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.BusyIndicator {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
65
imports/NeoChat/Component/Login/Login.qml
Normal file
65
imports/NeoChat/Component/Login/Login.qml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component 1.0
|
||||||
|
|
||||||
|
LoginStep {
|
||||||
|
id: login
|
||||||
|
|
||||||
|
showContinueButton: true
|
||||||
|
showBackButton: false
|
||||||
|
|
||||||
|
title: i18nc("@title", "Login")
|
||||||
|
message: i18n("Enter your Matrix ID")
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
LoginHelper.matrixId = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
QQC2.TextField {
|
||||||
|
id: matrixIdField
|
||||||
|
Kirigami.FormData.label: i18n("Matrix ID:")
|
||||||
|
placeholderText: "@user:matrix.org"
|
||||||
|
onTextChanged: {
|
||||||
|
if(acceptableInput) {
|
||||||
|
LoginHelper.matrixId = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
matrixIdField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onReturnPressed: {
|
||||||
|
login.action.trigger()
|
||||||
|
}
|
||||||
|
|
||||||
|
validator: RegularExpressionValidator {
|
||||||
|
regularExpression: /^\@?[a-zA-Z0-9\._=\-/]+\:[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*\.[a-zA-Z]+(:[0-9]+)?$/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action: Kirigami.Action {
|
||||||
|
text: LoginHelper.testing && matrixIdField.acceptableInput ? i18n("Loading") : i18nc("@action:button", "Continue")
|
||||||
|
onTriggered: {
|
||||||
|
if (LoginHelper.supportsSso && LoginHelper.supportsPassword) {
|
||||||
|
processed("qrc:/imports/NeoChat/Component/Login/LoginMethod.qml");
|
||||||
|
} else if (LoginHelper.supportsPassword) {
|
||||||
|
processed("qrc:/imports/NeoChat/Component/Login/Password.qml");
|
||||||
|
} else {
|
||||||
|
processed("qrc:/imports/NeoChat/Component/Login/Sso.qml");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enabled: LoginHelper.homeserverReachable
|
||||||
|
}
|
||||||
|
}
|
||||||
32
imports/NeoChat/Component/Login/LoginMethod.qml
Normal file
32
imports/NeoChat/Component/Login/LoginMethod.qml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as Controls
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component.Login 1.0
|
||||||
|
|
||||||
|
LoginStep {
|
||||||
|
id: loginMethod
|
||||||
|
|
||||||
|
title: i18n("Login Methods")
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
Controls.Button {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: i18n("Login with password")
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||||
|
onClicked: processed("qrc:/imports/NeoChat/Component/Login/Password.qml")
|
||||||
|
}
|
||||||
|
|
||||||
|
Controls.Button {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: i18n("Login with single sign-on")
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||||
|
onClicked: processed("qrc:/imports/NeoChat/Component/Login/Sso.qml")
|
||||||
|
}
|
||||||
|
}
|
||||||
31
imports/NeoChat/Component/Login/LoginRegister.qml
Normal file
31
imports/NeoChat/Component/Login/LoginRegister.qml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as Controls
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component.Login 1.0
|
||||||
|
|
||||||
|
LoginStep {
|
||||||
|
id: loginRegister
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
Controls.Button {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: i18n("Login")
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||||
|
onClicked: processed("qrc:/imports/NeoChat/Component/Login/Login.qml")
|
||||||
|
}
|
||||||
|
|
||||||
|
Controls.Button {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: i18n("Register")
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 12
|
||||||
|
onClicked: processed("qrc:/imports/NeoChat/Component/Login/Homeserver.qml")
|
||||||
|
}
|
||||||
|
}
|
||||||
27
imports/NeoChat/Component/Login/LoginStep.qml
Normal file
27
imports/NeoChat/Component/Login/LoginStep.qml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.14
|
||||||
|
import QtQuick.Controls 2.14
|
||||||
|
import QtQuick.Layouts 1.14
|
||||||
|
|
||||||
|
/// Step for the login/registration flow
|
||||||
|
ColumnLayout {
|
||||||
|
|
||||||
|
property string title: i18n("Welcome")
|
||||||
|
property string message: i18n("Welcome")
|
||||||
|
property bool showContinueButton: false
|
||||||
|
property bool showBackButton: false
|
||||||
|
property bool acceptable: false
|
||||||
|
property string previousUrl: ""
|
||||||
|
|
||||||
|
/// Process this module, this is called by the continue button.
|
||||||
|
/// Should call \sa processed when it finish successfully.
|
||||||
|
property Action action: null
|
||||||
|
|
||||||
|
/// Called when switching to the next step.
|
||||||
|
signal processed(url nextUrl)
|
||||||
|
|
||||||
|
signal showMessage(string message)
|
||||||
|
|
||||||
|
}
|
||||||
52
imports/NeoChat/Component/Login/Password.qml
Normal file
52
imports/NeoChat/Component/Login/Password.qml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component 1.0
|
||||||
|
|
||||||
|
LoginStep {
|
||||||
|
id: password
|
||||||
|
|
||||||
|
title: i18nc("@title", "Password")
|
||||||
|
message: i18n("Enter your password")
|
||||||
|
showContinueButton: true
|
||||||
|
showBackButton: true
|
||||||
|
previousUrl: LoginHelper.isLoggingIn ? "" : LoginHelper.supportsSso ? "qrc:/imports/NeoChat/Component/Login/LoginMethod.qml" : "qrc:/imports/NeoChat/Component/Login/Login.qml"
|
||||||
|
|
||||||
|
action: Kirigami.Action {
|
||||||
|
text: i18nc("@action:button", "Login")
|
||||||
|
enabled: passwordField.text.length > 0 && !LoginHelper.isLoggingIn
|
||||||
|
onTriggered: {
|
||||||
|
LoginHelper.login();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: LoginHelper
|
||||||
|
function onConnected() {
|
||||||
|
processed("qrc:/imports/NeoChat/Component/Login/Loading.qml")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
Kirigami.PasswordField {
|
||||||
|
id: passwordField
|
||||||
|
onTextChanged: LoginHelper.password = text
|
||||||
|
enabled: !LoginHelper.isLoggingIn
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
passwordField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onReturnPressed: {
|
||||||
|
password.action.trigger()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
imports/NeoChat/Component/Login/Sso.qml
Normal file
40
imports/NeoChat/Component/Login/Sso.qml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.12 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component 1.0
|
||||||
|
|
||||||
|
LoginStep {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: i18nc("@title", "Login")
|
||||||
|
message: i18n("Login with single sign-on")
|
||||||
|
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
Connections {
|
||||||
|
target: LoginHelper
|
||||||
|
function onSsoUrlChanged() {
|
||||||
|
Qt.openUrlExternally(LoginHelper.ssoUrl)
|
||||||
|
}
|
||||||
|
function onConnected() {
|
||||||
|
processed("qrc:/imports/NeoChat/Component/Login/Loading.qml")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
text: i18n("Login")
|
||||||
|
onClicked: {
|
||||||
|
LoginHelper.loginWithSso()
|
||||||
|
root.showMessage(i18n("Complete the authentication steps in your browser"))
|
||||||
|
}
|
||||||
|
Component.onCompleted: forceActiveFocus()
|
||||||
|
Keys.onReturnPressed: clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
imports/NeoChat/Component/Login/qmldir
Normal file
7
imports/NeoChat/Component/Login/qmldir
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module NeoChat.Component.Login
|
||||||
|
Login 1.0 Login.qml
|
||||||
|
Password 1.0 Password.qml
|
||||||
|
LoginRegister 1.0 LoginRegister.qml
|
||||||
|
Loading 1.0 Loading.qml
|
||||||
|
LoginMethod 1.0 LoginMethod.qml
|
||||||
|
LoginStep 1.0 LoginStep.qml
|
||||||
81
imports/NeoChat/Component/QuickSwitcher.qml
Normal file
81
imports/NeoChat/Component/QuickSwitcher.qml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Layouts 1.10
|
||||||
|
import QtQuick.Controls 2.12 as QQC2
|
||||||
|
import org.kde.kirigami 2.14 as Kirigami
|
||||||
|
import org.kde.kitemmodels 1.0
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
QQC2.Popup {
|
||||||
|
id: _popup
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (!visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
quickSearch.forceActiveFocus()
|
||||||
|
quickSearch.text = ""
|
||||||
|
}
|
||||||
|
anchors.centerIn: QQC2.Overlay.overlay
|
||||||
|
background: Kirigami.Card {}
|
||||||
|
height: 2 * Math.round(implicitHeight / 2)
|
||||||
|
padding: Kirigami.Units.largeSpacing * 2
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: Kirigami.Units.largeSpacing * 2
|
||||||
|
|
||||||
|
Kirigami.SearchField {
|
||||||
|
id: quickSearch
|
||||||
|
|
||||||
|
// TODO: get this broken property removed/disabled by default in Kirigami,
|
||||||
|
// we used to be able to expect that the text field wouldn't attempt to
|
||||||
|
// perform a mini-DDOS attack using signals.
|
||||||
|
autoAccept: false
|
||||||
|
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 21 // 3 * 7 = 21, roughly 7 avatars on screen
|
||||||
|
Keys.onLeftPressed: cView.decrementCurrentIndex()
|
||||||
|
Keys.onRightPressed: cView.incrementCurrentIndex()
|
||||||
|
onAccepted: {
|
||||||
|
const item = cView.itemAtIndex(cView.currentIndex)
|
||||||
|
|
||||||
|
RoomManager.enterRoom(item.currentRoom)
|
||||||
|
|
||||||
|
_popup.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListView {
|
||||||
|
id: cView
|
||||||
|
|
||||||
|
orientation: Qt.Horizontal
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
model: SortFilterRoomListModel {
|
||||||
|
id: sortFilterRoomListModel
|
||||||
|
sourceModel: RoomListModel {
|
||||||
|
id: roomListModel
|
||||||
|
connection: Controller.activeConnection
|
||||||
|
}
|
||||||
|
filterText: quickSearch.text
|
||||||
|
roomSortOrder: SortFilterRoomListModel.LastActivity
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
delegate: Kirigami.Avatar {
|
||||||
|
id: del
|
||||||
|
|
||||||
|
implicitHeight: Kirigami.Units.gridUnit * 3
|
||||||
|
implicitWidth: Kirigami.Units.gridUnit * 3
|
||||||
|
|
||||||
|
required property string avatar
|
||||||
|
required property var currentRoom
|
||||||
|
|
||||||
|
source: avatar != "" ? "image://mxc/" + avatar : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modal: true
|
||||||
|
focus: true
|
||||||
|
}
|
||||||
39
imports/NeoChat/Component/ShimmerGradient.qml
Normal file
39
imports/NeoChat/Component/ShimmerGradient.qml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||||
|
|
||||||
|
// Not to be confused with the Shimmer project.
|
||||||
|
// I like their gradiented GTK themes though.
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
Gradient {
|
||||||
|
id: gradient
|
||||||
|
|
||||||
|
orientation: Gradient.Horizontal
|
||||||
|
|
||||||
|
property color color: Kirigami.Theme.textColor
|
||||||
|
property color translucent: Qt.rgba(color.r, color.g, color.b, 0.2)
|
||||||
|
property color bright: Qt.rgba(color.r, color.g, color.b, 0.3)
|
||||||
|
property real pos: 0.5
|
||||||
|
property real offset: 0.6
|
||||||
|
|
||||||
|
property SequentialAnimation ani: SequentialAnimation {
|
||||||
|
running: true
|
||||||
|
loops: Animation.Infinite
|
||||||
|
NumberAnimation {
|
||||||
|
from: -2.0
|
||||||
|
to: 2.0
|
||||||
|
duration: 700
|
||||||
|
target: gradient
|
||||||
|
properties: "pos"
|
||||||
|
}
|
||||||
|
PauseAnimation {
|
||||||
|
duration: 300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GradientStop { position: gradient.pos-gradient.offset; color: gradient.translucent }
|
||||||
|
GradientStop { position: gradient.pos; color: gradient.bright }
|
||||||
|
GradientStop { position: gradient.pos+gradient.offset; color: gradient.translucent }
|
||||||
|
}
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
import QtQuick 2.15
|
||||||
*/
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick 2.12
|
import QtQuick.Layouts 1.15
|
||||||
import QtQuick.Controls 2.12
|
import QtGraphicalEffects 1.15
|
||||||
import QtQuick.Layouts 1.12
|
import Qt.labs.platform 1.1 as Platform
|
||||||
import QtGraphicalEffects 1.0
|
import QtMultimedia 5.15
|
||||||
import Qt.labs.platform 1.0 as Platform
|
|
||||||
import QtMultimedia 5.12
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
import NeoChat.Setting 1.0
|
|
||||||
|
|
||||||
import NeoChat.Component 1.0
|
import NeoChat.Component 1.0
|
||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
import NeoChat.Menu.Timeline 1.0
|
import NeoChat.Menu.Timeline 1.0
|
||||||
@@ -29,27 +26,6 @@ Control {
|
|||||||
autoLoad: false
|
autoLoad: false
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.Action {
|
|
||||||
id: saveFileAction
|
|
||||||
onTriggered: {
|
|
||||||
let contextMenu = fileDelegateContextMenu.createObject(root, {'room': currentRoom, 'author': author});
|
|
||||||
contextMenu.viewSource.connect(function() {
|
|
||||||
messagerSourceSheet.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
|
|
||||||
})
|
|
||||||
contextMenu.downloadAndOpen.connect(downloadAndOpen)
|
|
||||||
contextMenu.saveFileAs.connect(saveFileAs)
|
|
||||||
contextMenu.reply.connect(function() {
|
|
||||||
roomPanelInput.replyModel = Object.assign({}, model)
|
|
||||||
roomPanelInput.isReply = true
|
|
||||||
roomPanelInput.focus()
|
|
||||||
})
|
|
||||||
contextMenu.redact.connect(function() {
|
|
||||||
currentRoom.redactEvent(eventId)
|
|
||||||
})
|
|
||||||
contextMenu.popup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
contentItem: ColumnLayout {
|
||||||
RowLayout {
|
RowLayout {
|
||||||
ToolButton {
|
ToolButton {
|
||||||
@@ -69,6 +45,8 @@ Control {
|
|||||||
}
|
}
|
||||||
RowLayout {
|
RowLayout {
|
||||||
visible: audio.hasAudio
|
visible: audio.hasAudio
|
||||||
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
// Server doesn't support seeking, so use ProgressBar instead of Slider :(
|
// Server doesn't support seeking, so use ProgressBar instead of Slider :(
|
||||||
ProgressBar {
|
ProgressBar {
|
||||||
from: 0
|
from: 0
|
||||||
@@ -77,72 +55,8 @@ Control {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: humanSize(audio.position) + "/" + humanSize(audio.duration)
|
text: Controller.formatDuration(audio.position) + "/" + Controller.formatDuration(audio.duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
background: AutoMouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
id: messageMouseArea
|
|
||||||
|
|
||||||
onSecondaryClicked: saveFileAction.trigger()
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: messagerSourceSheet
|
|
||||||
|
|
||||||
MessageSourceSheet {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: openFolderDialog
|
|
||||||
|
|
||||||
OpenFolderDialog {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: fileDelegateContextMenu
|
|
||||||
|
|
||||||
FileDelegateContextMenu {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveFileAs() {
|
|
||||||
var folderDialog = openFolderDialog.createObject(ApplicationWindow.overlay)
|
|
||||||
|
|
||||||
folderDialog.chosen.connect(function(path) {
|
|
||||||
if (!path) return
|
|
||||||
|
|
||||||
currentRoom.downloadFile(eventId, path + "/" + currentRoom.fileNameToDownload(eventId))
|
|
||||||
})
|
|
||||||
|
|
||||||
folderDialog.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadAndOpen() {
|
|
||||||
if (downloaded) {
|
|
||||||
openSavedFile()
|
|
||||||
} else {
|
|
||||||
openOnFinished = true
|
|
||||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openSavedFile() {
|
|
||||||
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
|
||||||
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function humanSize(duration) {
|
|
||||||
if (!duration) {
|
|
||||||
return i18n("Unknown duration")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duration > 1000 * 60 * 60) {
|
|
||||||
return new Date(duration).toLocaleTimeString(Qt.locale(), "hh:mm:ss")
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Date(duration).toLocaleTimeString(Qt.locale(), "mm:ss")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
18
imports/NeoChat/Component/Timeline/EncryptedDelegate.qml
Normal file
18
imports/NeoChat/Component/Timeline/EncryptedDelegate.qml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
TextEdit {
|
||||||
|
text: i18n("This message is encrypted and the sender has not shared the key with this device.")
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize
|
||||||
|
selectByMouse: !Kirigami.Settings.isMobile
|
||||||
|
readOnly: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
textFormat: Text.RichText
|
||||||
|
}
|
||||||
@@ -1,142 +1,126 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
import QtQuick 2.15
|
||||||
*/
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
import QtQuick 2.12
|
import QtQuick.Layouts 1.15
|
||||||
import QtQuick.Controls 2.12
|
import QtGraphicalEffects 1.15
|
||||||
import QtQuick.Layouts 1.12
|
import Qt.labs.platform 1.1
|
||||||
import QtQuick.Controls.Material 2.12
|
|
||||||
import QtGraphicalEffects 1.0
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
import Qt.labs.platform 1.0
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
import NeoChat.Setting 1.0
|
|
||||||
|
|
||||||
import NeoChat.Component 1.0
|
import NeoChat.Component 1.0
|
||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
import NeoChat.Menu.Timeline 1.0
|
import NeoChat.Menu.Timeline 1.0
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
id: root
|
||||||
property bool openOnFinished: false
|
property bool openOnFinished: false
|
||||||
readonly property bool downloaded: progressInfo && progressInfo.completed
|
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||||
|
|
||||||
id: root
|
Layout.margins: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
spacing: 4
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
onDownloadedChanged: if (downloaded && openOnFinished) openSavedFile()
|
states: [
|
||||||
|
State {
|
||||||
|
name: "downloaded"
|
||||||
|
when: progressInfo.completed
|
||||||
|
|
||||||
z: -5
|
PropertyChanges {
|
||||||
|
target: downloadButton
|
||||||
|
|
||||||
Control {
|
icon.name: "document-open"
|
||||||
contentItem: RowLayout {
|
|
||||||
ToolButton {
|
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to open its downloaded file with an appropriate application", "Open File")
|
||||||
icon.name: progressInfo.completed ? "document-open" : "document-save"
|
|
||||||
onClicked: progressInfo.completed ? openSavedFile() : saveFileAs()
|
onClicked: openSavedFile()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "downloading"
|
||||||
|
when: progressInfo.active
|
||||||
|
|
||||||
ColumnLayout {
|
PropertyChanges {
|
||||||
Kirigami.Heading {
|
target: sizeLabel
|
||||||
Layout.fillWidth: true
|
text: i18nc("file download progress", "%1 / %2", Controller.formatByteSize(progressInfo.progress), Controller.formatByteSize(progressInfo.total))
|
||||||
level: 4
|
}
|
||||||
text: display
|
PropertyChanges {
|
||||||
wrapMode: Label.Wrap
|
target: downloadButton
|
||||||
}
|
icon.name: "media-playback-stop"
|
||||||
|
|
||||||
Label {
|
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; stops downloading the message's file", "Stop Download")
|
||||||
Layout.fillWidth: true
|
onClicked: currentRoom.cancelFileTransfer(eventId)
|
||||||
text: !progressInfo.completed && progressInfo.active ? (humanSize(progressInfo.progress) + "/" + humanSize(progressInfo.total)) : humanSize(content.info ? content.info.size : 0)
|
}
|
||||||
color: Kirigami.Theme.disabledTextColor
|
},
|
||||||
wrapMode: Label.Wrap
|
State {
|
||||||
}
|
name: "raw"
|
||||||
|
when: true
|
||||||
|
|
||||||
|
PropertyChanges {
|
||||||
|
target: downloadButton
|
||||||
|
|
||||||
|
onClicked: root.saveFileAs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
|
||||||
background: Item {
|
Kirigami.Icon {
|
||||||
MouseArea {
|
id: ikon
|
||||||
id: messageMouseArea
|
source: model.fileMimetypeIcon
|
||||||
anchors.fill: parent
|
fallback: "unknown"
|
||||||
acceptedButtons: Qt.RightButton
|
}
|
||||||
onClicked: {
|
ColumnLayout {
|
||||||
var contextMenu = fileDelegateContextMenu.createObject(root, {'room': currentRoom, 'author': author});
|
Layout.alignment: Qt.AlignVCenter
|
||||||
contextMenu.viewSource.connect(function() {
|
Layout.fillWidth: true
|
||||||
messageSourceSheet.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
|
|
||||||
})
|
|
||||||
contextMenu.downloadAndOpen.connect(downloadAndOpen)
|
|
||||||
contextMenu.saveFileAs.connect(saveFileAs)
|
|
||||||
contextMenu.reply.connect(function() {
|
|
||||||
roomPanelInput.replyModel = Object.assign({}, model)
|
|
||||||
roomPanelInput.isReply = true
|
|
||||||
roomPanelInput.focus()
|
|
||||||
})
|
|
||||||
contextMenu.redact.connect(function() {
|
|
||||||
currentRoom.redactEvent(eventId)
|
|
||||||
})
|
|
||||||
contextMenu.popup()
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
spacing: 0
|
||||||
id: messageSourceSheet
|
|
||||||
|
|
||||||
MessageSourceSheet {}
|
QQC2.Label {
|
||||||
}
|
text: model.display
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
|
||||||
Component {
|
Layout.fillWidth: true
|
||||||
id: fileDialog
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
id: sizeLabel
|
||||||
|
|
||||||
FileDialog {
|
text: Controller.formatByteSize(content.info ? content.info.size : 0)
|
||||||
fileMode: FileDialog.SaveFile
|
opacity: 0.7
|
||||||
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
|
||||||
onAccepted: {
|
|
||||||
currentRoom.downloadFile(eventId, file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Layout.fillWidth: true
|
||||||
id: fileDelegateContextMenu
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FileDelegateContextMenu {}
|
QQC2.Button {
|
||||||
}
|
id: downloadButton
|
||||||
|
icon.name: "download"
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to download its file", "Download")
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: fileDialog
|
||||||
|
|
||||||
|
FileDialog {
|
||||||
|
fileMode: FileDialog.SaveFile
|
||||||
|
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||||
|
onAccepted: {
|
||||||
|
currentRoom.downloadFile(eventId, file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveFileAs() {
|
function saveFileAs() {
|
||||||
var dialog = fileDialog.createObject(ApplicationWindow.overlay)
|
var dialog = fileDialog.createObject(QQC2.ApplicationWindow.overlay)
|
||||||
dialog.open()
|
dialog.open()
|
||||||
|
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadAndOpen()
|
function openSavedFile() {
|
||||||
{
|
|
||||||
if (downloaded) openSavedFile()
|
|
||||||
else
|
|
||||||
{
|
|
||||||
openOnFinished = true
|
|
||||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openSavedFile()
|
|
||||||
{
|
|
||||||
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
||||||
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function humanSize(bytes)
|
|
||||||
{
|
|
||||||
if (!bytes)
|
|
||||||
return i18nc("Unknown attachment size", "Unknown")
|
|
||||||
if (bytes < 4000)
|
|
||||||
return i18np("%1 byte", "%1 bytes", bytes)
|
|
||||||
bytes = Math.round(bytes / 100) / 10
|
|
||||||
if (bytes < 2000)
|
|
||||||
return i18nc("KB as in kilobytes", "%1 KB", bytes)
|
|
||||||
bytes = Math.round(bytes / 100) / 10
|
|
||||||
if (bytes < 2000)
|
|
||||||
return i18nc("MB as in megabytes", "%1 MB", bytes)
|
|
||||||
return i18nc("GB as in gigabytes", "%1 GB", Math.round(bytes / 100) / 10)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
import QtQuick 2.15
|
||||||
*/
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick 2.12
|
import QtQuick.Layouts 1.15
|
||||||
import QtQuick.Controls 2.12
|
import QtGraphicalEffects 1.15
|
||||||
import QtQuick.Layouts 1.12
|
import Qt.labs.platform 1.1
|
||||||
import QtGraphicalEffects 1.0
|
|
||||||
import Qt.labs.platform 1.0 as Platform
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
import NeoChat.Setting 1.0
|
|
||||||
|
|
||||||
import NeoChat.Component 1.0
|
import NeoChat.Component 1.0
|
||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
import NeoChat.Menu.Timeline 1.0
|
import NeoChat.Menu.Timeline 1.0
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
|
id: img
|
||||||
|
|
||||||
|
property var content: model.content
|
||||||
readonly property bool isAnimated: contentType === "image/gif"
|
readonly property bool isAnimated: contentType === "image/gif"
|
||||||
|
|
||||||
property bool openOnFinished: false
|
property bool openOnFinished: false
|
||||||
@@ -26,44 +25,24 @@ Image {
|
|||||||
// readonly property var info: isThumbnail ? content.info.thumbnail_info : content.info
|
// readonly property var info: isThumbnail ? content.info.thumbnail_info : content.info
|
||||||
readonly property var info: content.info
|
readonly property var info: content.info
|
||||||
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
|
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
|
||||||
|
property bool readonly: false
|
||||||
id: img
|
|
||||||
|
|
||||||
source: "image://mxc/" + mediaId
|
source: "image://mxc/" + mediaId
|
||||||
|
|
||||||
sourceSize.width: info.w
|
Image {
|
||||||
sourceSize.height: info.h
|
anchors.fill: parent
|
||||||
|
source: content.info["xyz.amorgan.blurhash"] ? ("image://blurhash/" + content.info["xyz.amorgan.blurhash"]) : ""
|
||||||
|
visible: parent.status !== Image.Ready
|
||||||
|
}
|
||||||
|
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
Control {
|
ToolTip.text: display
|
||||||
anchors.bottom: parent.bottom
|
ToolTip.visible: hoverHandler.hovered
|
||||||
anchors.bottomMargin: 8
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
|
|
||||||
horizontalPadding: 8
|
HoverHandler {
|
||||||
verticalPadding: 4
|
id: hoverHandler
|
||||||
|
enabled: img.readonly
|
||||||
contentItem: RowLayout {
|
|
||||||
Label {
|
|
||||||
text: Qt.formatTime(time)
|
|
||||||
color: "white"
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: author.displayName
|
|
||||||
color: "white"
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
radius: 2
|
|
||||||
color: "black"
|
|
||||||
opacity: 0.3
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -84,74 +63,22 @@ Image {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
function saveFileAs() {
|
||||||
id: messageMouseArea
|
var dialog = fileDialog.createObject(ApplicationWindow.overlay)
|
||||||
|
dialog.open()
|
||||||
anchors.fill: parent
|
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
||||||
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if(mouse.button === Qt.LeftButton) {
|
|
||||||
fullScreenImage.createObject(parent, {"filename": eventId, "localPath": currentRoom.urlToDownload(eventId)}).showFullScreen()
|
|
||||||
} else {
|
|
||||||
openContextMenu()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openContextMenu() {
|
|
||||||
var contextMenu = imageDelegateContextMenu.createObject(root, {'room': currentRoom, 'author': author});
|
|
||||||
contextMenu.viewSource.connect(function() {
|
|
||||||
messageSourceSheet.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
|
|
||||||
})
|
|
||||||
contextMenu.downloadAndOpen.connect(downloadAndOpen)
|
|
||||||
contextMenu.saveFileAs.connect(saveFileAs)
|
|
||||||
contextMenu.reply.connect(function() {
|
|
||||||
roomPanelInput.replyModel = Object.assign({}, model)
|
|
||||||
roomPanelInput.isReply = true
|
|
||||||
roomPanelInput.focus()
|
|
||||||
})
|
|
||||||
contextMenu.redact.connect(function() {
|
|
||||||
currentRoom.redactEvent(eventId)
|
|
||||||
})
|
|
||||||
contextMenu.popup()
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: messageSourceSheet
|
|
||||||
|
|
||||||
MessageSourceSheet {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: openFolderDialog
|
|
||||||
|
|
||||||
OpenFolderDialog {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: imageDelegateContextMenu
|
|
||||||
|
|
||||||
FileDelegateContextMenu {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: fullScreenImage
|
|
||||||
|
|
||||||
FullScreenImage {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveFileAs() {
|
Component {
|
||||||
var folderDialog = openFolderDialog.createObject(ApplicationWindow.overlay)
|
id: fileDialog
|
||||||
|
|
||||||
folderDialog.chosen.connect(function(path) {
|
FileDialog {
|
||||||
if (!path) return
|
fileMode: FileDialog.SaveFile
|
||||||
|
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||||
currentRoom.downloadFile(eventId, path + "/" + currentRoom.fileNameToDownload(eventId))
|
onAccepted: {
|
||||||
})
|
currentRoom.downloadFile(eventId, file)
|
||||||
|
}
|
||||||
folderDialog.open()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadAndOpen()
|
function downloadAndOpen()
|
||||||
@@ -160,7 +87,7 @@ Image {
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
openOnFinished = true
|
openOnFinished = true
|
||||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
import QtGraphicalEffects 1.12
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
import NeoChat.Setting 1.0
|
|
||||||
import NeoChat.Component 1.0
|
|
||||||
import NeoChat.Dialog 1.0
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
default property alias innerObject : column.children
|
|
||||||
|
|
||||||
readonly property bool sentByMe: author.isLocalUser
|
|
||||||
readonly property bool darkBackground: !sentByMe
|
|
||||||
readonly property bool replyVisible: reply ?? false
|
|
||||||
readonly property bool failed: marks == EventStatus.SendingFailed
|
|
||||||
readonly property color authorColor: eventType == "notice" ? Kirigami.Theme.activeTextColor : author.color
|
|
||||||
readonly property color replyAuthorColor: replyVisible ? reply.author.color : Kirigami.Theme.focusColor
|
|
||||||
|
|
||||||
property alias mouseArea: controlContainer.children
|
|
||||||
property bool isEmote: false
|
|
||||||
|
|
||||||
signal saveFileAs()
|
|
||||||
signal openExternally()
|
|
||||||
signal replyClicked(string eventID)
|
|
||||||
signal replyToMessageClicked(var replyUser, string replyContent, string eventID)
|
|
||||||
|
|
||||||
id: root
|
|
||||||
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
|
||||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
|
||||||
Layout.bottomMargin: 0
|
|
||||||
Layout.topMargin: showAuthor ? Kirigami.Units.smallSpacing : 0
|
|
||||||
|
|
||||||
Kirigami.Avatar {
|
|
||||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 2
|
|
||||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 2
|
|
||||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 2
|
|
||||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 2
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
|
|
||||||
visible: showAuthor && Config.showAvatarInTimeline
|
|
||||||
name: author.displayName
|
|
||||||
source: author.avatarMediaId ? "image://mxc/" + author.avatarMediaId : ""
|
|
||||||
color: author.color
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: userDetailDialog
|
|
||||||
|
|
||||||
UserDetailDialog {}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {"room": currentRoom, "user": author.object, "displayName": author.displayName, "avatarMediaId": author.avatarMediaId, "avatarUrl": author.avatarUrl}).open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 2
|
|
||||||
Layout.preferredHeight: 1
|
|
||||||
visible: !showAuthor && Config.showAvatarInTimeline
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QQC2.Control {
|
|
||||||
id: controlContainer
|
|
||||||
Layout.fillWidth: true
|
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
hoverEnabled: true
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
id: column
|
|
||||||
spacing: showAuthor ? Kirigami.Units.smallSpacing : 0
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: rowLayout
|
|
||||||
Layout.fillWidth: true
|
|
||||||
QQC2.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
topInset: 0
|
|
||||||
|
|
||||||
visible: showAuthor && !isEmote
|
|
||||||
|
|
||||||
text: author.displayName
|
|
||||||
font.bold: true
|
|
||||||
color: author.color
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
}
|
|
||||||
QQC2.Label {
|
|
||||||
visible: showAuthor && !isEmote
|
|
||||||
text: time.toLocaleTimeString(Locale.ShortFormat)
|
|
||||||
color: Kirigami.Theme.disabledTextColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loader {
|
|
||||||
id: replyLoader
|
|
||||||
source: 'qrc:imports/NeoChat/Component/Timeline/ReplyComponent.qml'
|
|
||||||
active: replyVisible
|
|
||||||
}
|
|
||||||
Connections {
|
|
||||||
target: replyLoader.item
|
|
||||||
onClicked: replyClicked(reply.eventId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RowLayout {
|
|
||||||
z: 2
|
|
||||||
anchors.bottom: controlContainer.top
|
|
||||||
anchors.bottomMargin: -Kirigami.Units.gridUnit
|
|
||||||
anchors.right: controlContainer.right
|
|
||||||
spacing: 0
|
|
||||||
QQC2.Button {
|
|
||||||
QQC2.ToolTip.text: i18n("React")
|
|
||||||
QQC2.ToolTip.visible: hovered
|
|
||||||
visible: controlContainer.hovered
|
|
||||||
icon.name: "preferences-desktop-emoticons"
|
|
||||||
onClicked: emojiDialog.open();
|
|
||||||
EmojiDialog {
|
|
||||||
id: emojiDialog
|
|
||||||
onReact: currentRoom.toggleReaction(eventId, emoji)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.Button {
|
|
||||||
QQC2.ToolTip.text: i18n("Reply")
|
|
||||||
QQC2.ToolTip.visible: hovered
|
|
||||||
visible: controlContainer.hovered
|
|
||||||
icon.name: "mail-replied-symbolic"
|
|
||||||
onClicked: replyToMessage(author, message, eventId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
background: Rectangle {
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
|
||||||
color: !model.isHighlighted ? Kirigami.Theme.backgroundColor : Kirigami.Theme.positiveBackgroundColor
|
|
||||||
opacity: controlContainer.hovered || model.isHighlighted ? 1 : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +1,65 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
import QtQuick 2.15
|
||||||
*/
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick 2.12
|
import QtQuick.Layouts 1.15
|
||||||
import QtQuick.Controls 2.12
|
|
||||||
import QtQuick.Layouts 1.12
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
|
|
||||||
Flow {
|
Flow {
|
||||||
visible: (reaction && reaction.length > 0) ?? false
|
|
||||||
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: reaction
|
model: reaction ?? null
|
||||||
|
|
||||||
delegate: AbstractButton {
|
delegate: AbstractButton {
|
||||||
width: Math.max(implicitWidth, height)
|
width: Math.max(implicitWidth, height)
|
||||||
|
|
||||||
contentItem: Label {
|
contentItem: Label {
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: modelData.reaction + (modelData.count > 1 ? " " + modelData.count : "")
|
text: modelData.reaction + " " + modelData.count
|
||||||
}
|
}
|
||||||
|
|
||||||
padding: Kirigami.Units.smallSpacing
|
padding: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
background: Rectangle {
|
background: Kirigami.ShadowedRectangle {
|
||||||
radius: height / 2
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
|
||||||
color: checked ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
|
color: checked ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
|
||||||
|
radius: height / 2
|
||||||
|
shadow.size: Kirigami.Units.smallSpacing
|
||||||
|
shadow.color: !model.isHighlighted ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
||||||
|
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||||
|
border.width: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
checkable: true
|
checkable: true
|
||||||
|
|
||||||
checked: modelData.hasLocalUser
|
checked: modelData.hasLocalUser
|
||||||
|
|
||||||
onToggled: currentRoom.toggleReaction(eventId, modelData.reaction)
|
onToggled: currentRoom.toggleReaction(eventId, modelData.reaction)
|
||||||
|
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
ToolTip.text: {
|
ToolTip.text: {
|
||||||
var text = "";
|
var text = "";
|
||||||
|
|
||||||
for (var i = 0; i < modelData.authors.length; i++) {
|
for (var i = 0; i < modelData.authors.length && i < 3; i++) {
|
||||||
if (i === modelData.authors.length - 1 && i !== 0) {
|
if (i !== 0) {
|
||||||
text += i18nc("Seperate the usernames of users", " and ")
|
if (i < modelData.authors.length - 1) {
|
||||||
} else if (i !== 0) {
|
text += ", "
|
||||||
text += ", "
|
} else {
|
||||||
|
text += i18nc("Separate the usernames of users", " and ")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
text += modelData.authors[i].displayName
|
text += modelData.authors[i].displayName
|
||||||
}
|
}
|
||||||
|
if (modelData.authors.length > 3) {
|
||||||
|
text += i18ncp("%1 is the number of other users", " and %1 other", " and %1 others", modelData.authors.length - 3)
|
||||||
|
}
|
||||||
|
|
||||||
text = i18ncp("%1 is the users who reacted and %2 the emoji that was given", "%2 reacted with %3", "%2 reacted with %3", modelData.authors.length, text, modelData.reaction)
|
text = i18ncp("%2 is the users who reacted and %3 the emoji that was given", "%2 reacted with %3", "%2 reacted with %3", modelData.authors.length, text, modelData.reaction)
|
||||||
|
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,103 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
import QtQuick 2.15
|
||||||
*/
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
import QtQuick 2.12
|
import QtQuick.Layouts 1.15
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
|
||||||
import QtQuick.Layouts 1.12
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
import NeoChat.Component.Timeline 1.0
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component.Timeline 1.0
|
||||||
|
|
||||||
QQC2.AbstractButton {
|
MouseArea {
|
||||||
visible: replyVisible
|
id: replyButton
|
||||||
Component.onCompleted: parent.Layout.fillWidth = true
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: replyName.implicitHeight + (loader.item ? loader.item.height : 0) + Kirigami.Units.largeSpacing
|
||||||
|
implicitWidth: Math.min(bubbleMaxWidth, Math.max((loader.item ? loader.item.width + Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing : 0), replyName.implicitWidth)) + Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 3
|
||||||
|
Component.onCompleted: {
|
||||||
|
parent.Layout.fillWidth = true;
|
||||||
|
parent.Layout.preferredWidth = Qt.binding(function() { return implicitWidth; })
|
||||||
|
parent.Layout.maximumWidth = Qt.binding(function() { return bubbleMaxWidth + Kirigami.Units.largeSpacing * 2; })
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
id: replyLeftBorder
|
||||||
|
width: Kirigami.Units.smallSpacing
|
||||||
|
height: parent.height
|
||||||
|
x: Config.compactLayout ? Kirigami.Units.largeSpacing : 0
|
||||||
|
color: Kirigami.Theme.highlightColor
|
||||||
|
}
|
||||||
|
|
||||||
contentItem: RowLayout {
|
Kirigami.Avatar {
|
||||||
Layout.fillWidth: true
|
id: replyAvatar
|
||||||
|
anchors.left: replyLeftBorder.right
|
||||||
|
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
width: visible ? Kirigami.Units.gridUnit : 0
|
||||||
|
height: Kirigami.Units.gridUnit
|
||||||
|
sourceSize.width: width
|
||||||
|
sourceSize.height: height
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
visible: Config.showAvatarInTimeline
|
||||||
|
source: reply.author.avatarMediaId ? ("image://mxc/" + reply.author.avatarMediaId) : ""
|
||||||
|
name: reply.author.name || ""
|
||||||
|
color: reply.author.color
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
QQC2.Label {
|
||||||
Layout.preferredWidth: Kirigami.Units.smallSpacing
|
id: replyName
|
||||||
Layout.fillHeight: true
|
anchors {
|
||||||
|
left: replyAvatar.right
|
||||||
color: Kirigami.Theme.highlightColor
|
leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: Kirigami.Units.smallSpacing
|
||||||
}
|
}
|
||||||
|
text: currentRoom.htmlSafeMemberName(reply.author.id)
|
||||||
|
color: reply.author.color
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
Kirigami.Avatar {
|
Loader {
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit
|
id: loader
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit
|
anchors.top: replyName.bottom
|
||||||
Layout.alignment: Qt.AlignTop
|
sourceComponent: {
|
||||||
visible: Config.showAvatarInTimeline
|
switch (reply.type) {
|
||||||
source: replyVisible && reply.author.avatarMediaId ? "image://mxc/" + reply.author.avatarMediaId : ""
|
case "image":
|
||||||
name: replyVisible ? reply.author.displayName : "H"
|
case "sticker":
|
||||||
color: replyVisible ? reply.author.color : Kirigami.Theme.highlightColor
|
return imageComponent;
|
||||||
}
|
case "message":
|
||||||
|
return textComponent;
|
||||||
ColumnLayout {
|
// TODO support more types
|
||||||
id: replyLayout
|
default:
|
||||||
Layout.fillWidth: true
|
return textComponent;
|
||||||
|
|
||||||
QQC2.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: replyVisible ? reply.author.displayName : ""
|
|
||||||
color: replyVisible ? reply.author.color: null
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: textComponent
|
||||||
TextDelegate {
|
TextDelegate {
|
||||||
Layout.fillWidth: true
|
id: replyText
|
||||||
text: replyVisible ? ("<style>pre {white-space: pre-wrap} a{color: " + Kirigami.Theme.linkColor + ";} .user-pill{}</style>" + reply.display) : ""
|
textMessage: reply.display
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
wrapMode: Text.WordWrap
|
hasContextMenu: false
|
||||||
|
width: Math.min(implicitWidth, bubbleMaxWidth - Kirigami.Units.largeSpacing * 3)
|
||||||
|
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: imageComponent
|
||||||
|
Image {
|
||||||
|
readonly property var content: reply.content
|
||||||
|
readonly property bool isThumbnail: !(content.info.thumbnail_info == null || content.thumbnailMediaId == null)
|
||||||
|
// readonly property var info: isThumbnail ? content.info.thumbnail_info : content.info
|
||||||
|
readonly property var info: content.info
|
||||||
|
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
|
||||||
|
source: "image://mxc/" + mediaId
|
||||||
|
|
||||||
|
width: bubbleMaxWidth * 0.75 - Kirigami.Units.smallSpacing * 5 - replyAvatar.width
|
||||||
|
height: reply.content.info.h / reply.content.info.w * width
|
||||||
|
x: Kirigami.Units.smallSpacing * 3 + replyAvatar.width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
import QtQuick 2.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
Kirigami.Heading {
|
Kirigami.Heading {
|
||||||
level: 4
|
level: 4
|
||||||
text: section
|
text: model.showSection ? section : ""
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
topPadding: Kirigami.Units.largeSpacing * 2
|
topPadding: Kirigami.Units.largeSpacing * 2
|
||||||
bottomPadding: Kirigami.Units.smallSpacing
|
bottomPadding: Kirigami.Units.smallSpacing
|
||||||
|
|||||||
@@ -1,32 +1,26 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
import QtQuick 2.15
|
||||||
*/
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick 2.12
|
import QtQuick.Layouts 1.15
|
||||||
import QtQuick.Controls 2.12
|
|
||||||
import QtQuick.Layouts 1.12
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
import QtQuick.Controls.Material 2.12
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
|
|
||||||
import NeoChat.Component 1.0
|
import NeoChat.Component 1.0
|
||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
import NeoChat.Setting 1.0
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: row
|
id: row
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.minimumWidth: Kirigami.Units.iconSizes.medium
|
|
||||||
Layout.preferredHeight: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Avatar {
|
Kirigami.Avatar {
|
||||||
|
id: icon
|
||||||
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
||||||
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
name: author.displayName
|
name: author.displayName
|
||||||
source: author.avatarMediaId ? "image://mxc/" + author.avatarMediaId : ""
|
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||||
color: author.color
|
color: author.color
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
@@ -43,18 +37,11 @@ RowLayout {
|
|||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
text: author.displayName
|
|
||||||
color: Kirigami.Theme.disabledTextColor
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: icon.height
|
||||||
text: display
|
wrapMode: Text.WordWrap
|
||||||
color: Kirigami.Theme.disabledTextColor
|
textFormat: Text.RichText
|
||||||
font.weight: Font.Medium
|
text: "<style>a {text-decoration: none;}</style><a href=\"https://matrix.to/#/" + author.id + "\" style='color: " + author.color + "'>" + currentRoom.htmlSafeMemberName(author.id) + "</a> " + display
|
||||||
|
onLinkActivated: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author.object, "displayName": author.displayName, "avatarMediaId": author.avatarMediaId, "avatarUrl": author.avatarUrl}).open()
|
||||||
wrapMode: Label.Wrap
|
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,92 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2020 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2020 Black Hat <bhat@encom.eu.org>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.4 as Kirigami
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
TextEdit {
|
TextEdit {
|
||||||
id: contentLabel
|
id: contentLabel
|
||||||
|
|
||||||
readonly property var isEmoji: /^(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+$/
|
readonly property var isEmoji: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/
|
||||||
|
readonly property var hasSpoiler: /data-mx-spoiler/g
|
||||||
|
|
||||||
property bool isEmote: false
|
property bool isEmote: false
|
||||||
|
property string textMessage: model.display
|
||||||
|
property bool spoilerRevealed: !hasSpoiler.test(textMessage)
|
||||||
|
|
||||||
text: "<style>pre {white-space: pre-wrap} a{color: " + Kirigami.Theme.linkColor + ";} .user-pill{}</style>" + (isEmote ? "* <a href='https://matrix.to/#/" + author.id + "' style='color: " + author.color + "'>" + author.displayName + "</a> " : "") + display
|
property bool hasContextMenu: true
|
||||||
|
|
||||||
|
signal requestOpenMessageContext()
|
||||||
|
|
||||||
|
ListView.onReused: Qt.binding(() => !hasSpoiler.test(textMessage))
|
||||||
|
|
||||||
|
Layout.fillWidth: Config.compactLayout
|
||||||
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||||
|
|
||||||
|
text: "<style>
|
||||||
|
table {
|
||||||
|
width:100%;
|
||||||
|
border-width: 1px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
table th,
|
||||||
|
table td {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
white-space: pre-wrap
|
||||||
|
}
|
||||||
|
a{
|
||||||
|
color: " + Kirigami.Theme.linkColor + ";
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
" + (!spoilerRevealed ? "
|
||||||
|
[data-mx-spoiler] {
|
||||||
|
color: transparent;
|
||||||
|
background: " + Kirigami.Theme.textColor + ";
|
||||||
|
}
|
||||||
|
" : "") + "
|
||||||
|
</style>" + (isEmote ? "* <a href='https://matrix.to/#/" + author.id + "' style='color: " + author.color + "'>" + author.displayName + "</a> " : "") + textMessage + (isEdited ? (" <span style=\"color: " + Kirigami.Theme.disabledTextColor + "\">" + "<span style='font-size: " + Kirigami.Theme.defaultFont.pixelSize +"px'>" + i18n(" (edited)") + "</span>") : "")
|
||||||
|
|
||||||
color: Kirigami.Theme.textColor
|
color: Kirigami.Theme.textColor
|
||||||
font.pointSize: isEmoji.test(display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
|
font.pointSize: model.reply === undefined && isEmoji.test(model.display) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
|
||||||
selectByMouse: !Kirigami.Settings.isMobile
|
selectByMouse: !Kirigami.Settings.isMobile
|
||||||
readOnly: true
|
readOnly: true
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.Wrap
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
|
|
||||||
onLinkActivated: {
|
onLinkActivated: RoomManager.openResource(link)
|
||||||
if (link.startsWith("https://matrix.to/")) {
|
onHoveredLinkChanged: if (hoveredLink.length > 0) {
|
||||||
var result = link.replace(/\?.*/, "").match("https://matrix.to/#/(!.*:.*)/(\\$.*:.*)")
|
applicationWindow().hoverLinkIndicator.text = hoveredLink;
|
||||||
if (!result || result.length < 3) return
|
} else {
|
||||||
if (result[1] != currentRoom.id) return
|
applicationWindow().hoverLinkIndicator.text = "";
|
||||||
if (!result[2]) return
|
|
||||||
goToEvent(result[2])
|
|
||||||
} else {
|
|
||||||
Qt.openUrlExternally(link)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
HoverHandler {
|
||||||
anchors.fill: parent
|
cursorShape: (parent.hoveredLink || !spoilerRevealed) ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||||
acceptedButtons: Qt.NoButton
|
}
|
||||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
|
||||||
|
TapHandler {
|
||||||
|
enabled: !parent.hoveredLink && !spoilerRevealed
|
||||||
|
onTapped: spoilerRevealed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
onTapped: openMessageContext(model, parent.selectedText)
|
||||||
|
enabled: hasContextMenu
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onLongPressed: requestOpenMessageContext()
|
||||||
|
enabled: hasContextMenu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,252 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2020 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2020 Black Hat <bhat@encom.eu.org>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12 as Controls
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.4 as Kirigami
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtGraphicalEffects 1.12
|
||||||
|
|
||||||
Item {
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component 1.0
|
||||||
|
import NeoChat.Dialog 1.0
|
||||||
|
|
||||||
|
QQC2.ItemDelegate {
|
||||||
|
id: messageDelegate
|
||||||
default property alias innerObject : column.children
|
default property alias innerObject : column.children
|
||||||
|
// readonly property bool failed: marks == EventStatus.SendingFailed
|
||||||
|
property bool isLoaded
|
||||||
|
|
||||||
height: column.implicitHeight + (readMarker ? 2 * Kirigami.Units.smallSpacing : 0)
|
property bool isEmote: false
|
||||||
|
property bool cardBackground: true
|
||||||
|
|
||||||
ColumnLayout {
|
readonly property int bubbleMaxWidth: Config.compactLayout && !Config.showAvatarInTimeline ? width : (Config.compactLayout ? width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 4 : Math.min(width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 6, Kirigami.Units.gridUnit * 20))
|
||||||
id: column
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
SectionDelegate {
|
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && model.author.isLocalUser && !applicationWindow().wideScreen
|
||||||
Layout.maximumWidth: parent.width
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
visible: showSection
|
signal saveFileAs()
|
||||||
|
signal openExternally()
|
||||||
|
signal replyClicked(string eventID)
|
||||||
|
|
||||||
|
topPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
background: null
|
||||||
|
property Item hoverComponent
|
||||||
|
|
||||||
|
// show hover actions
|
||||||
|
onHoveredChanged: {
|
||||||
|
if (hovered && !Kirigami.Settings.isMobile) {
|
||||||
|
updateHoverComponent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
// updates the global hover component to point to this delegate, and update its position
|
||||||
width: parent.width * 0.9
|
function updateHoverComponent() {
|
||||||
x: parent.width * 0.05
|
if (hoverComponent) {
|
||||||
height: Kirigami.Units.smallSpacing
|
hoverComponent.bubble = bubble
|
||||||
anchors.top: column.bottom
|
hoverComponent.updateFunction = updateHoverComponent;
|
||||||
anchors.topMargin: Kirigami.Units.smallSpacing
|
hoverComponent.event = model
|
||||||
visible: readMarker
|
}
|
||||||
color: Kirigami.Theme.positiveTextColor
|
}
|
||||||
|
|
||||||
|
height: sectionDelegate.height + Math.max(model.showAuthor ? avatar.height : 0, bubble.implicitHeight) + loader.height + (showAuthor ? Kirigami.Units.largeSpacing : 0)
|
||||||
|
|
||||||
|
SectionDelegate {
|
||||||
|
id: sectionDelegate
|
||||||
|
width: parent.width
|
||||||
|
anchors.left: avatar.left
|
||||||
|
anchors.leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
visible: model.showSection
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Avatar {
|
||||||
|
id: avatar
|
||||||
|
width: visible || Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0
|
||||||
|
height: width
|
||||||
|
sourceSize.width: width
|
||||||
|
sourceSize.height: width
|
||||||
|
anchors {
|
||||||
|
top: sectionDelegate.bottom
|
||||||
|
topMargin: model.showAuthor ? Kirigami.Units.largeSpacing : 0
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: model.showAuthor && Config.showAvatarInTimeline && !showUserMessageOnRight
|
||||||
|
name: model.author.name ?? model.author.displayName
|
||||||
|
source: visible && model.author.avatarMediaId ? ("image://mxc/" + model.author.avatarMediaId) : ""
|
||||||
|
color: model.author.color
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||||
|
room: currentRoom,
|
||||||
|
user: author.object,
|
||||||
|
displayName: author.displayName,
|
||||||
|
avatarMediaId: author.avatarMediaId,
|
||||||
|
avatarUrl: author.avatarUrl
|
||||||
|
}).open();
|
||||||
|
}
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Control {
|
||||||
|
id: bubble
|
||||||
|
topPadding: !Config.compactLayout ? Kirigami.Units.largeSpacing : 0
|
||||||
|
bottomPadding: !Config.compactLayout ? Kirigami.Units.largeSpacing : 0
|
||||||
|
leftPadding: Kirigami.Units.smallSpacing
|
||||||
|
rightPadding: Config.compactLayout ? Kirigami.Units.largeSpacing : Kirigami.Units.smallSpacing
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
// state: Config.compactLayout ? "compactLayout" : "default"
|
||||||
|
state: showUserMessageOnRight ? "userMessageOnRight" : "userMessageOnLeft"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: avatar.top
|
||||||
|
leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
rightMargin: showUserMessageOnRight ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
|
||||||
|
}
|
||||||
|
// HACK: anchoring didn't reset anchors.right when switching from parent.right to undefined reliably
|
||||||
|
width: Config.compactLayout ? messageDelegate.width - (Config.showAvatarInTimeline ? Kirigami.Units.gridUnit * 2 : 0) + Kirigami.Units.largeSpacing * 2 : implicitWidth
|
||||||
|
|
||||||
|
|
||||||
|
// states for anchor animations on window resize
|
||||||
|
// as setting anchors to undefined did not work reliably
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "userMessageOnRight"
|
||||||
|
AnchorChanges {
|
||||||
|
target: bubble
|
||||||
|
anchors.left: undefined
|
||||||
|
anchors.right: parent.right
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "userMessageOnLeft"
|
||||||
|
AnchorChanges {
|
||||||
|
target: bubble
|
||||||
|
anchors.left: avatar.right
|
||||||
|
anchors.right: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
transitions: [
|
||||||
|
Transition {
|
||||||
|
AnchorAnimation{duration: Kirigami.Units.longDuration; easing.type: Easing.OutCubic}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
id: column
|
||||||
|
spacing: 0
|
||||||
|
Item {
|
||||||
|
id: rowLayout
|
||||||
|
visible: model.showAuthor && !isEmote
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
||||||
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.preferredWidth: nameLabel.implicitWidth + timeLabel.implicitWidth + Kirigami.Units.largeSpacing * 2
|
||||||
|
Layout.maximumWidth: bubbleMaxWidth
|
||||||
|
implicitHeight: visible ? nameLabel.implicitHeight : 0
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
id: nameLabel
|
||||||
|
topInset: 0
|
||||||
|
|
||||||
|
visible: model.showAuthor && !isEmote
|
||||||
|
anchors.left: rowLayout.left
|
||||||
|
anchors.right: timeLabel.left
|
||||||
|
anchors.rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
text: visible ? author.displayName : ""
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: author.color
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||||
|
room: currentRoom,
|
||||||
|
user: author.object,
|
||||||
|
displayName: author.displayName,
|
||||||
|
avatarMediaId: author.avatarMediaId,
|
||||||
|
avatarUrl: author.avatarUrl
|
||||||
|
}).open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
id: timeLabel
|
||||||
|
anchors.right: rowLayout.right
|
||||||
|
visible: model.showAuthor && !isEmote
|
||||||
|
text: visible ? time.toLocaleTimeString(Locale.ShortFormat) : ""
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loader {
|
||||||
|
id: replyLoader
|
||||||
|
active: model.reply !== undefined
|
||||||
|
source: 'qrc:imports/NeoChat/Component/Timeline/ReplyComponent.qml'
|
||||||
|
visible: active
|
||||||
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
|
Layout.bottomMargin: Config.compactLayout ? 0 : Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: replyLoader.item
|
||||||
|
function onClicked() {
|
||||||
|
replyClicked(reply.eventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Item {
|
||||||
|
Rectangle {
|
||||||
|
visible: messageDelegate.hovered
|
||||||
|
color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
|
||||||
|
radius: Kirigami.Units.smallSpacing
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
Kirigami.ShadowedRectangle {
|
||||||
|
visible: cardBackground && !Config.compactLayout
|
||||||
|
anchors.fill: parent
|
||||||
|
color: {
|
||||||
|
if (model.author.isLocalUser) {
|
||||||
|
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
|
||||||
|
} else if (model.isHighlighted) {
|
||||||
|
return Kirigami.Theme.positiveBackgroundColor
|
||||||
|
} else {
|
||||||
|
return Kirigami.Theme.backgroundColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
radius: Kirigami.Units.smallSpacing
|
||||||
|
shadow.size: Kirigami.Units.smallSpacing
|
||||||
|
shadow.color: !model.isHighlighted ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
||||||
|
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||||
|
border.width: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: loader
|
||||||
|
anchors {
|
||||||
|
left: bubble.left
|
||||||
|
right: parent.right
|
||||||
|
top: bubble.bottom
|
||||||
|
topMargin: active && !Config.compactLayout ? Kirigami.Units.smallSpacing : 0
|
||||||
|
}
|
||||||
|
height: active ? item.implicitHeight : 0
|
||||||
|
//Layout.bottomMargin: readMarker ? Kirigami.Units.smallSpacing : 0
|
||||||
|
active: eventType !== "state" && eventType !== "notice" && reaction != undefined && reaction.length > 0
|
||||||
|
visible: active
|
||||||
|
sourceComponent: ReactionDelegate { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
import QtQuick 2.15
|
||||||
*/
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick 2.12
|
import QtQuick.Layouts 1.15
|
||||||
import QtQuick.Controls 2.12
|
import QtGraphicalEffects 1.15
|
||||||
import QtQuick.Layouts 1.12
|
import QtMultimedia 5.15
|
||||||
import QtQuick.Controls.Material 2.12
|
import Qt.labs.platform 1.1 as Platform
|
||||||
import QtGraphicalEffects 1.0
|
|
||||||
import QtMultimedia 5.12
|
|
||||||
import Qt.labs.platform 1.0 as Platform
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
import org.kde.kirigami 2.13 as Kirigami
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
import NeoChat.Component 1.0
|
import NeoChat.Component 1.0
|
||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
import NeoChat.Menu.Timeline 1.0
|
import NeoChat.Menu.Timeline 1.0
|
||||||
@@ -21,7 +18,6 @@ import NeoChat.Menu.Timeline 1.0
|
|||||||
Video {
|
Video {
|
||||||
id: vid
|
id: vid
|
||||||
|
|
||||||
property bool openOnFinished: false
|
|
||||||
property bool playOnFinished: false
|
property bool playOnFinished: false
|
||||||
readonly property bool downloaded: progressInfo && progressInfo.completed
|
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||||
|
|
||||||
@@ -32,10 +28,6 @@ Video {
|
|||||||
vid.source = progressInfo.localPath
|
vid.source = progressInfo.localPath
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloaded && openOnFinished) {
|
|
||||||
openSavedFile()
|
|
||||||
openOnFinished = false
|
|
||||||
}
|
|
||||||
if (downloaded && playOnFinished) {
|
if (downloaded && playOnFinished) {
|
||||||
playSavedFile()
|
playSavedFile()
|
||||||
playOnFinished = false
|
playOnFinished = false
|
||||||
@@ -93,7 +85,7 @@ Video {
|
|||||||
|
|
||||||
visible: vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError
|
visible: vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError
|
||||||
color: "white"
|
color: "white"
|
||||||
text: "Video"
|
text: i18n("Video")
|
||||||
font.pixelSize: 16
|
font.pixelSize: 16
|
||||||
|
|
||||||
padding: 8
|
padding: 8
|
||||||
@@ -105,36 +97,6 @@ Video {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Control {
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: 8
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
|
|
||||||
horizontalPadding: 8
|
|
||||||
verticalPadding: 4
|
|
||||||
|
|
||||||
contentItem: RowLayout {
|
|
||||||
Label {
|
|
||||||
text: Qt.formatTime(time)
|
|
||||||
color: "white"
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: author.displayName
|
|
||||||
color: "white"
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
radius: height / 2
|
|
||||||
color: "black"
|
|
||||||
opacity: 0.3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
@@ -153,100 +115,29 @@ Video {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoMouseArea {
|
TapHandler {
|
||||||
anchors.fill: parent
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onTapped: if (supportStreaming || progressInfo.completed) {
|
||||||
id: messageMouseArea
|
if (vid.playbackState == MediaPlayer.PlayingState) {
|
||||||
|
vid.pause()
|
||||||
onPrimaryClicked: {
|
|
||||||
if (supportStreaming || progressInfo.completed) {
|
|
||||||
if (vid.playbackState == MediaPlayer.PlayingState) {
|
|
||||||
vid.pause()
|
|
||||||
} else {
|
|
||||||
vid.play()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
downloadAndPlay()
|
vid.play()
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
downloadAndPlay()
|
||||||
onSecondaryClicked: {
|
|
||||||
var contextMenu = imageDelegateContextMenu.createObject(vid, {'room': currentRoom, 'author': author})
|
|
||||||
contextMenu.viewSource.connect(function() {
|
|
||||||
messageSourceSheet.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
|
|
||||||
})
|
|
||||||
contextMenu.downloadAndOpen.connect(downloadAndOpen)
|
|
||||||
contextMenu.saveFileAs.connect(saveFileAs)
|
|
||||||
contextMenu.reply.connect(function() {
|
|
||||||
roomPanelInput.replyModel = Object.assign({}, model)
|
|
||||||
roomPanelInput.isReply = true
|
|
||||||
roomPanelInput.focus()
|
|
||||||
})
|
|
||||||
contextMenu.redact.connect(function() {
|
|
||||||
currentRoom.redactEvent(eventId)
|
|
||||||
})
|
|
||||||
contextMenu.popup()
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: messageSourceSheet
|
|
||||||
|
|
||||||
MessageSourceSheet {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: openFolderDialog
|
|
||||||
|
|
||||||
OpenFolderDialog {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: imageDelegateContextMenu
|
|
||||||
|
|
||||||
FileDelegateContextMenu {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveFileAs() {
|
function downloadAndPlay() {
|
||||||
var folderDialog = openFolderDialog.createObject(ApplicationWindow.overlay)
|
if (downloaded) {
|
||||||
|
playSavedFile()
|
||||||
folderDialog.chosen.connect(function(path) {
|
} else {
|
||||||
if (!path) return
|
|
||||||
|
|
||||||
currentRoom.downloadFile(eventId, path + "/" + currentRoom.fileNameToDownload(eventId))
|
|
||||||
})
|
|
||||||
|
|
||||||
folderDialog.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadAndOpen()
|
|
||||||
{
|
|
||||||
if (downloaded) openSavedFile()
|
|
||||||
else
|
|
||||||
{
|
|
||||||
openOnFinished = true
|
|
||||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadAndPlay()
|
|
||||||
{
|
|
||||||
if (downloaded) playSavedFile()
|
|
||||||
else
|
|
||||||
{
|
|
||||||
playOnFinished = true
|
playOnFinished = true
|
||||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSavedFile()
|
function playSavedFile() {
|
||||||
{
|
|
||||||
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
|
||||||
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function playSavedFile()
|
|
||||||
{
|
|
||||||
vid.stop()
|
vid.stop()
|
||||||
vid.play()
|
vid.play()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
module NeoChat.Component.Timeline
|
module NeoChat.Component.Timeline
|
||||||
TimelineContainer 1.0 TimelineContainer.qml
|
TimelineContainer 1.0 TimelineContainer.qml
|
||||||
MessageDelegate 1.0 MessageDelegate.qml
|
|
||||||
TextDelegate 1.0 TextDelegate.qml
|
TextDelegate 1.0 TextDelegate.qml
|
||||||
StateDelegate 1.0 StateDelegate.qml
|
StateDelegate 1.0 StateDelegate.qml
|
||||||
SectionDelegate 1.0 SectionDelegate.qml
|
SectionDelegate 1.0 SectionDelegate.qml
|
||||||
@@ -9,3 +8,4 @@ FileDelegate 1.0 FileDelegate.qml
|
|||||||
VideoDelegate 1.0 VideoDelegate.qml
|
VideoDelegate 1.0 VideoDelegate.qml
|
||||||
ReactionDelegate 1.0 ReactionDelegate.qml
|
ReactionDelegate 1.0 ReactionDelegate.qml
|
||||||
AudioDelegate 1.0 AudioDelegate.qml
|
AudioDelegate 1.0 AudioDelegate.qml
|
||||||
|
EncryptedDelegate 1.0 EncryptedDelegate.qml
|
||||||
106
imports/NeoChat/Component/TypingPane.qml
Normal file
106
imports/NeoChat/Component/TypingPane.qml
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
/* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.de>
|
||||||
|
* SPDX-FileCopyrightText: 2020 Noah Davis <noahadvs@gmail.com>
|
||||||
|
* SPDX-FileCopyrightText: 2021 Srevin Saju <srevinsaju@sugarlabs.org>
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import org.kde.kirigami 2.14 as Kirigami
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: root
|
||||||
|
property string labelText: ""
|
||||||
|
|
||||||
|
active: visible
|
||||||
|
sourceComponent: Pane {
|
||||||
|
id: typingPane
|
||||||
|
|
||||||
|
leftPadding: Kirigami.Units.largeSpacing
|
||||||
|
rightPadding: Kirigami.Units.largeSpacing
|
||||||
|
topPadding: Kirigami.Units.smallSpacing
|
||||||
|
bottomPadding: Kirigami.Units.smallSpacing
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
FontMetrics {
|
||||||
|
id: fontMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: typingPane.spacing
|
||||||
|
Row {
|
||||||
|
id: dotRow
|
||||||
|
property int duration: 400
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
Repeater {
|
||||||
|
model: 3
|
||||||
|
delegate: Rectangle {
|
||||||
|
id: dot
|
||||||
|
color: Kirigami.Theme.textColor
|
||||||
|
radius: height/2
|
||||||
|
implicitWidth: fontMetrics.xHeight
|
||||||
|
implicitHeight: fontMetrics.xHeight
|
||||||
|
// rotating 45 degrees makes the dots look a bit smoother when scaled up
|
||||||
|
rotation: 45
|
||||||
|
opacity: 0.5
|
||||||
|
scale: 1
|
||||||
|
// FIXME: Sometimes the animation timings for each
|
||||||
|
// dot drift slightly reletative to each other.
|
||||||
|
// Not everyone can see this, but I'm pretty sure it's there.
|
||||||
|
SequentialAnimation {
|
||||||
|
running: true
|
||||||
|
PauseAnimation { duration: dotRow.duration * index / 2 }
|
||||||
|
SequentialAnimation {
|
||||||
|
loops: Animation.Infinite
|
||||||
|
ParallelAnimation {
|
||||||
|
// Animators unfortunately sync up instead of being
|
||||||
|
// staggered, so I'm using NumberAnimations instead.
|
||||||
|
NumberAnimation {
|
||||||
|
target: dot; property: "scale";
|
||||||
|
from: 1; to: 1.33
|
||||||
|
duration: dotRow.duration
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: dot; property: "opacity"
|
||||||
|
from: 0.5; to: 1
|
||||||
|
duration: dotRow.duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParallelAnimation {
|
||||||
|
NumberAnimation {
|
||||||
|
target: dot; property: "scale"
|
||||||
|
from: 1.33; to: 1
|
||||||
|
duration: dotRow.duration
|
||||||
|
}
|
||||||
|
NumberAnimation {
|
||||||
|
target: dot; property: "opacity"
|
||||||
|
from: 1; to: 0.5
|
||||||
|
duration: dotRow.duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PauseAnimation { duration: dotRow.duration }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
id: typingLabel
|
||||||
|
elide: Text.ElideRight
|
||||||
|
text: root.labelText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
leftInset: !mirrored ? 0 : -background.radius
|
||||||
|
rightInset: mirrored ? 0 : -background.radius
|
||||||
|
bottomInset: -background.radius
|
||||||
|
background: Rectangle {
|
||||||
|
radius: 3
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
border.color: Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.2)
|
||||||
|
border.width: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
imports/NeoChat/Component/confetti.png
Normal file
BIN
imports/NeoChat/Component/confetti.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
imports/NeoChat/Component/glowdot.png
Normal file
BIN
imports/NeoChat/Component/glowdot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -1,4 +1,7 @@
|
|||||||
module NeoChat.Component
|
module NeoChat.Component
|
||||||
AutoMouseArea 1.0 AutoMouseArea.qml
|
|
||||||
FullScreenImage 1.0 FullScreenImage.qml
|
FullScreenImage 1.0 FullScreenImage.qml
|
||||||
ChatTextInput 1.0 ChatTextInput.qml
|
ChatTextInput 1.0 ChatTextInput.qml
|
||||||
|
FancyEffectsContainer 1.0 FancyEffectsContainer.qml
|
||||||
|
TypingPane 1.0 TypingPane.qml
|
||||||
|
QuickSwitcher 1.0 QuickSwitcher.qml
|
||||||
|
ShimmerGradient 1.0 ShimmerGradient.qml
|
||||||
|
|||||||
@@ -1,31 +1,26 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
|
|
||||||
import NeoChat.Component 1.0
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component 1.0
|
||||||
|
|
||||||
Kirigami.OverlaySheet {
|
Kirigami.OverlaySheet {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
parent: applicationWindow().overlay
|
parent: applicationWindow().overlay
|
||||||
|
|
||||||
header: Kirigami.Heading {
|
title: i18n("Create a Room")
|
||||||
text: i18n("Create a Room")
|
|
||||||
}
|
|
||||||
|
|
||||||
contentItem: Kirigami.FormLayout {
|
contentItem: Kirigami.FormLayout {
|
||||||
TextField {
|
TextField {
|
||||||
id: roomNameField
|
id: roomNameField
|
||||||
Kirigami.FormData.label: i18n("Room Name")
|
Kirigami.FormData.label: i18n("Room Name")
|
||||||
onAccepted: roomTopixField.forceActiveFocus();
|
onAccepted: roomTopicField.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
@@ -36,11 +31,11 @@ Kirigami.OverlaySheet {
|
|||||||
|
|
||||||
Button {
|
Button {
|
||||||
id: okButton
|
id: okButton
|
||||||
|
|
||||||
text: i18nc("@action:button", "Ok")
|
text: i18nc("@action:button", "Ok")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
Controller.createRoom(Controller.activeConnection, roomNameField.text, roomTopicField.text)
|
Controller.createRoom(roomNameField.text, roomTopicField.text);
|
||||||
root.close();
|
root.close();
|
||||||
// TODO investigate how to join the new room automatically
|
|
||||||
root.destroy();
|
root.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
import QtQuick 2.15
|
||||||
*/
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
import QtQuick 2.14
|
|
||||||
import QtQuick.Controls 2.14 as QQC2
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
import NeoChat.Component.Emoji 1.0
|
import NeoChat.Component.Emoji 1.0
|
||||||
@@ -24,7 +23,6 @@ QQC2.Popup {
|
|||||||
implicitHeight: Kirigami.Units.gridUnit * 20
|
implicitHeight: Kirigami.Units.gridUnit * 20
|
||||||
|
|
||||||
contentItem: EmojiPicker {
|
contentItem: EmojiPicker {
|
||||||
onChosen: react(emoji);
|
onChosen: react(emoji)
|
||||||
emojiModel: EmojiModel {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
import QtQuick 2.15
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import Qt.labs.platform 1.1
|
import Qt.labs.platform 1.1
|
||||||
|
|
||||||
FileDialog {
|
FileDialog {
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import Qt.labs.platform 1.1
|
|
||||||
|
|
||||||
FolderDialog {
|
|
||||||
signal chosen(string path)
|
|
||||||
|
|
||||||
id: root
|
|
||||||
|
|
||||||
title: i18n("Please choose a folder")
|
|
||||||
|
|
||||||
onAccepted: chosen(folder)
|
|
||||||
}
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
|
|
||||||
import NeoChat.Component 1.0
|
|
||||||
import NeoChat.Setting 1.0
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
Kirigami.OverlaySheet {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property var room
|
|
||||||
|
|
||||||
readonly property bool canChangeAvatar: room.canSendState("m.room.avatar")
|
|
||||||
readonly property bool canChangeName: room.canSendState("m.room.name")
|
|
||||||
readonly property bool canChangeTopic: room.canSendState("m.room.topic")
|
|
||||||
readonly property bool canChangeCanonicalAlias: room.canSendState("m.room.canonical_alias")
|
|
||||||
|
|
||||||
parent: applicationWindow().overlay
|
|
||||||
|
|
||||||
|
|
||||||
header: Kirigami.Heading {
|
|
||||||
text: i18nc("%1 is the room name", "Room Settings - %1", room.displayName)
|
|
||||||
elide: Text.ElideRight
|
|
||||||
}
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
Kirigami.Avatar {
|
|
||||||
Layout.preferredWidth: 72
|
|
||||||
Layout.preferredHeight: 72
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
|
|
||||||
name: room.displayName
|
|
||||||
source: room.avatarMediaId ? "image://mxc/" + room.avatarMediaId : ""
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
enabled: canChangeAvatar
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
|
|
||||||
|
|
||||||
fileDialog.chosen.connect(function(path) {
|
|
||||||
if (!path) return
|
|
||||||
|
|
||||||
room.changeAvatar(path)
|
|
||||||
})
|
|
||||||
|
|
||||||
fileDialog.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.FormLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
TextField {
|
|
||||||
id: roomNameField
|
|
||||||
text: room.name
|
|
||||||
Kirigami.FormData.label: i18n("Room Name:")
|
|
||||||
enabled: canChangeName
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField {
|
|
||||||
id: roomTopicField
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: room.topic
|
|
||||||
Kirigami.FormData.label: i18n("Room topic:")
|
|
||||||
enabled: canChangeTopic
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
|
|
||||||
visible: canChangeName || canChangeTopic
|
|
||||||
|
|
||||||
text: i18n("Save")
|
|
||||||
highlighted: true
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (room.name != roomNameField.text) {
|
|
||||||
room.setName(roomNameField.text)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (room.topic != roomTopicField.text) {
|
|
||||||
room.setTopic(roomTopicField.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Separator {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
visible: canonicalAliasComboBox.visible || altAlias.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
|
||||||
id: canonicalAliasComboBox
|
|
||||||
visible: room.aliases && room.aliases.length
|
|
||||||
Kirigami.FormData.label: i18n("Canonical Alias:")
|
|
||||||
popup.z: 999; // HACK This is an absolute hack, but combos inside OverlaySheets have their popups show up underneath, because of fun z ordering stuff
|
|
||||||
|
|
||||||
enabled: canChangeCanonicalAlias
|
|
||||||
|
|
||||||
model: room.aliases
|
|
||||||
|
|
||||||
currentIndex: room.aliases.indexOf(room.canonicalAlias)
|
|
||||||
onCurrentIndexChanged: {
|
|
||||||
if (room.canonicalAlias != room.aliases[currentIndex]) {
|
|
||||||
room.setCanonicalAlias(room.aliases[currentIndex])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: altAlias
|
|
||||||
Kirigami.FormData.label: i18n("Alt Aliases")
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
visible: room.altAliases && room.altAliases.length
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: room.altAliases
|
|
||||||
|
|
||||||
delegate: RowLayout {
|
|
||||||
Layout.maximumWidth: parent.width
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: modelData
|
|
||||||
color: Kirigami.Theme.disabledColor
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolButton {
|
|
||||||
icon.name: ""
|
|
||||||
onClicked: room.removeLocalAlias(modelData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Separator {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
visible: next.visible || prev.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
Control {
|
|
||||||
id: next
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
visible: room.predecessorId && room.connection.room(room.predecessorId)
|
|
||||||
|
|
||||||
padding: Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
contentItem: Kirigami.InlineMessage {
|
|
||||||
text: i18n("This room continues another conversation.")
|
|
||||||
actions: Kirigami.Action {
|
|
||||||
text: i18n("See older messages...")
|
|
||||||
onTriggered: {
|
|
||||||
roomListForm.enteredRoom = Controller.activeConnection.room(room.predecessorId)
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Control {
|
|
||||||
id: prev
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
visible: room.successorId && room.connection.room(room.successorId)
|
|
||||||
|
|
||||||
padding: Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
contentItem: Kirigami.InlineMessage {
|
|
||||||
text: i18n("This room has been replaced.")
|
|
||||||
actions: Kirigami.Action {
|
|
||||||
text: i18n("See new room...")
|
|
||||||
onTriggered: {
|
|
||||||
roomListForm.enteredRoom = Controller.activeConnection.room(room.successorId)
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Component {
|
|
||||||
id: openFileDialog
|
|
||||||
|
|
||||||
OpenFileDialog {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
import NeoChat.Component 1.0
|
import NeoChat.Component 1.0
|
||||||
import NeoChat.Setting 1.0
|
|
||||||
|
|
||||||
Kirigami.OverlaySheet {
|
Kirigami.OverlaySheet {
|
||||||
id: root
|
id: root
|
||||||
@@ -28,9 +27,7 @@ Kirigami.OverlaySheet {
|
|||||||
rightPadding: 0
|
rightPadding: 0
|
||||||
topPadding: 0
|
topPadding: 0
|
||||||
|
|
||||||
header: Kirigami.Heading {
|
title: i18nc("@title:menu Account detail dialog", "Account detail")
|
||||||
text: i18nc("Account detail dialog", "Account detail - %1", displayName)
|
|
||||||
}
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
contentItem: ColumnLayout {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
@@ -47,7 +44,8 @@ Kirigami.OverlaySheet {
|
|||||||
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
||||||
|
|
||||||
name: displayName
|
name: displayName
|
||||||
source: avatarMediaId ? "image://mxc/" + avatarMediaId : ""
|
source: avatarMediaId ? ("image://mxc/" + avatarMediaId) : ""
|
||||||
|
color: user.color
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
@@ -70,7 +68,7 @@ Kirigami.OverlaySheet {
|
|||||||
|
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
wrapMode: Text.NoWrap
|
wrapMode: Text.NoWrap
|
||||||
text: displayName
|
text: room.htmlSafeMemberName(user.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
@@ -100,22 +98,64 @@ Kirigami.OverlaySheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Kirigami.BasicListItem {
|
Kirigami.BasicListItem {
|
||||||
visible: user !== room.localUser && room.canSendState("kick")
|
visible: user !== room.localUser && room.canSendState("kick") && room.containsUser(user.id)
|
||||||
|
|
||||||
action: Kirigami.Action {
|
action: Kirigami.Action {
|
||||||
text: i18n("Kick this user")
|
text: i18n("Kick this user")
|
||||||
icon.name: "im-kick-user"
|
icon.name: "im-kick-user"
|
||||||
onTriggered: room.kickMember(user.id)
|
onTriggered: {
|
||||||
|
room.kickMember(user.id)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Kirigami.BasicListItem {
|
Kirigami.BasicListItem {
|
||||||
visible: user !== room.localUser && room.canSendState("ban")
|
visible: user !== room.localUser && room.canSendState("ban") && !room.isUserBanned(user.id)
|
||||||
|
|
||||||
action: Kirigami.Action {
|
action: Kirigami.Action {
|
||||||
text: i18n("Ban this user")
|
text: i18n("Ban this user")
|
||||||
icon.name: "im-ban-user"
|
icon.name: "im-ban-user"
|
||||||
icon.color: Kirigami.Theme.negativeTextColor
|
icon.color: Kirigami.Theme.negativeTextColor
|
||||||
onTriggered: room.banMember(user.id)
|
onTriggered: {
|
||||||
|
room.ban(user.id)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
visible: user !== room.localUser && room.canSendState("ban") && room.isUserBanned(user.id)
|
||||||
|
|
||||||
|
action: Kirigami.Action {
|
||||||
|
text: i18n("Unban this user")
|
||||||
|
icon.name: "im-irc"
|
||||||
|
icon.color: Kirigami.Theme.negativeTextColor
|
||||||
|
onTriggered: {
|
||||||
|
room.unban(user.id)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
visible: user === room.localUser || room.canSendState("redact")
|
||||||
|
|
||||||
|
action: Kirigami.Action {
|
||||||
|
text: i18n("Delete recent messages by this user")
|
||||||
|
icon.name: "delete"
|
||||||
|
icon.color: Kirigami.Theme.negativeTextColor
|
||||||
|
onTriggered: {
|
||||||
|
room.deleteMessagesByUser(user.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
visible: user !== room.localUser
|
||||||
|
action: Kirigami.Action {
|
||||||
|
text: i18n("Open a private chat")
|
||||||
|
icon.name: "document-send"
|
||||||
|
onTriggered: {
|
||||||
|
Controller.openOrCreateDirectChat(user);
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Component {
|
Component {
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
module NeoChat.Dialog
|
module NeoChat.Dialog
|
||||||
RoomSettingsDialog 1.0 RoomSettingsDialog.qml
|
|
||||||
UserDetailDialog 1.0 UserDetailDialog.qml
|
UserDetailDialog 1.0 UserDetailDialog.qml
|
||||||
LoginDialog 1.0 LoginDialog.qml
|
LoginDialog 1.0 LoginDialog.qml
|
||||||
CreateRoomDialog 1.0 CreateRoomDialog.qml
|
CreateRoomDialog 1.0 CreateRoomDialog.qml
|
||||||
AcceptInvitationDialog 1.0 AcceptInvitationDialog.qml
|
AcceptInvitationDialog 1.0 AcceptInvitationDialog.qml
|
||||||
OpenFileDialog 1.0 OpenFileDialog.qml
|
OpenFileDialog 1.0 OpenFileDialog.qml
|
||||||
OpenFolderDialog 1.0 OpenFolderDialog.qml
|
|
||||||
ImageClipboardDialog 1.0 ImageClipboardDialog.qml
|
ImageClipboardDialog 1.0 ImageClipboardDialog.qml
|
||||||
StartChatDialog 1.0 StartChatDialog.qml
|
StartChatDialog 1.0 StartChatDialog.qml
|
||||||
EmojiDialog 1.0 EmojiDialog.qml
|
EmojiDialog 1.0 EmojiDialog.qml
|
||||||
|
|||||||
90
imports/NeoChat/Menu/EditMenu.qml
Normal file
90
imports/NeoChat/Menu/EditMenu.qml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
import Qt.labs.platform 1.1 as Labs
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.12 as QQC2
|
||||||
|
import QtQuick.Layouts 1.10
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
Labs.Menu {
|
||||||
|
id: editMenu
|
||||||
|
|
||||||
|
required property Item field
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: editMenu.field !== null && editMenu.field.canUndo
|
||||||
|
text: i18nc("text editing menu action", "Undo")
|
||||||
|
shortcut: StandardKey.Undo
|
||||||
|
onTriggered: {
|
||||||
|
editMenu.field.undo()
|
||||||
|
editMenu.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: editMenu.field !== null && editMenu.field.canRedo
|
||||||
|
text: i18nc("text editing menu action", "Redo")
|
||||||
|
shortcut: StandardKey.Redo
|
||||||
|
onTriggered: {
|
||||||
|
editMenu.field.undo()
|
||||||
|
editMenu.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Labs.MenuSeparator {
|
||||||
|
}
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: editMenu.field !== null && editMenu.field.selectedText
|
||||||
|
text: i18nc("text editing menu action", "Cut")
|
||||||
|
shortcut: StandardKey.Cut
|
||||||
|
onTriggered: {
|
||||||
|
editMenu.field.cut()
|
||||||
|
editMenu.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: editMenu.field !== null && editMenu.field.selectedText
|
||||||
|
text: i18nc("text editing menu action", "Copy")
|
||||||
|
shortcut: StandardKey.Copy
|
||||||
|
onTriggered: {
|
||||||
|
editMenu.field.copy()
|
||||||
|
editMenu.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: editMenu.field !== null && editMenu.field.canPaste
|
||||||
|
text: i18nc("text editing menu action", "Paste")
|
||||||
|
shortcut: StandardKey.Paste
|
||||||
|
onTriggered: {
|
||||||
|
editMenu.field.paste()
|
||||||
|
editMenu.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: editMenu.field !== null && editMenu.field.selectedText !== ""
|
||||||
|
text: i18nc("text editing menu action", "Delete")
|
||||||
|
shortcut: ""
|
||||||
|
onTriggered: {
|
||||||
|
editMenu.field.remove(editMenu.field.selectionStart, editMenu.field.selectionEnd)
|
||||||
|
editMenu.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Labs.MenuSeparator {
|
||||||
|
}
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: editMenu.field !== null
|
||||||
|
text: i18nc("text editing menu action", "Select All")
|
||||||
|
shortcut: StandardKey.SelectAll
|
||||||
|
onTriggered: {
|
||||||
|
editMenu.field.selectAll()
|
||||||
|
editMenu.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
96
imports/NeoChat/Menu/GlobalMenu.qml
Normal file
96
imports/NeoChat/Menu/GlobalMenu.qml
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import Qt.labs.platform 1.1 as Labs
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Window 2.15
|
||||||
|
import QtQuick.Controls 2.12 as QQC2
|
||||||
|
import QtQuick.Layouts 1.10
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component 1.0
|
||||||
|
import NeoChat.Dialog 1.0
|
||||||
|
import NeoChat.Page 1.0
|
||||||
|
import NeoChat.Panel 1.0
|
||||||
|
|
||||||
|
Labs.MenuBar {
|
||||||
|
Labs.Menu {
|
||||||
|
title: i18nc("menu", "NeoChat")
|
||||||
|
|
||||||
|
// TODO: make about page its own thing so we can go to it instead of settings where it's currently at
|
||||||
|
// Labs.MenuItem {
|
||||||
|
// text: i18nc("menu", "About NeoChat")
|
||||||
|
// }
|
||||||
|
Labs.MenuItem {
|
||||||
|
enabled: pageStack.layers.currentItem.title !== i18n("Settings")
|
||||||
|
text: i18nc("menu", "Preferences…")
|
||||||
|
|
||||||
|
shortcut: StandardKey.Preferences
|
||||||
|
onTriggered: pageStack.pushDialogLayer("qrc:/imports/NeoChat/Settings/SettingsPage.qml")
|
||||||
|
}
|
||||||
|
Labs.MenuItem {
|
||||||
|
text: i18nc("menu", "Quit NeoChat")
|
||||||
|
|
||||||
|
shortcut: StandardKey.Quit
|
||||||
|
onTriggered: Qt.quit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Labs.Menu {
|
||||||
|
title: i18nc("menu", "File")
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
text: i18nc("menu", "New Private Chat…")
|
||||||
|
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
|
||||||
|
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/StartChatPage.qml", {"connection": Controller.activeConnection})
|
||||||
|
}
|
||||||
|
Labs.MenuItem {
|
||||||
|
text: i18nc("menu", "New Group…")
|
||||||
|
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
|
||||||
|
shortcut: StandardKey.New
|
||||||
|
onTriggered: {
|
||||||
|
const dialog = createRoomDialog.createObject(root.overlay)
|
||||||
|
dialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Labs.MenuItem {
|
||||||
|
text: i18nc("menu", "Browse Chats…")
|
||||||
|
onTriggered: pushReplaceLayer("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {"connection": Controller.activeConnection})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EditMenu {
|
||||||
|
title: i18nc("menu", "Edit")
|
||||||
|
field: (root.activeFocusItem instanceof TextEdit || root.activeFocusItem instanceof TextInput) ? root.activeFocusItem : null
|
||||||
|
}
|
||||||
|
Labs.Menu {
|
||||||
|
title: i18nc("menu", "View")
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
text: i18nc("menu item that opens a UI element called the 'Quick Switcher', which offers a fast keyboard-based interface for switching in between chats.", "Open Quick Switcher")
|
||||||
|
shortcut: "Ctrl+K"
|
||||||
|
onTriggered: quickView.item.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Labs.Menu {
|
||||||
|
title: i18nc("menu", "Window")
|
||||||
|
|
||||||
|
// Labs.MenuItem {
|
||||||
|
// text: settings.userWantsSidebars ? i18nc("menu", "Hide Sidebar") : i18nc("menu", "Show Sidebar")
|
||||||
|
// onTriggered: settings.userWantsSidebars = !settings.userWantsSidebars
|
||||||
|
// }
|
||||||
|
Labs.MenuItem {
|
||||||
|
text: root.visibility === Window.FullScreen ? i18nc("menu", "Exit Full Screen") : i18nc("menu", "Enter Full Screen")
|
||||||
|
onTriggered: root.visibility === Window.FullScreen ? root.showNormal() : root.showFullScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: offline help system (https://invent.kde.org/network/neochat/-/issues/411)
|
||||||
|
Labs.Menu {
|
||||||
|
title: i18nc("menu", "Help")
|
||||||
|
|
||||||
|
Labs.MenuItem {
|
||||||
|
text: i18nc("menu", "Matrix FAQ")
|
||||||
|
onTriggered: Qt.openUrlExternally("https://matrix.org/faq/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
import QtQuick 2.15
|
||||||
*/
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12
|
import org.kde.kirigami 2.19 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Page 1.0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context menu when clicking on a room in the room list
|
* Context menu when clicking on a room in the room list
|
||||||
@@ -15,23 +18,28 @@ Menu {
|
|||||||
property var room
|
property var room
|
||||||
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: i18n("Favourite")
|
id: newWindow
|
||||||
checkable: true
|
text: i18n("Open in new window")
|
||||||
checked: room.isFavourite
|
onTriggered: RoomManager.openWindow(room);
|
||||||
|
visible: !Kirigami.Settings.isMobile
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuSeparator {
|
||||||
|
visible: newWindow.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: room.isFavourite ? i18n("Remove from Favourites") : i18n("Add to Favourites")
|
||||||
|
|
||||||
onTriggered: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
onTriggered: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: i18n("Deprioritize")
|
text: room.isLowPriority ? i18n("Reprioritize") : i18n("Deprioritize")
|
||||||
checkable: true
|
|
||||||
checked: room.isLowPriority
|
|
||||||
|
|
||||||
onTriggered: room.isLowPriority ? room.removeTag("m.lowpriority") : room.addTag("m.lowpriority", 1.0)
|
onTriggered: room.isLowPriority ? room.removeTag("m.lowpriority") : room.addTag("m.lowpriority", 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuSeparator {}
|
|
||||||
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: i18n("Mark as Read")
|
text: i18n("Mark as Read")
|
||||||
|
|
||||||
@@ -42,7 +50,7 @@ Menu {
|
|||||||
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: i18n("Leave Room")
|
text: i18n("Leave Room")
|
||||||
onTriggered: room.forget()
|
onTriggered: RoomManager.leaveRoom(room)
|
||||||
}
|
}
|
||||||
|
|
||||||
onClosed: destroy()
|
onClosed: destroy()
|
||||||
|
|||||||
25
imports/NeoChat/Menu/TextContextMenu.qml
Normal file
25
imports/NeoChat/Menu/TextContextMenu.qml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context menu when clicking on a room in the room list
|
||||||
|
*/
|
||||||
|
Menu {
|
||||||
|
id: root
|
||||||
|
property var selectedText
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: WebShortcutModel {
|
||||||
|
selectedText: root.selectedText
|
||||||
|
}
|
||||||
|
delegate: MenuItem {
|
||||||
|
text: model.display
|
||||||
|
icon.name: model.decoration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuSeparator {}
|
||||||
|
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
||||||
@@ -1,54 +1,85 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12
|
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import Qt.labs.platform 1.1
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
|
import NeoChat.Menu 1.0
|
||||||
|
|
||||||
Menu {
|
MessageDelegateContextMenu {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property var room
|
required property var file
|
||||||
required property var author
|
required property var progressInfo
|
||||||
|
|
||||||
signal viewSource()
|
property list<Kirigami.Action> actions: [
|
||||||
signal downloadAndOpen()
|
Kirigami.Action {
|
||||||
signal saveFileAs()
|
text: i18n("Open Externally")
|
||||||
signal reply()
|
icon.name: "document-open"
|
||||||
signal redact()
|
onTriggered: {
|
||||||
|
if (file.downloaded) {
|
||||||
MenuItem {
|
if (!Qt.openUrlExternally(progressInfo.localPath)) {
|
||||||
text: i18n("View Source")
|
Qt.openUrlExternally(progressInfo.localDir);
|
||||||
|
}
|
||||||
onTriggered: viewSource()
|
} else {
|
||||||
|
file.onDownloadedChanged.connect(function() {
|
||||||
|
if (!Qt.openUrlExternally(progressInfo.localPath)) {
|
||||||
|
Qt.openUrlExternally(progressInfo.localDir);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Save As")
|
||||||
|
icon.name: "document-save"
|
||||||
|
onTriggered: {
|
||||||
|
var dialog = saveAsDialog.createObject(ApplicationWindow.overlay)
|
||||||
|
dialog.open()
|
||||||
|
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Reply")
|
||||||
|
icon.name: "mail-replied-symbolic"
|
||||||
|
onTriggered: {
|
||||||
|
ChatBoxHelper.replyToMessage(eventId, message, author);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
|
||||||
|
text: i18n("Remove")
|
||||||
|
icon.name: "edit-delete-remove"
|
||||||
|
icon.color: "red"
|
||||||
|
onTriggered: {
|
||||||
|
currentRoom.redactEvent(eventId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("View Source")
|
||||||
|
icon.name: "code-context"
|
||||||
|
onTriggered: {
|
||||||
|
messageSourceSheet.createObject(root, {'sourceText': root.source}).open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
Component {
|
||||||
|
id: saveAsDialog
|
||||||
|
FileDialog {
|
||||||
|
fileMode: FileDialog.SaveFile
|
||||||
|
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||||
|
onAccepted: {
|
||||||
|
if (!currentFile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
currentRoom.downloadFile(eventId, currentFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: i18n("Open Externally")
|
|
||||||
|
|
||||||
onTriggered: downloadAndOpen()
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: i18n("Save As")
|
|
||||||
|
|
||||||
onTriggered: saveFileAs()
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: i18n("Reply")
|
|
||||||
|
|
||||||
onTriggered: reply()
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
visible: room.canSendState("redact") || room.localUser.id === author.id
|
|
||||||
text: i18n("Redact")
|
|
||||||
onTriggered: redact()
|
|
||||||
}
|
|
||||||
|
|
||||||
onClosed: destroy()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,117 +1,212 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
import QtQuick 2.15
|
||||||
*/
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
import QtQuick 2.12
|
import QtQuick.Layouts 1.15
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
|
|
||||||
import NeoChat.Dialog 1.0
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Dialog 1.0
|
||||||
|
|
||||||
Kirigami.OverlaySheet {
|
Loader {
|
||||||
id: root
|
id: loadRoot
|
||||||
|
|
||||||
required property var author
|
required property var author
|
||||||
required property string message
|
required property string message
|
||||||
required property string eventId
|
required property string eventId
|
||||||
|
property string eventType: ""
|
||||||
|
property string formattedBody: ""
|
||||||
|
required property string source
|
||||||
|
property string selectedText: ""
|
||||||
|
|
||||||
signal viewSource()
|
property list<Kirigami.Action> actions: [
|
||||||
signal reply(var author, string message)
|
Kirigami.Action {
|
||||||
signal remove()
|
text: i18n("Edit")
|
||||||
|
icon.name: "document-edit"
|
||||||
parent: applicationWindow().overlay
|
onTriggered: ChatBoxHelper.edit(message, formattedBody, eventId);
|
||||||
|
visible: eventType.length > 0 && author.id === Controller.activeConnection.localUserId && (eventType === "emote" || eventType === "message")
|
||||||
leftPadding: 0
|
},
|
||||||
rightPadding: 0
|
Kirigami.Action {
|
||||||
|
text: i18n("Reply")
|
||||||
ColumnLayout {
|
icon.name: "mail-replied-symbolic"
|
||||||
spacing: 0
|
onTriggered: ChatBoxHelper.replyToMessage(eventId, message, author);
|
||||||
RowLayout {
|
},
|
||||||
id: headerLayout
|
Kirigami.Action {
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
|
||||||
Kirigami.Avatar {
|
|
||||||
id: avatar
|
|
||||||
source: author.avatarMediaId ? "image://mxc/" + author.avatarMediaId : ""
|
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
}
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Kirigami.Heading {
|
|
||||||
level: 3
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: author.displayName
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
|
||||||
QQC2.Label {
|
|
||||||
text: message
|
|
||||||
Layout.fillWidth: true
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row {
|
|
||||||
spacing: 0
|
|
||||||
Repeater {
|
|
||||||
model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"]
|
|
||||||
delegate: QQC2.ItemDelegate {
|
|
||||||
width: 32
|
|
||||||
height: 32
|
|
||||||
|
|
||||||
contentItem: QQC2.Label {
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.family: "emoji"
|
|
||||||
text: modelData
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
currentRoom.toggleReaction(eventId, modelData)
|
|
||||||
root.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Kirigami.BasicListItem {
|
|
||||||
action: Kirigami.Action {
|
|
||||||
text: i18n("Reply")
|
|
||||||
icon.name: "mail-replied-symbolic"
|
|
||||||
onTriggered: reply(author, message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Kirigami.BasicListItem {
|
|
||||||
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
|
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
|
||||||
action: Kirigami.Action {
|
text: i18n("Remove")
|
||||||
text: i18n("Remove")
|
icon.name: "edit-delete-remove"
|
||||||
icon.name: "edit-delete-remove"
|
icon.color: "red"
|
||||||
icon.color: "red"
|
onTriggered: currentRoom.redactEvent(eventId);
|
||||||
onTriggered: remove()
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Copy")
|
||||||
|
icon.name: "edit-copy"
|
||||||
|
onTriggered: Clipboard.saveText(loadRoot.selectedText === "" ? loadRoot.message : loadRoot.selectedText)
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("View Source")
|
||||||
|
icon.name: "code-context"
|
||||||
|
onTriggered: {
|
||||||
|
messageSourceSheet.createObject(page, {'sourceText': loadRoot.source}).open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Kirigami.BasicListItem {
|
]
|
||||||
action: Kirigami.Action {
|
|
||||||
text: i18n("Copy")
|
Component {
|
||||||
icon.name: "edit-copy"
|
id: regularMenu
|
||||||
onTriggered: Clipboard.saveText(message)
|
|
||||||
|
QQC2.Menu {
|
||||||
|
Repeater {
|
||||||
|
model: loadRoot.actions
|
||||||
|
QQC2.MenuItem {
|
||||||
|
visible: modelData.visible
|
||||||
|
action: modelData
|
||||||
|
onClicked: loadRoot.item.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
QQC2.Menu {
|
||||||
Kirigami.BasicListItem {
|
id: webshortcutmenu
|
||||||
action: Kirigami.Action {
|
title: i18n("Search for '%1'", webshortcutmodel.trunkatedSearchText)
|
||||||
text: i18n("View Source")
|
property bool isVisible: selectedText && selectedText.length > 0 && webshortcutmodel.enabled
|
||||||
icon.name: "code-context"
|
Component.onCompleted: webshortcutmenu.parent.visible = isVisible
|
||||||
onTriggered: viewSource()
|
onIsVisibleChanged: webshortcutmenu.parent.visible = isVisible
|
||||||
|
Instantiator {
|
||||||
|
model: WebShortcutModel {
|
||||||
|
id: webshortcutmodel
|
||||||
|
selectedText: loadRoot.selectedText
|
||||||
|
onOpenUrl: RoomManager.visitNonMatrix(url)
|
||||||
|
}
|
||||||
|
delegate: QQC2.MenuItem {
|
||||||
|
text: model.display
|
||||||
|
icon.name: model.decoration
|
||||||
|
onTriggered: webshortcutmodel.trigger(model.edit)
|
||||||
|
}
|
||||||
|
onObjectAdded: webshortcutmenu.insertItem(0, object)
|
||||||
|
}
|
||||||
|
QQC2.MenuSeparator {}
|
||||||
|
QQC2.MenuItem {
|
||||||
|
text: i18n("Configure Web Shortcuts...")
|
||||||
|
icon.name: "configure"
|
||||||
|
onTriggered: webshortcutmodel.configureWebShortcuts()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Component {
|
||||||
|
id: mobileMenu
|
||||||
|
|
||||||
|
Kirigami.OverlayDrawer {
|
||||||
|
id: drawer
|
||||||
|
height: popupContent.implicitHeight
|
||||||
|
edge: Qt.BottomEdge
|
||||||
|
padding: 0
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
topPadding: 0
|
||||||
|
|
||||||
|
parent: applicationWindow().overlay
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: popupContent
|
||||||
|
width: parent.width
|
||||||
|
spacing: 0
|
||||||
|
RowLayout {
|
||||||
|
id: headerLayout
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: Kirigami.Units.largeSpacing
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
Kirigami.Avatar {
|
||||||
|
id: avatar
|
||||||
|
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Kirigami.Heading {
|
||||||
|
level: 3
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: currentRoom.htmlSafeMemberName(author.id)
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
text: message
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
|
||||||
|
onLinkActivated: RoomManager.openResource(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
spacing: 0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
|
||||||
|
Repeater {
|
||||||
|
model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"]
|
||||||
|
delegate: QQC2.ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
contentItem: Kirigami.Heading {
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
|
font.family: "emoji"
|
||||||
|
text: modelData
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
currentRoom.toggleReaction(eventId, modelData);
|
||||||
|
loadRoot.item.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
Repeater {
|
||||||
|
id: listViewAction
|
||||||
|
model: loadRoot.actions
|
||||||
|
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
icon: modelData.icon.name
|
||||||
|
iconColor: modelData.icon.color ?? undefined
|
||||||
|
enabled: modelData.enabled
|
||||||
|
visible: modelData.visible
|
||||||
|
text: modelData.text
|
||||||
|
onClicked: {
|
||||||
|
modelData.triggered()
|
||||||
|
loadRoot.item.close();
|
||||||
|
}
|
||||||
|
implicitHeight: visible ? Kirigami.Units.gridUnit * 3 : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asynchronous: true
|
||||||
|
sourceComponent: Kirigami.Settings.isMobile ? mobileMenu : regularMenu
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onStatusChanged: if (status == Loader.Ready) {
|
||||||
|
if (Kirigami.Settings.isMobile) {
|
||||||
|
item.open();
|
||||||
|
} else {
|
||||||
|
item.popup();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,30 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.12 as Kirigami
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
|
import org.kde.syntaxhighlighting 1.0
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
Kirigami.OverlaySheet {
|
Kirigami.OverlaySheet {
|
||||||
|
|
||||||
property string sourceText
|
property string sourceText
|
||||||
|
|
||||||
header: Kirigami.Heading {
|
title: i18n("Message Source")
|
||||||
text: i18n("Message Source")
|
|
||||||
}
|
|
||||||
|
|
||||||
TextArea {
|
TextArea {
|
||||||
|
id: sourceTextArea
|
||||||
text: sourceText
|
text: sourceText
|
||||||
readOnly: true
|
readOnly: true
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
|
|
||||||
|
SyntaxHighlighter {
|
||||||
|
textEdit: sourceTextArea
|
||||||
|
repository: Repository
|
||||||
|
definition: "JSON"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
module NeoChat.Menu
|
module NeoChat.Menu
|
||||||
RoomListContextMenu 1.0 RoomListContextMenu.qml
|
RoomListContextMenu 1.0 RoomListContextMenu.qml
|
||||||
|
GlobalMenu 1.0 GlobalMenu.qml
|
||||||
|
EditMenu 1.0 EditMenu.qml
|
||||||
|
|||||||
@@ -1,137 +0,0 @@
|
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick 2.14
|
|
||||||
import QtQuick.Controls 2.14 as Controls
|
|
||||||
import QtQuick.Layouts 1.14
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.12 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
Kirigami.ScrollablePage {
|
|
||||||
title: i18n("Accounts")
|
|
||||||
|
|
||||||
ListView {
|
|
||||||
model: AccountListModel { }
|
|
||||||
delegate: Kirigami.SwipeListItem {
|
|
||||||
leftPadding: 0
|
|
||||||
rightPadding: 0
|
|
||||||
action: Kirigami.Action {
|
|
||||||
onTriggered: Controller.activeConnection = model.connection
|
|
||||||
}
|
|
||||||
Kirigami.BasicListItem {
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
|
|
||||||
text: model.user.displayName
|
|
||||||
subtitle: model.user.id
|
|
||||||
icon: model.connection.user.avatarMediaId ? "image://mxc/" + model.connection.user.avatarMediaId : "im-user"
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
Controller.activeConnection = model.connection
|
|
||||||
pageStack.layers.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
actions: [
|
|
||||||
Kirigami.Action {
|
|
||||||
text: i18n("Edit this account")
|
|
||||||
iconName: "document-edit"
|
|
||||||
onTriggered: {
|
|
||||||
userEditSheet.connection = model.connection
|
|
||||||
userEditSheet.open()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Kirigami.Action {
|
|
||||||
text: i18n("Logout")
|
|
||||||
iconName: "im-kick-user"
|
|
||||||
onTriggered: {
|
|
||||||
Controller.logout(model.connection, true)
|
|
||||||
if(Controller.accountCount === 1)
|
|
||||||
pageStack.layers.pop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: Controller
|
|
||||||
function onConnectionAdded() {
|
|
||||||
if (pageStack.layers.depth > 2)
|
|
||||||
pageStack.layers.pop()
|
|
||||||
}
|
|
||||||
function onPasswordStatus(status) {
|
|
||||||
if(status == Controller.Success)
|
|
||||||
showPassiveNotification(i18n("Password changed successfully"))
|
|
||||||
else if(status == Controller.Wrong)
|
|
||||||
showPassiveNotification(i18n("Wrong password entered"))
|
|
||||||
else
|
|
||||||
showPassiveNotification(i18n("Unknown problem while trying to change password"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actions.main: Kirigami.Action {
|
|
||||||
text: i18n("Add an account")
|
|
||||||
iconName: "list-add-user"
|
|
||||||
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/LoginPage.qml")
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.OverlaySheet {
|
|
||||||
id: userEditSheet
|
|
||||||
|
|
||||||
property var connection
|
|
||||||
|
|
||||||
header: Kirigami.Heading {
|
|
||||||
text: i18n("Edit Account")
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.FormLayout {
|
|
||||||
anchors.top: passwordsMessage.bottom
|
|
||||||
Controls.TextField {
|
|
||||||
id: name
|
|
||||||
text: userEditSheet.connection.localUser.displayName
|
|
||||||
Kirigami.FormData.label: i18n("Name:")
|
|
||||||
}
|
|
||||||
Controls.TextField {
|
|
||||||
id: currentPassword
|
|
||||||
Kirigami.FormData.label: i18n("Current Password:")
|
|
||||||
echoMode: TextInput.Password
|
|
||||||
}
|
|
||||||
Controls.TextField {
|
|
||||||
id: newPassword
|
|
||||||
Kirigami.FormData.label: i18n("New Password:")
|
|
||||||
echoMode: TextInput.Password
|
|
||||||
|
|
||||||
}
|
|
||||||
Controls.TextField {
|
|
||||||
id: confirmPassword
|
|
||||||
Kirigami.FormData.label: i18n("Confirm new Password:")
|
|
||||||
echoMode: TextInput.Password
|
|
||||||
}
|
|
||||||
|
|
||||||
Controls.Button {
|
|
||||||
text: i18n("Save")
|
|
||||||
onClicked: {
|
|
||||||
if(userEditSheet.connection.localUser.displayName !== name.text)
|
|
||||||
userEditSheet.connection.localUser.rename(name.text)
|
|
||||||
if(currentPassword.text !== "" && newPassword.text !== "" && confirmPassword.text !== "") {
|
|
||||||
if(newPassword.text === confirmPassword.text) {
|
|
||||||
Controller.changePassword(userEditSheet.connection, currentPassword.text, newPassword.text)
|
|
||||||
} else {
|
|
||||||
showPassiveNotification(i18n("Passwords do not match"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
userEditSheet.close()
|
|
||||||
currentPassword.text = ""
|
|
||||||
newPassword.text = ""
|
|
||||||
confirmPassword.text = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
/*
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
* SPDX-FileCopyrightText: (C) 2020 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick 2.10
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.1 as QQC2
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.15
|
||||||
import org.kde.kirigami 2.12 as Kirigami
|
import QtGraphicalEffects 1.15
|
||||||
import QtQuick.Dialogs 1.2
|
import Qt.labs.platform 1.1 as Platform
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
import org.kde.kquickimageeditor 1.0 as KQuickImageEditor
|
import org.kde.kquickimageeditor 1.0 as KQuickImageEditor
|
||||||
import QtGraphicalEffects 1.12
|
|
||||||
import Qt.labs.platform 1.0 as Platform
|
|
||||||
|
|
||||||
Kirigami.Page {
|
Kirigami.Page {
|
||||||
id: rootEditorView
|
id: rootEditorView
|
||||||
@@ -24,14 +21,14 @@ Kirigami.Page {
|
|||||||
title: i18n("Edit")
|
title: i18n("Edit")
|
||||||
leftPadding: 0
|
leftPadding: 0
|
||||||
rightPadding: 0
|
rightPadding: 0
|
||||||
|
topPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
|
||||||
function crop() {
|
function crop() {
|
||||||
const ratioX = editImage.paintedWidth / editImage.nativeWidth;
|
const ratioX = editImage.paintedWidth / editImage.nativeWidth;
|
||||||
const ratioY = editImage.paintedHeight / editImage.nativeHeight;
|
const ratioY = editImage.paintedHeight / editImage.nativeHeight;
|
||||||
rootEditorView.resizing = false
|
rootEditorView.resizing = false
|
||||||
imageDoc.crop(resizeRectangle.insideX / ratioX, resizeRectangle.insideY / ratioY, resizeRectangle.insideWidth / ratioX, resizeRectangle.insideHeight / ratioY);
|
imageDoc.crop(selectionTool.selectionX / ratioX, selectionTool.selectionY / ratioY, selectionTool.selectionWidth / ratioX, selectionTool.selectionHeight / ratioY);
|
||||||
}
|
}
|
||||||
|
|
||||||
actions {
|
actions {
|
||||||
@@ -61,8 +58,11 @@ Kirigami.Page {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
contentItem: KQuickImageEditor.ImageItem {
|
KQuickImageEditor.ImageItem {
|
||||||
id: editImage
|
id: editImage
|
||||||
|
// Assigning this to the contentItem and setting the padding causes weird positioning issues
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Kirigami.Units.gridUnit
|
||||||
fillMode: KQuickImageEditor.ImageItem.PreserveAspectFit
|
fillMode: KQuickImageEditor.ImageItem.PreserveAspectFit
|
||||||
image: imageDoc.image
|
image: imageDoc.image
|
||||||
|
|
||||||
@@ -79,27 +79,41 @@ Kirigami.Page {
|
|||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: StandardKey.SaveAs
|
sequence: StandardKey.SaveAs
|
||||||
onActivated: saveAsAction.trigger();
|
onActivated: saveAsAction.trigger();
|
||||||
} anchors.fill: parent
|
|
||||||
|
|
||||||
FileDialog {
|
|
||||||
id: fileDialog
|
|
||||||
title: i18n("Save As")
|
|
||||||
folder: shortcuts.home
|
|
||||||
selectMultiple: false
|
|
||||||
selectExisting: false
|
|
||||||
onAccepted: {
|
|
||||||
fileDialog.close()
|
|
||||||
}
|
|
||||||
onRejected: {
|
|
||||||
fileDialog.close()
|
|
||||||
}
|
|
||||||
Component.onCompleted: visible = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KQuickImageEditor.ImageDocument {
|
KQuickImageEditor.ImageDocument {
|
||||||
id: imageDoc
|
id: imageDoc
|
||||||
path: rootEditorView.imagePath
|
path: rootEditorView.imagePath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
KQuickImageEditor.SelectionTool {
|
||||||
|
id: selectionTool
|
||||||
|
visible: rootEditorView.resizing
|
||||||
|
width: editImage.paintedWidth
|
||||||
|
height: editImage.paintedHeight
|
||||||
|
x: editImage.horizontalPadding
|
||||||
|
y: editImage.verticalPadding
|
||||||
|
KQuickImageEditor.CropBackground {
|
||||||
|
anchors.fill: parent
|
||||||
|
z: -1
|
||||||
|
insideX: selectionTool.selectionX
|
||||||
|
insideY: selectionTool.selectionY
|
||||||
|
insideWidth: selectionTool.selectionWidth
|
||||||
|
insideHeight: selectionTool.selectionHeight
|
||||||
|
}
|
||||||
|
Connections {
|
||||||
|
target: selectionTool.selectionArea
|
||||||
|
function onDoubleClicked() {
|
||||||
|
rootEditorView.crop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onImageChanged: {
|
||||||
|
selectionTool.selectionX = 0
|
||||||
|
selectionTool.selectionY = 0
|
||||||
|
selectionTool.selectionWidth = Qt.binding(() => selectionTool.width)
|
||||||
|
selectionTool.selectionHeight = Qt.binding(() => selectionTool.height)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
header: QQC2.ToolBar {
|
header: QQC2.ToolBar {
|
||||||
@@ -110,7 +124,18 @@ Kirigami.Page {
|
|||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
iconName: rootEditorView.resizing ? "dialog-cancel" : "transform-crop"
|
iconName: rootEditorView.resizing ? "dialog-cancel" : "transform-crop"
|
||||||
text: rootEditorView.resizing ? i18n("Cancel") : i18nc("@action:button Crop an image", "Crop");
|
text: rootEditorView.resizing ? i18n("Cancel") : i18nc("@action:button Crop an image", "Crop");
|
||||||
onTriggered: rootEditorView.resizing = !rootEditorView.resizing;
|
onTriggered: {
|
||||||
|
resizeRectangle.width = editImage.paintedWidth
|
||||||
|
resizeRectangle.height = editImage.paintedHeight
|
||||||
|
resizeRectangle.x = editImage.horizontalPadding
|
||||||
|
resizeRectangle.y = editImage.verticalPadding
|
||||||
|
resizeRectangle.insideX = 100
|
||||||
|
resizeRectangle.insideY = 100
|
||||||
|
resizeRectangle.insideWidth = 100
|
||||||
|
resizeRectangle.insideHeight = 100
|
||||||
|
|
||||||
|
rootEditorView.resizing = !rootEditorView.resizing;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Kirigami.Action {
|
Kirigami.Action {
|
||||||
iconName: "dialog-ok"
|
iconName: "dialog-ok"
|
||||||
@@ -152,40 +177,4 @@ Kirigami.Page {
|
|||||||
showCloseButton: true
|
showCloseButton: true
|
||||||
visible: false
|
visible: false
|
||||||
}
|
}
|
||||||
|
|
||||||
KQuickImageEditor.ResizeRectangle {
|
|
||||||
id: resizeRectangle
|
|
||||||
|
|
||||||
visible: rootEditorView.resizing
|
|
||||||
|
|
||||||
width: editImage.paintedWidth
|
|
||||||
height: editImage.paintedHeight
|
|
||||||
x: 0
|
|
||||||
y: editImage.verticalPadding
|
|
||||||
|
|
||||||
insideX: 100
|
|
||||||
insideY: 100
|
|
||||||
insideWidth: 100
|
|
||||||
insideHeight: 100
|
|
||||||
|
|
||||||
onAcceptSize: rootEditorView.crop();
|
|
||||||
|
|
||||||
//resizeHandle: KQuickImageEditor.BasicResizeHandle { }
|
|
||||||
|
|
||||||
/*Rectangle {
|
|
||||||
radius: 2
|
|
||||||
width: Kirigami.Units.gridUnit * 8
|
|
||||||
height: Kirigami.Units.gridUnit * 3
|
|
||||||
anchors.centerIn: parent
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
|
||||||
color: Kirigami.Theme.backgroundColor
|
|
||||||
QQC2.Label {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "x: " + (resizeRectangle.x - rootEditorView.contentItem.width + editImage.paintedWidth)
|
|
||||||
+ " y: " + (resizeRectangle.y - rootEditorView.contentItem.height + editImage.paintedHeight)
|
|
||||||
+ "\nwidth: " + resizeRectangle.width
|
|
||||||
+ " height: " + resizeRectangle.height
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12
|
|
||||||
import org.kde.kirigami 2.14 as Kirigami
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
|
|
||||||
Kirigami.Page {
|
|
||||||
id: root
|
|
||||||
property var room
|
|
||||||
title: i18n("Invitation Received - %1", room.displayName)
|
|
||||||
|
|
||||||
Kirigami.PlaceholderMessage {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: i18n("Accept this invitation?")
|
|
||||||
RowLayout {
|
|
||||||
Button {
|
|
||||||
Layout.alignment : Qt.AlignHCenter
|
|
||||||
text: i18n("Cancel")
|
|
||||||
|
|
||||||
onClicked: roomManager.getBack();
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
Layout.alignment : Qt.AlignHCenter
|
|
||||||
text: i18n("Reject")
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
room.forget()
|
|
||||||
roomManager.getBack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
Layout.alignment : Qt.AlignHCenter
|
|
||||||
text: i18n("Accept")
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
room.acceptInvitation();
|
|
||||||
roomManager.enterRoom(room);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
import QtQuick 2.15
|
||||||
*/
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick 2.12
|
import QtQuick.Layouts 1.15
|
||||||
import QtQuick.Controls 2.12
|
|
||||||
import QtQuick.Layouts 1.12
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
import org.kde.kirigami 2.14 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
@@ -15,15 +14,13 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
property var room
|
property var room
|
||||||
|
|
||||||
parent: applicationWindow().overlay
|
|
||||||
|
|
||||||
title: i18n("Invite a User")
|
title: i18n("Invite a User")
|
||||||
|
|
||||||
actions {
|
actions {
|
||||||
main: Kirigami.Action {
|
main: Kirigami.Action {
|
||||||
icon.name: "dialog-close"
|
icon.name: "dialog-close"
|
||||||
text: i18nc("@action", "Cancel")
|
text: i18nc("@action", "Cancel")
|
||||||
onTriggered: applicationWindow().pageStack.pop()
|
onTriggered: applicationWindow().pageStack.layers.pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header: RowLayout {
|
header: RowLayout {
|
||||||
@@ -86,7 +83,7 @@ Kirigami.ScrollablePage {
|
|||||||
Layout.preferredWidth: height
|
Layout.preferredWidth: height
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
source: avatar ? "image://mxc/" + avatar : ""
|
source: avatar ? ("image://mxc/" + avatar) : ""
|
||||||
name: name
|
name: name
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +122,7 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
room.inviteToRoom(userID);
|
room.inviteToRoom(userID);
|
||||||
applicationWindow().pageStack.pop();
|
applicationWindow().pageStack.layers.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
import QtQuick 2.15
|
||||||
*/
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick 2.12
|
import QtQuick.Layouts 1.15
|
||||||
import QtQuick.Controls 2.12
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
import NeoChat.Component 1.0
|
import NeoChat.Component 1.0
|
||||||
import NeoChat.Effect 1.0
|
|
||||||
import NeoChat.Setting 1.0
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
Kirigami.ScrollablePage {
|
Kirigami.ScrollablePage {
|
||||||
id: root
|
id: root
|
||||||
@@ -23,22 +17,21 @@ Kirigami.ScrollablePage {
|
|||||||
property alias keyword: identifierField.text
|
property alias keyword: identifierField.text
|
||||||
property string server
|
property string server
|
||||||
|
|
||||||
signal joinRoom(string room)
|
|
||||||
|
|
||||||
title: i18n("Explore Rooms")
|
title: i18n("Explore Rooms")
|
||||||
|
|
||||||
|
Component.onCompleted: identifierField.forceActiveFocus()
|
||||||
|
|
||||||
header: Control {
|
header: Control {
|
||||||
padding: Kirigami.Units.largeSpacing
|
padding: Kirigami.Units.largeSpacing
|
||||||
contentItem: RowLayout {
|
contentItem: RowLayout {
|
||||||
Kirigami.SearchField {
|
Kirigami.SearchField {
|
||||||
|
id: identifierField
|
||||||
property bool isRoomAlias: text.match(/#(.+):(.+)/g)
|
property bool isRoomAlias: text.match(/#(.+):(.+)/g)
|
||||||
property var room: isRoomAlias ? connection.roomByAlias(text) : null
|
property var room: isRoomAlias ? connection.roomByAlias(text) : null
|
||||||
property bool isJoined: room != null
|
property bool isJoined: room != null
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
id: identifierField
|
|
||||||
|
|
||||||
placeholderText: i18n("Find a room...")
|
placeholderText: i18n("Find a room...")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,9 +45,9 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!identifierField.isJoined) {
|
if (!identifierField.isJoined) {
|
||||||
Controller.joinRoom(connection, identifierField.text);
|
Controller.joinRoom(identifierField.text);
|
||||||
|
// When joining the room, the room will be opened
|
||||||
}
|
}
|
||||||
roomManager.enterRoom(connection.room(identifierField.room));
|
|
||||||
applicationWindow().pageStack.layers.pop();
|
applicationWindow().pageStack.layers.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,19 +98,19 @@ Kirigami.ScrollablePage {
|
|||||||
width: publicRoomsListView.width
|
width: publicRoomsListView.width
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!isJoined) {
|
if (!isJoined) {
|
||||||
Controller.joinRoom(connection, roomID)
|
Controller.joinRoom(roomID)
|
||||||
justJoined = true;
|
justJoined = true;
|
||||||
} else {
|
} else {
|
||||||
roomManager.enterRoom(connection.room(roomID))
|
RoomManager.enterRoom(connection.room(roomID))
|
||||||
applicationWindow().pageStack.layers.pop();
|
|
||||||
}
|
}
|
||||||
|
applicationWindow().pageStack.layers.pop();
|
||||||
}
|
}
|
||||||
contentItem: RowLayout {
|
contentItem: RowLayout {
|
||||||
Kirigami.Avatar {
|
Kirigami.Avatar {
|
||||||
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||||
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||||
|
|
||||||
source: model.avatar ? "image://mxc/" + model.avatar : ""
|
source: model.avatar ? ("image://mxc/" + model.avatar) : ""
|
||||||
name: name
|
name: name
|
||||||
}
|
}
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
import QtQuick.Layouts 1.15
|
||||||
*/
|
|
||||||
import org.kde.kirigami 2.12 as Kirigami
|
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
import QtQuick.Controls 2.12 as QQC2
|
||||||
|
import org.kde.kirigami 2.12 as Kirigami
|
||||||
|
|
||||||
Kirigami.Page {
|
Kirigami.Page {
|
||||||
title: i18n("Loading")
|
title: i18n("Loading")
|
||||||
|
|
||||||
QQC2.BusyIndicator {
|
Kirigami.PlaceholderMessage {
|
||||||
|
id: loadingIndicator
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
text: i18n("Loading")
|
||||||
|
QQC2.BusyIndicator {
|
||||||
|
running: loadingIndicator.visible
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
import NeoChat.Component 1.0
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.12 as Kirigami
|
|
||||||
|
|
||||||
Kirigami.ScrollablePage {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
title: i18n("Login")
|
|
||||||
|
|
||||||
header: QQC2.Control {
|
|
||||||
padding: Kirigami.Units.smallSpacing
|
|
||||||
contentItem: Kirigami.InlineMessage {
|
|
||||||
id: inlineMessage
|
|
||||||
visible: false
|
|
||||||
showCloseButton: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.FormLayout {
|
|
||||||
id: formLayout
|
|
||||||
QQC2.TextField {
|
|
||||||
id: serverField
|
|
||||||
Kirigami.FormData.label: i18n("Server Address")
|
|
||||||
text: "https://matrix.org"
|
|
||||||
onAccepted: usernameField.forceActiveFocus()
|
|
||||||
}
|
|
||||||
QQC2.TextField {
|
|
||||||
id: usernameField
|
|
||||||
Kirigami.FormData.label: i18n("Username")
|
|
||||||
onAccepted: passwordField.forceActiveFocus()
|
|
||||||
}
|
|
||||||
Kirigami.PasswordField {
|
|
||||||
id: passwordField
|
|
||||||
Kirigami.FormData.label: i18n("Password")
|
|
||||||
onAccepted: accessTokenField.forceActiveFocus()
|
|
||||||
}
|
|
||||||
QQC2.TextField {
|
|
||||||
id: accessTokenField
|
|
||||||
Kirigami.FormData.label: i18n("Access Token (Optional)")
|
|
||||||
onAccepted: deviceNameField.forceActiveFocus()
|
|
||||||
}
|
|
||||||
QQC2.TextField {
|
|
||||||
id: deviceNameField
|
|
||||||
Kirigami.FormData.label: i18n("Device Name (Optional)")
|
|
||||||
onAccepted: doLogin()
|
|
||||||
}
|
|
||||||
RowLayout {
|
|
||||||
QQC2.Button {
|
|
||||||
visible: Controller.accountCount > 0
|
|
||||||
text: i18n("Cancel")
|
|
||||||
onClicked: {
|
|
||||||
pageStack.layers.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.Button {
|
|
||||||
text: i18n("Login")
|
|
||||||
onClicked: doLogin()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: Controller
|
|
||||||
onErrorOccured: {
|
|
||||||
inlineMessage.type = Kirigami.MessageType.Error;
|
|
||||||
if (detail && detail.length !== 0) {
|
|
||||||
inlineMessage.text = i18n("%1: %2", error, detail);
|
|
||||||
} else {
|
|
||||||
inlineMessage.text = error;
|
|
||||||
}
|
|
||||||
inlineMessage.visible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function doLogin() {
|
|
||||||
inlineMessage.text = i18n("Loading, this might take up to 10 seconds.");
|
|
||||||
inlineMessage.type = Kirigami.MessageType.Information
|
|
||||||
inlineMessage.visible = true;
|
|
||||||
if (accessTokenField.text.length > 0) {
|
|
||||||
Controller.loginWithAccessToken(serverField.text.trim(), usernameField.text.trim(), accessTokenField.text, deviceNameField.text.trim());
|
|
||||||
} else {
|
|
||||||
Controller.loginWithCredentials(serverField.text.trim(), usernameField.text.trim(), passwordField.text, deviceNameField.text.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +1,50 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQml.Models 2.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
import org.kde.kitemmodels 1.0
|
import org.kde.kitemmodels 1.0
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
import NeoChat.Component 1.0
|
import NeoChat.Component 1.0
|
||||||
import NeoChat.Menu 1.0
|
import NeoChat.Menu 1.0
|
||||||
|
|
||||||
Kirigami.ScrollablePage {
|
Kirigami.ScrollablePage {
|
||||||
id: page
|
id: page
|
||||||
|
|
||||||
property var roomListModel
|
title: i18n("Rooms")
|
||||||
property var enteredRoom
|
|
||||||
required property var activeConnection
|
|
||||||
|
|
||||||
signal enterRoom(var room)
|
property var enteredRoom
|
||||||
signal leaveRoom(var room)
|
property bool collapsedMode: Config.roomListPageWidth === applicationWindow().collapsedPageWidth && applicationWindow().shouldUseSidebars
|
||||||
|
|
||||||
|
onCollapsedModeChanged: if (collapsedMode) {
|
||||||
|
sortFilterRoomListModel.filterText = "";
|
||||||
|
if (page.contentItem && page.contentItem.flickableItem && page.contentItem.flickableItem.QQC2.ScrollBar.vertical) {
|
||||||
|
page.contentItem.flickableItem.QQC2.ScrollBar.vertical.visible = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
page.contentItem.flickableItem.QQC2.ScrollBar.vertical.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HACK: the scrollbar is created with a 0 timer, so we need to set the visible flag
|
||||||
|
// after it has been created
|
||||||
|
Timer {
|
||||||
|
running: true
|
||||||
|
interval: 200
|
||||||
|
onTriggered: page.contentItem.flickableItem.QQC2.ScrollBar.vertical.visible = !collapsedMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: RoomManager
|
||||||
|
function onCurrentRoomChanged() {
|
||||||
|
itemSelection.setCurrentIndex(roomListModel.index(roomListModel.indexForRoom(RoomManager.currentRoom), 0), ItemSelectionModel.SelectCurrent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function goToNextRoom() {
|
function goToNextRoom() {
|
||||||
do {
|
do {
|
||||||
@@ -39,19 +60,57 @@ Kirigami.ScrollablePage {
|
|||||||
listView.currentItem.action.trigger();
|
listView.currentItem.action.trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
title: i18n("Rooms")
|
titleDelegate: collapsedMode ? empty : searchField
|
||||||
|
|
||||||
titleDelegate: Kirigami.SearchField {
|
Component {
|
||||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
id: empty
|
||||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
Item {}
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
onTextChanged: sortFilterRoomListModel.filterText = text
|
|
||||||
KeyNavigation.tab: listView
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: searchField
|
||||||
|
Kirigami.SearchField {
|
||||||
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
|
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
onTextChanged: sortFilterRoomListModel.filterText = text
|
||||||
|
KeyNavigation.tab: listView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header: QQC2.ItemDelegate {
|
||||||
|
visible: page.collapsedMode
|
||||||
|
action: Kirigami.Action {
|
||||||
|
id: enterRoomAction
|
||||||
|
onTriggered: quickView.item.open();
|
||||||
|
}
|
||||||
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
leftPadding: Kirigami.Units.largeSpacing
|
||||||
|
rightPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
|
width: visible ? page.width : 0
|
||||||
|
height: visible ? Kirigami.Units.gridUnit * 2 : 0
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 22
|
||||||
|
height: 22
|
||||||
|
source: "search"
|
||||||
|
}
|
||||||
|
Kirigami.Separator {
|
||||||
|
width: parent.width
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: listView
|
id: listView
|
||||||
|
|
||||||
|
activeFocusOnTab: true
|
||||||
|
clip: accountList.count > 1
|
||||||
|
|
||||||
Kirigami.PlaceholderMessage {
|
Kirigami.PlaceholderMessage {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width - (Kirigami.Units.largeSpacing * 4)
|
width: parent.width - (Kirigami.Units.largeSpacing * 4)
|
||||||
@@ -60,150 +119,201 @@ Kirigami.ScrollablePage {
|
|||||||
helpfulAction: Kirigami.Action {
|
helpfulAction: Kirigami.Action {
|
||||||
icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add"
|
icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add"
|
||||||
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms")
|
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms")
|
||||||
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {"connection": activeConnection, "keyword": sortFilterRoomListModel.filterText})
|
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/JoinRoomPage.qml", {
|
||||||
|
connection: Controller.activeConnection,
|
||||||
|
keyword: sortFilterRoomListModel.filterText
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model: SortFilterRoomListModel {
|
|
||||||
|
|
||||||
|
ItemSelectionModel {
|
||||||
|
id: itemSelection
|
||||||
|
model: roomListModel
|
||||||
|
onCurrentChanged: {
|
||||||
|
listView.currentIndex = sortFilterRoomListModel.mapFromSource(current).row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model: SortFilterRoomListModel {
|
||||||
id: sortFilterRoomListModel
|
id: sortFilterRoomListModel
|
||||||
sourceModel: roomListModel
|
sourceModel: RoomListModel {
|
||||||
|
id: roomListModel
|
||||||
|
connection: Controller.activeConnection
|
||||||
|
}
|
||||||
roomSortOrder: Config.mergeRoomList ? SortFilterRoomListModel.LastActivity : SortFilterRoomListModel.Categories
|
roomSortOrder: Config.mergeRoomList ? SortFilterRoomListModel.LastActivity : SortFilterRoomListModel.Categories
|
||||||
|
onLayoutChanged: {
|
||||||
|
listView.currentIndex = sortFilterRoomListModel.mapFromSource(itemSelection.currentIndex).row
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null
|
section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null
|
||||||
section.delegate: Kirigami.ListSectionHeader {
|
section.delegate: Kirigami.ListSectionHeader {
|
||||||
id: sectionHeader
|
id: sectionHeader
|
||||||
|
height: implicitHeight
|
||||||
action: Kirigami.Action {
|
action: Kirigami.Action {
|
||||||
onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
||||||
}
|
}
|
||||||
contentItem: RowLayout {
|
contentItem: RowLayout {
|
||||||
implicitHeight: categoryName.implicitHeight
|
implicitHeight: categoryName.implicitHeight
|
||||||
Kirigami.Icon {
|
|
||||||
source: roomListModel.categoryVisible(section) ? "go-up" : "go-down"
|
|
||||||
implicitHeight: Kirigami.Units.iconSizes.small
|
|
||||||
implicitWidth: Kirigami.Units.iconSizes.small
|
|
||||||
}
|
|
||||||
Kirigami.Heading {
|
Kirigami.Heading {
|
||||||
id: categoryName
|
id: categoryName
|
||||||
level: 3
|
level: 3
|
||||||
text: roomListModel.categoryName(section)
|
text: roomListModel.categoryName(section)
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
visible: !page.collapsedMode
|
||||||
|
}
|
||||||
|
Kirigami.Icon {
|
||||||
|
source: page.collapsedMode ? roomListModel.categoryIconName(section) : (roomListModel.categoryVisible(section) ? "go-up" : "go-down")
|
||||||
|
implicitHeight: Kirigami.Units.iconSizes.small
|
||||||
|
implicitWidth: Kirigami.Units.iconSizes.small
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: Kirigami.AbstractListItem {
|
reuseItems: true
|
||||||
id: roomListItem
|
currentIndex: -1 // we don't want any room highlighted by default
|
||||||
property bool itemVisible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList
|
|
||||||
visible: itemVisible
|
delegate: page.collapsedMode ? collapsedModeListComponent : normalModeListComponent
|
||||||
highlighted: roomManager.currentRoom && roomManager.currentRoom.name === name
|
|
||||||
focus: true
|
Component {
|
||||||
action: Kirigami.Action {
|
id: collapsedModeListComponent
|
||||||
id: enterRoomAction
|
|
||||||
onTriggered: {
|
QQC2.ItemDelegate {
|
||||||
if (category === RoomType.Invited) {
|
action: Kirigami.Action {
|
||||||
roomManager.openInvitation(currentRoom);
|
id: enterRoomAction
|
||||||
} else {
|
onTriggered: {
|
||||||
var roomItem = roomManager.enterRoom(currentRoom)
|
RoomManager.enterRoom(currentRoom);
|
||||||
roomListItem.KeyNavigation.right = roomItem
|
|
||||||
roomItem.focus = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Keys.onEnterPressed: enterRoomAction.trigger()
|
||||||
|
Keys.onReturnPressed: enterRoomAction.trigger()
|
||||||
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
contentItem: RowLayout {
|
leftPadding: Kirigami.Units.largeSpacing
|
||||||
id: roomLayout
|
rightPadding: Kirigami.Units.largeSpacing
|
||||||
spacing: Kirigami.Units.largeSpacing
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
width: listView.width
|
width: ListView.view.width
|
||||||
|
height: ListView.view.width
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
onTapped: roomListContextMenu.createObject(roomLayout, {"room": currentRoom}).popup()
|
|
||||||
}
|
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
onTapped: enterRoomAction.trigger()
|
|
||||||
onLongPressed: roomListContextMenu.createObject(roomLayout, {"room": currentRoom}).popup()
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Avatar {
|
|
||||||
id: roomAvatar
|
|
||||||
property int size: Kirigami.Units.gridUnit * 2 + Kirigami.Units.smallSpacing
|
|
||||||
Layout.minimumHeight: size
|
|
||||||
Layout.maximumHeight: size
|
|
||||||
Layout.minimumWidth: size
|
|
||||||
Layout.maximumWidth: size
|
|
||||||
|
|
||||||
|
contentItem: Kirigami.Avatar {
|
||||||
source: avatar ? "image://mxc/" + avatar : ""
|
source: avatar ? "image://mxc/" + avatar : ""
|
||||||
name: model.name || i18n("No Name")
|
name: model.name || i18n("No Name")
|
||||||
|
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
QQC2.ToolTip {
|
||||||
id: roomitemcolumn
|
enabled: text.length !== 0
|
||||||
Layout.fillWidth: true
|
text: name ?? ""
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 2
|
|
||||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 2
|
|
||||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
|
||||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
QQC2.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
text: name ?? ""
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.bold: unreadCount >= 0 || highlightCount > 0 || notificationCount > 0
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
text: (lastEvent == "" ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm," ")
|
|
||||||
visible: text.length > 0
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.7)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.Label {
|
|
||||||
text: notificationCount
|
|
||||||
visible: notificationCount > 0
|
|
||||||
padding: Kirigami.Units.smallSpacing
|
|
||||||
color: highlightCount > 0 ? "white" : Kirigami.Theme.textColor
|
|
||||||
Layout.minimumWidth: height
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
background: Rectangle {
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
|
||||||
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
|
|
||||||
radius: height / 2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: roomListContextMenu
|
id: roomListContextMenu
|
||||||
RoomListContextMenu {}
|
RoomListContextMenu {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: normalModeListComponent
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
id: roomListItem
|
||||||
|
visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList
|
||||||
|
topPadding: Kirigami.Units.largeSpacing
|
||||||
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
|
highlighted: listView.currentIndex === index
|
||||||
|
focus: true
|
||||||
|
icon: undefined
|
||||||
|
action: Kirigami.Action {
|
||||||
|
id: enterRoomAction
|
||||||
|
onTriggered: {
|
||||||
|
RoomManager.enterRoom(currentRoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Keys.onEnterPressed: enterRoomAction.trigger()
|
||||||
|
Keys.onReturnPressed: enterRoomAction.trigger()
|
||||||
|
bold: unreadCount > 0
|
||||||
|
label: name ?? ""
|
||||||
|
subtitle: {
|
||||||
|
let txt = (lastEvent.length === 0 ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm, " ")
|
||||||
|
if (txt.length) {
|
||||||
|
return txt
|
||||||
|
}
|
||||||
|
return " "
|
||||||
|
}
|
||||||
|
|
||||||
|
leading: Kirigami.Avatar {
|
||||||
|
source: avatar ? "image://mxc/" + avatar : ""
|
||||||
|
name: model.name || i18n("No Name")
|
||||||
|
implicitWidth: visible ? height : 0
|
||||||
|
visible: Config.showAvatarInTimeline
|
||||||
|
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
trailing: RowLayout {
|
||||||
|
QQC2.Label {
|
||||||
|
text: notificationCount
|
||||||
|
visible: notificationCount > 0
|
||||||
|
padding: Kirigami.Units.smallSpacing
|
||||||
|
color: highlightCount > 0 ? "white" : Kirigami.Theme.textColor
|
||||||
|
Layout.minimumWidth: height
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
background: Rectangle {
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
||||||
|
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.backgroundColor
|
||||||
|
radius: height / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.Button {
|
||||||
|
id: configButton
|
||||||
|
visible: roomListItem.hovered || Kirigami.Settings.isMobile
|
||||||
|
Accessible.name: i18n("Configure room")
|
||||||
|
|
||||||
|
action: Kirigami.Action {
|
||||||
|
id: optionAction
|
||||||
|
icon.name: "configure"
|
||||||
|
onTriggered: {
|
||||||
|
const menu = roomListContextMenu.createObject(page, {"room": currentRoom})
|
||||||
|
configButton.visible = true
|
||||||
|
configButton.down = true
|
||||||
|
menu.closed.connect(function() {
|
||||||
|
configButton.down = undefined
|
||||||
|
configButton.visible = Qt.binding(function() { return roomListItem.hovered || Kirigami.Settings.isMobile })
|
||||||
|
})
|
||||||
|
menu.popup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer: RowLayout {
|
footer: QQC2.ToolBar {
|
||||||
visible: accountListTab.count > 1
|
visible: accountList.count > 1 && !collapsedMode
|
||||||
height: visible ? accountListTab.implicitHeight : 0
|
height: visible ? implicitHeight : 0
|
||||||
Repeater {
|
leftPadding: 0
|
||||||
id: accountListTab
|
rightPadding: 0
|
||||||
model: AccountListModel { }
|
topPadding: 0
|
||||||
delegate: QQC2.TabButton {
|
bottomPadding: 0
|
||||||
checkable: true
|
contentItem: RowLayout {
|
||||||
checked: Controller.activeConnection.user.id === model.connection.user.id
|
spacing: 0
|
||||||
onClicked: Controller.activeConnection = model.connection
|
Repeater {
|
||||||
Layout.fillWidth: true
|
id: accountList
|
||||||
text: model.user.id
|
model: AccountListModel {
|
||||||
|
id: accountListModel
|
||||||
|
}
|
||||||
|
delegate: Kirigami.BasicListItem {
|
||||||
|
checkable: true
|
||||||
|
checked: Controller.activeConnection && Controller.activeConnection.localUser.id === model.user.id
|
||||||
|
onClicked: Controller.activeConnection = model.connection
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
text: model.user.id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
19
imports/NeoChat/Page/RoomWindow.qml
Normal file
19
imports/NeoChat/Page/RoomWindow.qml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
Kirigami.ApplicationWindow {
|
||||||
|
id: window
|
||||||
|
required property var currentRoom
|
||||||
|
minimumWidth: Kirigami.Units.gridUnit * 10
|
||||||
|
minimumHeight: Kirigami.Units.gridUnit * 15
|
||||||
|
pageStack.initialPage: RoomPage {
|
||||||
|
visible: true
|
||||||
|
currentRoom: window.currentRoom
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick 2.14
|
|
||||||
import QtQuick.Controls 2.14 as QQC2
|
|
||||||
import QtQuick.Layouts 1.14
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.12 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
Kirigami.ScrollablePage {
|
|
||||||
title: i18n("Settings")
|
|
||||||
|
|
||||||
Kirigami.FormLayout {
|
|
||||||
QQC2.CheckBox {
|
|
||||||
// TODO: When there are enough notification and timeline event
|
|
||||||
// settings, make 2 separate groups with FormData labels.
|
|
||||||
Kirigami.FormData.label: i18n("Notifications and events:")
|
|
||||||
text: i18n("Show notifications")
|
|
||||||
checked: Config.showNotifications
|
|
||||||
onToggled: Config.showNotifications = checked
|
|
||||||
}
|
|
||||||
QQC2.CheckBox {
|
|
||||||
text: i18n("Show leave and join events")
|
|
||||||
checked: Config.showLeaveJoinEvent
|
|
||||||
onToggled: Config.showLeaveJoinEvent = checked
|
|
||||||
}
|
|
||||||
QQC2.RadioButton {
|
|
||||||
Kirigami.FormData.label: i18n("Rooms and private chats:")
|
|
||||||
text: i18n("Separated")
|
|
||||||
checked: !Config.mergeRoomList
|
|
||||||
onToggled: Config.mergeRoomList = false
|
|
||||||
}
|
|
||||||
QQC2.RadioButton {
|
|
||||||
text: i18n("Intermixed")
|
|
||||||
checked: Config.mergeRoomList
|
|
||||||
onToggled: Config.mergeRoomList = true
|
|
||||||
}
|
|
||||||
QQC2.CheckBox {
|
|
||||||
Kirigami.FormData.label: i18n("Timeline:")
|
|
||||||
text: i18n("Show User Avatar")
|
|
||||||
checked: Config.showAvatarInTimeline
|
|
||||||
onToggled: Config.showAvatarInTimeline = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,15 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
import org.kde.kirigami 2.14 as Kirigami
|
|
||||||
|
|
||||||
import NeoChat.Component 1.0
|
import QtQuick 2.15
|
||||||
import NeoChat.Effect 1.0
|
import QtQuick.Controls 2.15
|
||||||
import NeoChat.Setting 1.0
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component 1.0
|
||||||
|
|
||||||
Kirigami.ScrollablePage {
|
Kirigami.ScrollablePage {
|
||||||
id: root
|
id: root
|
||||||
@@ -43,7 +39,10 @@ Kirigami.ScrollablePage {
|
|||||||
text: i18n("Chat")
|
text: i18n("Chat")
|
||||||
highlighted: true
|
highlighted: true
|
||||||
|
|
||||||
onClicked: Controller.createDirectChat(connection, identifierField.text)
|
onClicked: {
|
||||||
|
connection.requestDirectChat(identifierField.text);
|
||||||
|
applicationWindow().pageStack.layers.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,23 +103,27 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
id: joinChatButton
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
visible: directChats != null
|
visible: directChats && directChats.length > 0
|
||||||
|
|
||||||
icon.name: "document-send"
|
icon.name: "document-send"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
roomListForm.joinRoom(connection.room(directChats[0]))
|
connection.requestDirectChat(userID);
|
||||||
root.close()
|
applicationWindow().pageStack.layers.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
icon.name: "irc-join-channel"
|
icon.name: "irc-join-channel"
|
||||||
|
// We wants to make sure an user can't start more than one
|
||||||
|
// chat with someone.
|
||||||
|
visible: !joinChatButton.visible
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
Controller.createDirectChat(connection, userID)
|
connection.requestDirectChat(userID);
|
||||||
root.close()
|
applicationWindow().pageStack.layers.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
97
imports/NeoChat/Page/WelcomePage.qml
Normal file
97
imports/NeoChat/Page/WelcomePage.qml
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as Controls
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component.Login 1.0
|
||||||
|
|
||||||
|
Kirigami.ScrollablePage {
|
||||||
|
id: welcomePage
|
||||||
|
|
||||||
|
property alias currentStep: module.item
|
||||||
|
|
||||||
|
title: module.item.title ?? i18n("Welcome")
|
||||||
|
|
||||||
|
header: Controls.Control {
|
||||||
|
contentItem: Kirigami.InlineMessage {
|
||||||
|
id: headerMessage
|
||||||
|
type: Kirigami.MessageType.Error
|
||||||
|
showCloseButton: true
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: LoginHelper.init()
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: LoginHelper
|
||||||
|
function onErrorOccured(message) {
|
||||||
|
headerMessage.text = message;
|
||||||
|
headerMessage.visible = true;
|
||||||
|
headerMessage.type = Kirigami.MessageType.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Kirigami.Icon {
|
||||||
|
source: "org.kde.neochat"
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 16
|
||||||
|
}
|
||||||
|
Controls.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
font.pixelSize: 25
|
||||||
|
text: module.item.message ?? module.item.title ?? i18n("Welcome to Matrix")
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: module
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
source: "qrc:/imports/NeoChat/Component/Login/Login.qml"
|
||||||
|
onSourceChanged: {
|
||||||
|
headerMessage.visible = false
|
||||||
|
headerMessage.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
Controls.Button {
|
||||||
|
text: i18nc("@action:button", "Back")
|
||||||
|
|
||||||
|
enabled: welcomePage.currentStep.previousUrl !== ""
|
||||||
|
visible: welcomePage.currentStep.showBackButton
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
onClicked: {
|
||||||
|
module.source = welcomePage.currentStep.previousUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Controls.Button {
|
||||||
|
id: continueButton
|
||||||
|
enabled: welcomePage.currentStep.acceptable
|
||||||
|
visible: welcomePage.currentStep.showContinueButton
|
||||||
|
action: welcomePage.currentStep.action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: currentStep
|
||||||
|
|
||||||
|
function onProcessed(nextUrl) {
|
||||||
|
module.source = nextUrl;
|
||||||
|
}
|
||||||
|
function onShowMessage(message) {
|
||||||
|
headerMessage.text = message;
|
||||||
|
headerMessage.visible = true;
|
||||||
|
headerMessage.type = Kirigami.MessageType.Information;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
module NeoChat.Page
|
module NeoChat.Page
|
||||||
LoadingPage 1.0 LoadingPage.qml
|
LoadingPage 1.0 LoadingPage.qml
|
||||||
LoginPage 1.0 LoginPage.qml
|
|
||||||
RoomListPage 1.0 RoomListPage.qml
|
RoomListPage 1.0 RoomListPage.qml
|
||||||
RoomPage 1.0 RoomPage.qml
|
RoomPage 1.0 RoomPage.qml
|
||||||
|
RoomWindow 1.0 RoomWindow.qml
|
||||||
JoinRoomPage 1.0 JoinRoomPage.qml
|
JoinRoomPage 1.0 JoinRoomPage.qml
|
||||||
InviteUserPage 1.0 InviteUserPage.qml
|
InviteUserPage 1.0 InviteUserPage.qml
|
||||||
SettingsPage 1.0 SettingsPage.qml
|
|
||||||
ImageEditorPage 1.0 ImageEditorPage.qml
|
ImageEditorPage 1.0 ImageEditorPage.qml
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,63 @@
|
|||||||
/**
|
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12
|
|
||||||
import QtQuick.Controls.Material 2.12
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
import org.kde.kitemmodels 1.0
|
import org.kde.kitemmodels 1.0
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
import NeoChat.Component 1.0
|
import NeoChat.Component 1.0
|
||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
import NeoChat.Setting 1.0
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
|
|
||||||
Kirigami.OverlayDrawer {
|
Kirigami.OverlayDrawer {
|
||||||
id: roomDrawer
|
id: roomDrawer
|
||||||
property var room
|
readonly property var room: RoomManager.currentRoom
|
||||||
|
|
||||||
|
width: modal ? undefined : actualWidth
|
||||||
|
readonly property int minWidth: Kirigami.Units.gridUnit * 15
|
||||||
|
readonly property int maxWidth: Kirigami.Units.gridUnit * 25
|
||||||
|
readonly property int defaultWidth: Kirigami.Units.gridUnit * 20
|
||||||
|
property int actualWidth: {
|
||||||
|
if (Config.roomDrawerWidth === -1) {
|
||||||
|
return Kirigami.Units.gridUnit * 20;
|
||||||
|
} else {
|
||||||
|
return Config.roomDrawerWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.right: undefined
|
||||||
|
width: 2
|
||||||
|
z: 500
|
||||||
|
cursorShape: !Kirigami.Settings.isMobile ? Qt.SplitHCursor : undefined
|
||||||
|
enabled: true
|
||||||
|
visible: true
|
||||||
|
onPressed: _lastX = mapToGlobal(mouseX, mouseY).x
|
||||||
|
onReleased: {
|
||||||
|
Config.roomDrawerWidth = roomDrawer.actualWidth;
|
||||||
|
Config.save();
|
||||||
|
}
|
||||||
|
property real _lastX: -1
|
||||||
|
|
||||||
|
onPositionChanged: {
|
||||||
|
if (_lastX === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Qt.application.layoutDirection === Qt.RightToLeft) {
|
||||||
|
roomDrawer.actualWidth = Math.min(roomDrawer.maxWidth, Math.max(roomDrawer.minWidth, Config.roomDrawerWidth - _lastX + mapToGlobal(mouseX, mouseY).x))
|
||||||
|
} else {
|
||||||
|
roomDrawer.actualWidth = Math.min(roomDrawer.maxWidth, Math.max(roomDrawer.minWidth, Config.roomDrawerWidth + _lastX - mapToGlobal(mouseX, mouseY).x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
|
edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
|
||||||
@@ -30,72 +66,60 @@ Kirigami.OverlayDrawer {
|
|||||||
leftPadding: 0
|
leftPadding: 0
|
||||||
rightPadding: 0
|
rightPadding: 0
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
contentItem: ColumnLayout {
|
contentItem: Loader {
|
||||||
id: columnLayout
|
active: roomDrawer.drawerOpen
|
||||||
spacing: 0
|
sourceComponent: ColumnLayout {
|
||||||
Kirigami.AbstractApplicationHeader {
|
id: columnLayout
|
||||||
Layout.fillWidth: true
|
spacing: 0
|
||||||
topPadding: Kirigami.Units.smallSpacing / 2;
|
Kirigami.AbstractApplicationHeader {
|
||||||
bottomPadding: Kirigami.Units.smallSpacing / 2;
|
Layout.fillWidth: true
|
||||||
rightPadding: Kirigami.Units.smallSpacing
|
topPadding: Kirigami.Units.smallSpacing / 2;
|
||||||
leftPadding: Kirigami.Units.smallSpacing
|
bottomPadding: Kirigami.Units.smallSpacing / 2;
|
||||||
|
rightPadding: Kirigami.Units.smallSpacing
|
||||||
|
leftPadding: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
ToolButton {
|
ToolButton {
|
||||||
icon.name: "list-add-user"
|
icon.name: "list-add-user"
|
||||||
text: i18n("Invite")
|
text: i18n("Invite")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
applicationWindow().pageStack.push("qrc:/imports/NeoChat/Page/InviteUserPage.qml", {"room": room})
|
applicationWindow().pageStack.layers.push("qrc:/imports/NeoChat/Page/InviteUserPage.qml", {room: room})
|
||||||
roomDrawer.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item {
|
|
||||||
// HACK otherwise rating item is not right aligned
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolButton {
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
icon.name: room.isFavourite ? "rating" : "rating-unrated"
|
|
||||||
checkable: true
|
|
||||||
checked: room.isFavourite
|
|
||||||
onClicked: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
|
||||||
ToolTip {
|
|
||||||
text: room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToolButton {
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
icon.name: 'settings-configure'
|
|
||||||
onClicked: {
|
|
||||||
roomSettingDialog.createObject(ApplicationWindow.overlay, {"room": room}).open()
|
|
||||||
if (!wideScreen) {
|
|
||||||
roomDrawer.close();
|
roomDrawer.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Item {
|
||||||
|
// HACK otherwise rating item is not right aligned
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
ToolTip {
|
ToolButton {
|
||||||
text: i18n("Room settings")
|
Layout.alignment: Qt.AlignRight
|
||||||
|
icon.name: room && room.isFavourite ? "rating" : "rating-unrated"
|
||||||
|
checkable: true
|
||||||
|
checked: room && room.isFavourite
|
||||||
|
onClicked: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
||||||
|
ToolTip {
|
||||||
|
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolButton {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
icon.name: 'settings-configure'
|
||||||
|
onClicked: ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/imports/NeoChat/RoomSettings/Categories.qml', {room: room})
|
||||||
|
|
||||||
|
ToolTip {
|
||||||
|
text: i18n("Room settings")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
ColumnLayout {
|
||||||
id: fullScreenImage
|
|
||||||
|
|
||||||
FullScreenImage {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Control {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
bottomPadding: Kirigami.Units.largeSpacing
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
id: infoLayout
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: Kirigami.Units.largeSpacing
|
||||||
Kirigami.Heading {
|
Kirigami.Heading {
|
||||||
text: i18n("Room information")
|
text: i18n("Room information")
|
||||||
level: 3
|
level: 3
|
||||||
@@ -110,8 +134,8 @@ Kirigami.OverlayDrawer {
|
|||||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3.5
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 3.5
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3.5
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 3.5
|
||||||
|
|
||||||
name: room ? room.displayName : i18n("No name")
|
name: room ? room.name : i18n("No name")
|
||||||
source: room ? "image://mxc/" + room.avatarMediaId : undefined
|
source: room ? ("image://mxc/" + room.avatarMediaId) : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
@@ -120,6 +144,7 @@ Kirigami.OverlayDrawer {
|
|||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Kirigami.Heading {
|
Kirigami.Heading {
|
||||||
|
Layout.maximumWidth: Kirigami.Units.gridUnit * 9
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
level: 1
|
level: 1
|
||||||
font.bold: true
|
font.bold: true
|
||||||
@@ -134,8 +159,6 @@ Kirigami.OverlayDrawer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextEdit {
|
TextEdit {
|
||||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 13
|
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 13
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
||||||
readonly property var replaceLinks: /\(https:\/\/[^ ]*\)/
|
readonly property var replaceLinks: /\(https:\/\/[^ ]*\)/
|
||||||
@@ -144,6 +167,7 @@ Kirigami.OverlayDrawer {
|
|||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
color: Kirigami.Theme.textColor
|
color: Kirigami.Theme.textColor
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
|
readOnly: true
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
@@ -151,85 +175,107 @@ Kirigami.OverlayDrawer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.ListSectionHeader {
|
Kirigami.ListSectionHeader {
|
||||||
label: i18n("Members")
|
label: i18n("Members")
|
||||||
Label {
|
activeFocusOnTab: false
|
||||||
Layout.alignment: Qt.AlignRight
|
Label {
|
||||||
text: room ? i18np("%1 Member", "%1 Members", room.totalMemberCount) : i18n("No Member Count")
|
Layout.alignment: Qt.AlignRight
|
||||||
}
|
text: room ? i18np("%1 Member", "%1 Members", room.joinedCount) : i18n("No Member Count")
|
||||||
}
|
|
||||||
|
|
||||||
ScrollView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
ListView {
|
|
||||||
id: userListView
|
|
||||||
clip: true
|
|
||||||
headerPositioning: ListView.OverlayHeader
|
|
||||||
boundsBehavior: Flickable.DragOverBounds
|
|
||||||
|
|
||||||
model: KSortFilterProxyModel {
|
|
||||||
id: sortedMessageEventModel
|
|
||||||
|
|
||||||
sourceModel: UserListModel {
|
|
||||||
room: roomDrawer.room
|
|
||||||
}
|
|
||||||
|
|
||||||
sortRole: "perm"
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delegate: Kirigami.AbstractListItem {
|
Pane {
|
||||||
width: userListView.width
|
padding: Kirigami.Units.smallSpacing
|
||||||
implicitHeight: Kirigami.Units.gridUnit * 2
|
implicitWidth: parent.width
|
||||||
|
z: 2
|
||||||
|
background: Rectangle {
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
Kirigami.Theme.inherit: false
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||||
|
}
|
||||||
|
contentItem: Kirigami.SearchField {
|
||||||
|
id: userListSearchField
|
||||||
|
onAccepted: sortedMessageEventModel.filterString = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
contentItem: RowLayout {
|
ScrollView {
|
||||||
Kirigami.Avatar {
|
Layout.fillWidth: true
|
||||||
Layout.preferredWidth: height
|
Layout.fillHeight: true
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
source: "image://mxc/" + avatar
|
ListView {
|
||||||
name: name
|
id: userListView
|
||||||
|
clip: true
|
||||||
|
headerPositioning: ListView.OverlayHeader
|
||||||
|
boundsBehavior: Flickable.DragOverBounds
|
||||||
|
activeFocusOnTab: true
|
||||||
|
|
||||||
|
model: KSortFilterProxyModel {
|
||||||
|
id: sortedMessageEventModel
|
||||||
|
|
||||||
|
sourceModel: UserListModel {
|
||||||
|
room: roomDrawer.room
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
sortRole: "perm"
|
||||||
Layout.fillWidth: true
|
filterRole: "name"
|
||||||
|
filterCaseSensitivity: Qt.CaseInsensitive
|
||||||
text: name
|
|
||||||
textFormat: Text.PlainText
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
visible: perm != UserType.Member
|
|
||||||
|
|
||||||
text: {
|
|
||||||
if (perm == UserType.Owner) {
|
|
||||||
return i18n("Owner")
|
|
||||||
}
|
|
||||||
if (perm == UserType.Admin) {
|
|
||||||
return i18n("Admin")
|
|
||||||
}
|
|
||||||
if (perm == UserType.Moderator) {
|
|
||||||
return i18n("Mod")
|
|
||||||
}
|
|
||||||
if (perm == UserType.Muted) {
|
|
||||||
return i18n("Muted")
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
color: perm == UserType.Muted ? Kirigami.Theme.disabledTextColor : Kirigami.Theme.textColor
|
|
||||||
font.pixelSize: 12
|
|
||||||
textFormat: Text.PlainText
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
action: Kirigami.Action {
|
delegate: Kirigami.AbstractListItem {
|
||||||
onTriggered: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": room, "user": user}).open()
|
width: userListView.width
|
||||||
|
implicitHeight: Kirigami.Units.gridUnit * 2
|
||||||
|
z: 1
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
Kirigami.Avatar {
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||||
|
visible: Config.showAvatarInRoomDrawer
|
||||||
|
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||||
|
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||||
|
source: avatar ? ("image://mxc/" + avatar) : ""
|
||||||
|
name: name
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: name
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
visible: perm != UserType.Member
|
||||||
|
|
||||||
|
text: {
|
||||||
|
if (perm == UserType.Owner) {
|
||||||
|
return i18n("Owner")
|
||||||
|
}
|
||||||
|
if (perm == UserType.Admin) {
|
||||||
|
return i18n("Admin")
|
||||||
|
}
|
||||||
|
if (perm == UserType.Moderator) {
|
||||||
|
return i18n("Mod")
|
||||||
|
}
|
||||||
|
if (perm == UserType.Muted) {
|
||||||
|
return i18n("Muted")
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
font.pixelSize: 12
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action: Kirigami.Action {
|
||||||
|
onTriggered: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": room, "user": user, "displayName": name, "avatarMediaId": avatar}).open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,12 +288,6 @@ Kirigami.OverlayDrawer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
|
||||||
id: roomSettingDialog
|
|
||||||
|
|
||||||
RoomSettingsDialog {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: userDetailDialog
|
id: userDetailDialog
|
||||||
|
|
||||||
|
|||||||
35
imports/NeoChat/RoomSettings/Categories.qml
Normal file
35
imports/NeoChat/RoomSettings/Categories.qml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import org.kde.kirigami 2.18 as Kirigami
|
||||||
|
import QtQuick.Controls 2.15 as Controls
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
Kirigami.CategorizedSettings {
|
||||||
|
id: root
|
||||||
|
required property var room
|
||||||
|
objectName: "settingsPage"
|
||||||
|
actions: [
|
||||||
|
Kirigami.SettingAction {
|
||||||
|
text: i18n("General")
|
||||||
|
icon.name: "settings-configure"
|
||||||
|
page: Qt.resolvedUrl("General.qml")
|
||||||
|
initialProperties: {
|
||||||
|
return {
|
||||||
|
room: root.room
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.SettingAction {
|
||||||
|
text: i18n("Security")
|
||||||
|
icon.name: "security-low"
|
||||||
|
page: Qt.resolvedUrl("Security.qml")
|
||||||
|
initialProperties: {
|
||||||
|
return {
|
||||||
|
room: root.room
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
202
imports/NeoChat/RoomSettings/General.qml
Normal file
202
imports/NeoChat/RoomSettings/General.qml
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
||||||
|
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component 1.0
|
||||||
|
import NeoChat.Dialog 1.0
|
||||||
|
|
||||||
|
Kirigami.ScrollablePage {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var room
|
||||||
|
|
||||||
|
readonly property bool canChangeAvatar: room.canSendState("m.room.avatar")
|
||||||
|
readonly property bool canChangeName: room.canSendState("m.room.name")
|
||||||
|
readonly property bool canChangeTopic: room.canSendState("m.room.topic")
|
||||||
|
readonly property bool canChangeCanonicalAlias: room.canSendState("m.room.canonical_alias")
|
||||||
|
|
||||||
|
title: i18n('General')
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Kirigami.Avatar {
|
||||||
|
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
name: room.name
|
||||||
|
source: room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
|
||||||
|
|
||||||
|
RoundButton {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
height: Kirigami.Units.gridUnits
|
||||||
|
width: Kirigami.Units.gridUnits
|
||||||
|
icon.name: 'cloud-upload'
|
||||||
|
Accessible.name: i18n("Update avatar")
|
||||||
|
enabled: canChangeAvatar
|
||||||
|
onClicked: {
|
||||||
|
const fileDialog = openFileDialog.createObject(ApplicationWindow.overlay)
|
||||||
|
|
||||||
|
fileDialog.chosen.connect(function(path) {
|
||||||
|
if (!path) return
|
||||||
|
|
||||||
|
room.changeAvatar(path)
|
||||||
|
})
|
||||||
|
|
||||||
|
fileDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextField {
|
||||||
|
id: roomNameField
|
||||||
|
text: room.name
|
||||||
|
Kirigami.FormData.label: i18n("Room Name:")
|
||||||
|
enabled: canChangeName
|
||||||
|
}
|
||||||
|
|
||||||
|
TextArea {
|
||||||
|
id: roomTopicField
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: room.topic
|
||||||
|
Kirigami.FormData.label: i18n("Room topic:")
|
||||||
|
enabled: canChangeTopic
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: canonicalAliasComboBox.visible || altAlias.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox {
|
||||||
|
id: canonicalAliasComboBox
|
||||||
|
visible: room.aliases && room.aliases.length
|
||||||
|
Kirigami.FormData.label: i18n("Canonical Alias:")
|
||||||
|
popup.z: 999; // HACK This is an absolute hack, but combos inside OverlaySheets have their popups show up underneath, because of fun z ordering stuff
|
||||||
|
|
||||||
|
enabled: canChangeCanonicalAlias
|
||||||
|
|
||||||
|
model: room.aliases
|
||||||
|
|
||||||
|
currentIndex: room.aliases.indexOf(room.canonicalAlias)
|
||||||
|
onCurrentIndexChanged: {
|
||||||
|
if (room.canonicalAlias != room.aliases[currentIndex]) {
|
||||||
|
room.setCanonicalAlias(room.aliases[currentIndex])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: altAlias
|
||||||
|
Kirigami.FormData.label: i18n("Other Aliases:")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
visible: room.altAliases && room.altAliases.length
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: room.altAliases
|
||||||
|
|
||||||
|
delegate: RowLayout {
|
||||||
|
Layout.maximumWidth: parent.width
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: modelData
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
icon.name: ""
|
||||||
|
onClicked: room.removeLocalAlias(modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: next.visible || prev.visible
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
id: next
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
visible: room.predecessorId && room.connection.room(room.predecessorId)
|
||||||
|
|
||||||
|
padding: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
contentItem: Kirigami.InlineMessage {
|
||||||
|
text: i18n("This room continues another conversation.")
|
||||||
|
actions: Kirigami.Action {
|
||||||
|
text: i18n("See older messages...")
|
||||||
|
onTriggered: {
|
||||||
|
roomListForm.enteredRoom = Controller.activeConnection.room(room.predecessorId)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
id: prev
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
visible: room.successorId && room.connection.room(room.successorId)
|
||||||
|
|
||||||
|
padding: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
contentItem: Kirigami.InlineMessage {
|
||||||
|
text: i18n("This room has been replaced.")
|
||||||
|
actions: Kirigami.Action {
|
||||||
|
text: i18n("See new room...")
|
||||||
|
onTriggered: {
|
||||||
|
roomListForm.enteredRoom = Controller.activeConnection.room(room.successorId)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: openFileDialog
|
||||||
|
|
||||||
|
OpenFileDialog {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer: ToolBar {
|
||||||
|
contentItem: RowLayout {
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
enabled: room.name !== roomNameField.text || room.topic !== roomTopicField.text
|
||||||
|
text: i18n("Apply")
|
||||||
|
onClicked: {
|
||||||
|
if (room.name != roomNameField.text) {
|
||||||
|
room.setName(roomNameField.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (room.topic != roomTopicField.text) {
|
||||||
|
room.setTopic(roomTopicField.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
69
imports/NeoChat/RoomSettings/Security.qml
Normal file
69
imports/NeoChat/RoomSettings/Security.qml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2019-2020 Black Hat <bhat@encom.eu.org>
|
||||||
|
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Component 1.0
|
||||||
|
import NeoChat.Dialog 1.0
|
||||||
|
|
||||||
|
Kirigami.ScrollablePage {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var room
|
||||||
|
|
||||||
|
title: i18n('Security')
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
text: i18nc("@option:check", "Private (invite only)")
|
||||||
|
Kirigami.FormData.label: i18nc("@option:check", "Access:")
|
||||||
|
checked: room.joinRule === "invite"
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: i18n("Only invited people can join.")
|
||||||
|
font: Kirigami.Theme.smallFont
|
||||||
|
}
|
||||||
|
CheckBox {
|
||||||
|
text: i18nc("@option:check", "Space members")
|
||||||
|
checked: room.joinRule === "restricted"
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: i18n("Anyone in a space can find and join.")
|
||||||
|
font: Kirigami.Theme.smallFont
|
||||||
|
}
|
||||||
|
CheckBox {
|
||||||
|
text: i18nc("@option:check", "Public")
|
||||||
|
checked: room.joinRule === "public"
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
text: i18nc("@option:check", "Anyone can find and join.") + room.joinRule
|
||||||
|
font: Kirigami.Theme.smallFont
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer: ToolBar {
|
||||||
|
contentItem: RowLayout {
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
enabled: false
|
||||||
|
text: i18n("Apply")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
pragma Singleton
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls.Material 2.12
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
readonly property int theme: MSettings.darkTheme ? Material.Dark : Material.Light
|
|
||||||
|
|
||||||
readonly property color primary: "#344955"
|
|
||||||
readonly property color accent: "#4286F5"
|
|
||||||
readonly property color foreground: MSettings.darkTheme ? "#FFFFFF" : "#1D333E"
|
|
||||||
readonly property color background: MSettings.darkTheme ? "#303030" : "#FFFFFF"
|
|
||||||
readonly property color lighter: MSettings.darkTheme ? "#FFFFFF" : "#5B7480"
|
|
||||||
readonly property color banner: MSettings.darkTheme ? "#404040" : "#F2F3F4"
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
pragma Singleton
|
|
||||||
import QtQuick 2.12
|
|
||||||
import Qt.labs.settings 1.0
|
|
||||||
|
|
||||||
Settings {
|
|
||||||
property bool showNotification: true
|
|
||||||
|
|
||||||
property bool showTray: true
|
|
||||||
|
|
||||||
property bool darkTheme
|
|
||||||
|
|
||||||
property string fontFamily: "Roboto,Noto Sans,Noto Color Emoji"
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
module NeoChat.Setting
|
|
||||||
singleton MSettings 1.0 Setting.qml
|
|
||||||
singleton MPalette 1.0 Palette.qml
|
|
||||||
12
imports/NeoChat/Settings/About.qml
Normal file
12
imports/NeoChat/Settings/About.qml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
Kirigami.AboutPage {
|
||||||
|
title: i18nc('@title:window', 'About NeoChat')
|
||||||
|
aboutData: Controller.aboutData
|
||||||
|
}
|
||||||
234
imports/NeoChat/Settings/AccountsPage.qml
Normal file
234
imports/NeoChat/Settings/AccountsPage.qml
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as Controls
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import Qt.labs.platform 1.1
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Dialog 1.0
|
||||||
|
|
||||||
|
Kirigami.Page {
|
||||||
|
title: i18n("Accounts")
|
||||||
|
|
||||||
|
leftPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||||
|
topPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||||
|
bottomPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||||
|
rightPadding: pageSettingStack.wideMode ? Kirigami.Units.smallSpacing : 0
|
||||||
|
|
||||||
|
actions.main: Kirigami.Action {
|
||||||
|
text: i18n("Add an account")
|
||||||
|
icon.name: "list-add-user"
|
||||||
|
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/WelcomePage.qml")
|
||||||
|
visible: !pageSettingStack.wideMode
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: pageSettingStack
|
||||||
|
function onWideModeChanged() {
|
||||||
|
scroll.background.visible = pageSettingStack.wideMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Controls.ScrollView {
|
||||||
|
id: scroll
|
||||||
|
Component.onCompleted: background.visible = pageSettingStack.wideMode
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Controls.ScrollBar.horizontal.policy: Controls.ScrollBar.AlwaysOff
|
||||||
|
ListView {
|
||||||
|
clip: true
|
||||||
|
model: AccountListModel { }
|
||||||
|
delegate: Kirigami.SwipeListItem {
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
|
text: model.user.displayName
|
||||||
|
labelItem.textFormat: Text.PlainText
|
||||||
|
subtitle: model.user.id
|
||||||
|
icon: model.user.avatarMediaId ? ("image://mxc/" + model.user.avatarMediaId) : "im-user"
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
Controller.activeConnection = model.connection
|
||||||
|
pageStack.layers.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actions: [
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Edit this account")
|
||||||
|
iconName: "document-edit"
|
||||||
|
onTriggered: {
|
||||||
|
userEditSheet.connection = model.connection
|
||||||
|
userEditSheet.open()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Logout")
|
||||||
|
iconName: "im-kick-user"
|
||||||
|
onTriggered: {
|
||||||
|
Controller.logout(model.connection, true)
|
||||||
|
if(Controller.accountCount === 1)
|
||||||
|
pageStack.layers.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer: Column {
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
Kirigami.ActionToolBar {
|
||||||
|
alignment: Qt.AlignRight
|
||||||
|
visible: pageSettingStack.wideMode
|
||||||
|
rightPadding: Kirigami.Units.smallSpacing
|
||||||
|
width: parent.width
|
||||||
|
flat: false
|
||||||
|
actions: [
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("Add an account")
|
||||||
|
icon.name: "list-add-user"
|
||||||
|
onTriggered: pageStack.layers.push("qrc:/imports/NeoChat/Page/WelcomePage.qml")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: Kirigami.Units.smallSpacing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Connections {
|
||||||
|
target: Controller
|
||||||
|
function onConnectionAdded() {
|
||||||
|
if (pageStack.layers.depth > 2)
|
||||||
|
pageStack.layers.pop()
|
||||||
|
}
|
||||||
|
function onPasswordStatus(status) {
|
||||||
|
if(status == Controller.Success)
|
||||||
|
showPassiveNotification(i18n("Password changed successfully"))
|
||||||
|
else if(status == Controller.Wrong)
|
||||||
|
showPassiveNotification(i18n("Wrong password entered"))
|
||||||
|
else
|
||||||
|
showPassiveNotification(i18n("Unknown problem while trying to change password"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: openFileDialog
|
||||||
|
|
||||||
|
OpenFileDialog {
|
||||||
|
folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.OverlaySheet {
|
||||||
|
id: userEditSheet
|
||||||
|
|
||||||
|
property var connection
|
||||||
|
|
||||||
|
title: i18n("Edit Account")
|
||||||
|
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
RowLayout {
|
||||||
|
Kirigami.Avatar {
|
||||||
|
id: avatar
|
||||||
|
source: userEditSheet.connection && userEditSheet.connection.localUser.avatarMediaId ? ("image://mxc/" + userEditSheet.connection.localUser.avatarMediaId) : ""
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
property var fileDialog: null;
|
||||||
|
onClicked: {
|
||||||
|
if (fileDialog != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileDialog = openFileDialog.createObject(Controls.ApplicationWindow.Overlay)
|
||||||
|
|
||||||
|
fileDialog.chosen.connect(function(receivedSource) {
|
||||||
|
mouseArea.fileDialog = null;
|
||||||
|
if (!receivedSource) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
parent.source = receivedSource;
|
||||||
|
});
|
||||||
|
fileDialog.onRejected.connect(function() {
|
||||||
|
mouseArea.fileDialog = null;
|
||||||
|
});
|
||||||
|
fileDialog.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Controls.Button {
|
||||||
|
visible: avatar.source.length !== 0
|
||||||
|
icon.name: "edit-clear"
|
||||||
|
|
||||||
|
onClicked: avatar.source = ""
|
||||||
|
}
|
||||||
|
Kirigami.FormData.label: i18n("Avatar:")
|
||||||
|
}
|
||||||
|
Controls.TextField {
|
||||||
|
id: name
|
||||||
|
text: userEditSheet.connection ? userEditSheet.connection.localUser.displayName : ""
|
||||||
|
Kirigami.FormData.label: i18n("Name:")
|
||||||
|
}
|
||||||
|
Controls.TextField {
|
||||||
|
id: currentPassword
|
||||||
|
Kirigami.FormData.label: i18n("Current Password:")
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
}
|
||||||
|
Controls.TextField {
|
||||||
|
id: newPassword
|
||||||
|
Kirigami.FormData.label: i18n("New Password:")
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
|
||||||
|
}
|
||||||
|
Controls.TextField {
|
||||||
|
id: confirmPassword
|
||||||
|
Kirigami.FormData.label: i18n("Confirm new Password:")
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Controls.Button {
|
||||||
|
text: i18n("Save")
|
||||||
|
onClicked: {
|
||||||
|
if(!Controller.setAvatar(userEditSheet.connection, avatar.source))
|
||||||
|
showPassiveNotification("The Avatar could not be set")
|
||||||
|
if(userEditSheet.connection.localUser.displayName !== name.text)
|
||||||
|
userEditSheet.connection.localUser.rename(name.text)
|
||||||
|
if(currentPassword.text !== "" && newPassword.text !== "" && confirmPassword.text !== "") {
|
||||||
|
if(newPassword.text === confirmPassword.text) {
|
||||||
|
Controller.changePassword(userEditSheet.connection, currentPassword.text, newPassword.text)
|
||||||
|
} else {
|
||||||
|
showPassiveNotification(i18n("Passwords do not match"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userEditSheet.close()
|
||||||
|
currentPassword.text = ""
|
||||||
|
newPassword.text = ""
|
||||||
|
confirmPassword.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Controls.Button {
|
||||||
|
text: i18n("Cancel")
|
||||||
|
onClicked: {
|
||||||
|
userEditSheet.close()
|
||||||
|
avatar.source = userEditSheet.connection.localUser.avatarMediaId ? ("image://mxc/" + userEditSheet.connection.localUser.avatarMediaId) : ""
|
||||||
|
currentPassword.text = ""
|
||||||
|
newPassword.text = ""
|
||||||
|
confirmPassword.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user