Merge branch 'develop' into 'master'
More UI Improvements Closes #2 and #114 See merge request b0/spectral!46
|
@ -3,7 +3,7 @@ image: Visual Studio 2017
|
||||||
environment:
|
environment:
|
||||||
DEPLOY_DIR: Spectral-%APPVEYOR_BUILD_VERSION%
|
DEPLOY_DIR: Spectral-%APPVEYOR_BUILD_VERSION%
|
||||||
matrix:
|
matrix:
|
||||||
- QTDIR: C:\Qt\5.12.1\msvc2017_64
|
- QTDIR: C:\Qt\5.12\msvc2017_64
|
||||||
VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat"
|
VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat"
|
||||||
PLATFORM:
|
PLATFORM:
|
||||||
|
|
||||||
|
|
|
@ -3,30 +3,42 @@ stages:
|
||||||
- deploy
|
- deploy
|
||||||
|
|
||||||
build-flatpak:
|
build-flatpak:
|
||||||
image: black0/flatpak
|
image: registry.gitlab.com/b0/flatpak-kde-docker
|
||||||
stage: build
|
stage: build
|
||||||
before_script:
|
before_script:
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
script:
|
script:
|
||||||
- cd flatpak
|
- cd flatpak
|
||||||
- flatpak-builder --force-clean --repo=repo build-dir org.eu.encom.spectral.yaml
|
- flatpak-builder --force-clean --ccache --repo=repo build-dir org.eu.encom.spectral.yaml
|
||||||
- flatpak build-bundle repo spectral.flatpak org.eu.encom.spectral
|
- flatpak build-bundle repo spectral.flatpak org.eu.encom.spectral
|
||||||
- cd ../
|
- cd ../
|
||||||
|
cache:
|
||||||
|
key: "flatpak-$CI_COMMIT_REF_SLUG"
|
||||||
|
paths:
|
||||||
|
- flatpak/.flatpak-builder
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- flatpak/spectral.flatpak
|
- flatpak/spectral.flatpak
|
||||||
|
|
||||||
build-appimage:
|
build-appimage:
|
||||||
image: black0/qt
|
image: registry.gitlab.com/b0/qt-docker
|
||||||
stage: build
|
stage: build
|
||||||
before_script:
|
before_script:
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
script:
|
script:
|
||||||
|
- mkdir -p ccache
|
||||||
|
- export CCACHE_BASEDIR=${CI_PROJECT_DIR}
|
||||||
|
- export CCACHE_DIR=${CI_PROJECT_DIR}/ccache
|
||||||
- /opt/qt512/bin/qt512-env.sh
|
- /opt/qt512/bin/qt512-env.sh
|
||||||
- /opt/qt512/bin/qmake CONFIG+=debug CONFIG+=qml_debug PREFIX=/usr
|
- /opt/qt512/bin/qmake CONFIG+=debug CONFIG+=qml_debug CONFIG+=ccache PREFIX=/usr
|
||||||
- make
|
- make
|
||||||
- make INSTALL_ROOT=appdir install
|
- make INSTALL_ROOT=appdir install
|
||||||
- /usr/bin/linuxdeployqt-continuous-x86_64.AppImage appdir/usr/share/applications/org.eu.encom.spectral.desktop -appimage -qmldir=qml -qmldir=imports -qmake=/opt/qt512/bin/qmake
|
- /usr/bin/linuxdeployqt-continuous-x86_64.AppImage appdir/usr/share/applications/org.eu.encom.spectral.desktop -appimage -qmldir=qml -qmldir=imports -qmake=/opt/qt512/bin/qmake
|
||||||
|
cache:
|
||||||
|
key: "appimage-$CI_COMMIT_REF_SLUG"
|
||||||
|
paths:
|
||||||
|
- ccache/
|
||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- Spectral*.AppImage
|
- Spectral*.AppImage
|
||||||
|
|
|
@ -15,7 +15,7 @@ Spectral is a glossy cross-platform client for Matrix, the decentralized communi
|
||||||
|
|
||||||
There is a separate document for Spectral, including installing, compiling, etc.
|
There is a separate document for Spectral, including installing, compiling, etc.
|
||||||
|
|
||||||
It is at [Spectral Doc](https://doc.spectral.encom.eu.org/)
|
It is at [Spectral Doc](https://b0.gitlab.io/spectral-doc/)
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
|
@ -31,6 +31,10 @@ This program uses libqmatrixclient library and some C++ models from Quaternion.
|
||||||
|
|
||||||
[libqmatrixclient](https://github.com/QMatrixClient/libqmatrixclient)
|
[libqmatrixclient](https://github.com/QMatrixClient/libqmatrixclient)
|
||||||
|
|
||||||
|
This program includes the source code of hoedown.
|
||||||
|
|
||||||
|
[Hoedown](https://github.com/hoedown/hoedown)
|
||||||
|
|
||||||
## Donation
|
## Donation
|
||||||
|
|
||||||
Donations are welcome! My Bitcoin wallet address is 1AmNvttxJ6zne8f2GEH8zMAMQuT4cMdnDN
|
Donations are welcome! My Bitcoin wallet address is 1AmNvttxJ6zne8f2GEH8zMAMQuT4cMdnDN
|
||||||
|
@ -41,4 +45,4 @@ Donations are welcome! My Bitcoin wallet address is 1AmNvttxJ6zne8f2GEH8zMAMQuT4
|
||||||
|
|
||||||
This program is licensed under GNU General Public License, Version 3.
|
This program is licensed under GNU General Public License, Version 3.
|
||||||
|
|
||||||
Exceptions are src/notifications/wintoastlib.c and wintoastlib.h, copied from https://github.com/mohabouje/WinToast and licensed under MIT.
|
Exceptions are src/notifications/wintoastlib.c and wintoastlib.h, which are from https://github.com/mohabouje/WinToast and licensed under MIT.
|
||||||
|
|
|
@ -1,219 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="1280"
|
|
||||||
height="960"
|
|
||||||
viewBox="0 0 338.66666 254.00001"
|
|
||||||
version="1.1"
|
|
||||||
id="svg4636"
|
|
||||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
|
||||||
sodipodi:docname="roompanel-dark.svg">
|
|
||||||
<defs
|
|
||||||
id="defs4630" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="1"
|
|
||||||
inkscape:cx="574.88953"
|
|
||||||
inkscape:cy="546.35799"
|
|
||||||
inkscape:document-units="px"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
units="px"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1050"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="30"
|
|
||||||
inkscape:window-maximized="1" />
|
|
||||||
<metadata
|
|
||||||
id="metadata4633">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title />
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(0,-42.999983)">
|
|
||||||
<circle
|
|
||||||
id="path4638"
|
|
||||||
cx="274.10834"
|
|
||||||
cy="259.42917"
|
|
||||||
r="75.40625"
|
|
||||||
style="opacity:0.25;fill:#13100e;fill-opacity:1;stroke-width:0.26458335" />
|
|
||||||
<circle
|
|
||||||
id="path4638-6"
|
|
||||||
cx="303.0802"
|
|
||||||
cy="169.86771"
|
|
||||||
r="48.286457"
|
|
||||||
style="opacity:0.25;fill:#13100e;fill-opacity:1;stroke-width:0.16942617" />
|
|
||||||
<circle
|
|
||||||
id="path4638-6-7"
|
|
||||||
cx="165.76144"
|
|
||||||
cy="82.290604"
|
|
||||||
r="25.135412"
|
|
||||||
style="opacity:0.25;fill:#13100e;fill-opacity:1;stroke-width:0.08819444" />
|
|
||||||
<circle
|
|
||||||
id="path4638-6-5"
|
|
||||||
cx="38.100006"
|
|
||||||
cy="195.3998"
|
|
||||||
r="76.332291"
|
|
||||||
style="opacity:0.25;fill:#13100e;fill-opacity:1;stroke-width:0.26783261" />
|
|
||||||
<circle
|
|
||||||
id="path4638-6-3"
|
|
||||||
cx="87.709373"
|
|
||||||
cy="148.70103"
|
|
||||||
r="41.01041"
|
|
||||||
style="opacity:0.25;fill:#13100e;fill-opacity:1;stroke-width:0.14389619" />
|
|
||||||
<circle
|
|
||||||
id="path4638-6-56"
|
|
||||||
cx="220.3979"
|
|
||||||
cy="100.94374"
|
|
||||||
r="48.286453"
|
|
||||||
style="opacity:0.25;fill:#13100e;fill-opacity:1;stroke-width:0.16942617" />
|
|
||||||
<g
|
|
||||||
id="g5310"
|
|
||||||
transform="matrix(0.18980272,0,0,0.18980272,163.39608,213.89968)"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero">
|
|
||||||
<path
|
|
||||||
id="path5231"
|
|
||||||
d="M 34.004,340.809 H 2 c -1.104,0 -2,-0.896 -2,-2 V 2 C 0,0.896 0.896,0 2,0 h 32.004 c 1.104,0 2,0.896 2,2 v 7.71 c 0,1.104 -0.896,2 -2,2 h -21.13 v 317.386 h 21.13 c 1.104,0 2,0.896 2,2.001 v 7.712 c 0,1.104 -0.896,2 -2,2 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5233"
|
|
||||||
d="m 10.875,9.711 v 321.386 h 23.13 v 7.711 H 1.999 V 2.001 h 32.006 v 7.71 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5235"
|
|
||||||
d="m 252.402,233.711 h -32.993 c -1.104,0 -2,-0.896 -2,-2 v -68.073 c 0,-3.949 -0.154,-7.722 -0.457,-11.213 -0.289,-3.282 -1.074,-6.153 -2.332,-8.53 -1.204,-2.276 -3.017,-4.119 -5.384,-5.476 -2.393,-1.362 -5.775,-2.056 -10.042,-2.056 -4.238,0 -7.674,0.798 -10.213,2.371 -2.565,1.596 -4.604,3.701 -6.053,6.258 -1.498,2.643 -2.51,5.694 -3.013,9.067 -0.526,3.513 -0.793,7.125 -0.793,10.741 v 66.91 c 0,1.104 -0.896,2 -2,2 h -32.991 c -1.104,0 -2,-0.896 -2,-2 v -67.373 c 0,-3.435 -0.078,-6.964 -0.228,-10.485 -0.148,-3.251 -0.767,-6.278 -1.841,-8.995 -1.018,-2.571 -2.667,-4.584 -5.047,-6.153 -2.372,-1.552 -6.029,-2.341 -10.865,-2.341 -1.372,0 -3.265,0.328 -5.629,0.976 -2.28,0.624 -4.536,1.826 -6.705,3.577 -2.152,1.732 -4.036,4.306 -5.605,7.655 -1.569,3.356 -2.367,7.877 -2.367,13.438 v 69.701 c 0,1.104 -0.895,2 -2,2 H 68.857 c -1.104,0 -2,-0.896 -2,-2 V 111.594 c 0,-1.104 0.896,-1.999 2,-1.999 h 31.13 c 1.104,0 2,0.896 2,1.999 v 11.007 c 3.834,-4.499 8.248,-8.152 13.173,-10.896 6.396,-3.559 13.799,-5.362 22.002,-5.362 7.846,0 15.127,1.548 21.642,4.604 5.794,2.722 10.424,7.26 13.791,13.52 3.449,-4.362 7.833,-8.306 13.071,-11.752 6.422,-4.228 14.102,-6.371 22.824,-6.371 6.499,0 12.625,0.807 18.209,2.399 5.686,1.628 10.635,4.271 14.712,7.857 4.088,3.605 7.318,8.357 9.601,14.123 2.25,5.719 3.391,12.649 3.391,20.604 v 80.384 c -0.001,1.104 -0.896,2 -2.001,2 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5237"
|
|
||||||
d="m 99.988,111.595 v 16.264 h 0.463 c 4.338,-6.191 9.563,-10.998 15.684,-14.406 6.117,-3.402 13.129,-5.11 21.027,-5.11 7.588,0 14.521,1.475 20.793,4.415 6.274,2.945 11.038,8.131 14.291,15.567 3.56,-5.265 8.4,-9.913 14.521,-13.94 6.117,-4.025 13.358,-6.042 21.724,-6.042 6.351,0 12.234,0.776 17.66,2.325 5.418,1.549 10.065,4.027 13.938,7.434 3.869,3.41 6.889,7.863 9.062,13.357 2.167,5.504 3.253,12.122 3.253,19.869 v 80.385 H 219.41 v -68.074 c 0,-4.025 -0.154,-7.82 -0.465,-11.385 -0.313,-3.56 -1.161,-6.656 -2.555,-9.293 -1.395,-2.631 -3.45,-4.724 -6.157,-6.274 -2.711,-1.543 -6.391,-2.322 -11.037,-2.322 -4.646,0 -8.403,0.896 -11.269,2.671 -2.868,1.784 -5.112,4.109 -6.737,6.971 -1.626,2.869 -2.711,6.12 -3.252,9.762 -0.545,3.638 -0.814,7.318 -0.814,11.035 v 66.91 h -32.991 v -67.375 c 0,-3.562 -0.081,-7.087 -0.23,-10.57 -0.158,-3.487 -0.814,-6.7 -1.978,-9.645 -1.162,-2.94 -3.099,-5.304 -5.809,-7.088 -2.711,-1.775 -6.699,-2.671 -11.965,-2.671 -1.551,0 -3.603,0.349 -6.156,1.048 -2.556,0.697 -5.036,2.016 -7.435,3.949 -2.404,1.938 -4.454,4.726 -6.158,8.363 -1.705,3.642 -2.556,8.402 -2.556,14.287 v 69.701 H 68.856 V 111.595 Z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5239"
|
|
||||||
d="m 304.909,236.733 c -5.883,0 -11.46,-0.729 -16.574,-2.163 -5.192,-1.464 -9.806,-3.774 -13.713,-6.871 -3.944,-3.117 -7.068,-7.111 -9.282,-11.871 -2.205,-4.733 -3.324,-10.412 -3.324,-16.876 0,-7.13 1.293,-13.117 3.846,-17.797 2.542,-4.674 5.877,-8.464 9.912,-11.263 3.97,-2.752 8.556,-4.842 13.63,-6.209 4.901,-1.322 9.937,-2.394 14.961,-3.184 4.986,-0.775 9.949,-1.404 14.754,-1.872 4.679,-0.452 8.88,-1.139 12.489,-2.039 3.412,-0.854 6.118,-2.09 8.042,-3.672 1.666,-1.37 2.416,-3.384 2.292,-6.151 -0.002,-3.289 -0.502,-5.816 -1.492,-7.595 -0.998,-1.798 -2.283,-3.15 -3.927,-4.138 -1.703,-1.02 -3.725,-1.713 -6.012,-2.062 -2.47,-0.37 -5.146,-0.557 -7.947,-0.557 -6.034,0 -10.789,1.271 -14.135,3.783 -3.233,2.424 -5.155,6.64 -5.714,12.527 -0.098,1.026 -0.961,1.812 -1.992,1.812 h -32.992 c -0.552,0 -1.079,-0.229 -1.457,-0.629 -0.376,-0.402 -0.572,-0.941 -0.54,-1.491 0.485,-8.073 2.55,-14.894 6.142,-20.272 3.548,-5.331 8.147,-9.682 13.661,-12.931 5.424,-3.191 11.612,-5.498 18.392,-6.857 6.684,-1.335 13.5,-2.013 20.26,-2.013 6.096,0 12.365,0.437 18.626,1.296 6.377,0.88 12.285,2.622 17.562,5.177 5.376,2.604 9.845,6.29 13.282,10.951 3.498,4.744 5.271,11.048 5.271,18.731 v 62.494 c 0,5.307 0.306,10.462 0.915,15.319 0.576,4.64 1.572,8.116 2.963,10.338 0.385,0.616 0.407,1.395 0.055,2.031 -0.353,0.635 -1.022,1.03 -1.75,1.03 h -33.457 c -0.861,0 -1.624,-0.55 -1.898,-1.367 -0.646,-1.941 -1.176,-3.939 -1.572,-5.936 -0.141,-0.696 -0.267,-1.402 -0.38,-2.12 -4.825,4.184 -10.349,7.24 -16.474,9.105 -7.299,2.218 -14.843,3.342 -22.423,3.342 z m 37.032,-60.072 c -0.809,0.409 -1.676,0.768 -2.596,1.074 -2.161,0.72 -4.511,1.326 -6.988,1.807 -2.442,0.475 -5.033,0.872 -7.699,1.186 -2.631,0.311 -5.251,0.697 -7.784,1.146 -2.329,0.433 -4.705,1.035 -7.051,1.792 -2.194,0.711 -4.114,1.667 -5.699,2.842 -1.531,1.128 -2.785,2.587 -3.731,4.335 -0.917,1.709 -1.385,3.97 -1.385,6.719 0,2.598 0.465,4.778 1.385,6.481 0.928,1.722 2.142,3.035 3.716,4.018 1.644,1.026 3.601,1.757 5.816,2.17 2.344,0.439 4.799,0.663 7.297,0.663 6.105,0 10.836,-0.996 14.063,-2.961 3.244,-1.973 5.666,-4.349 7.199,-7.062 1.568,-2.78 2.542,-5.62 2.892,-8.436 0.376,-3.019 0.565,-5.436 0.565,-7.187 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5241"
|
|
||||||
d="m 273.544,129.255 c 3.405,-5.113 7.744,-9.215 13.012,-12.316 5.264,-3.097 11.186,-5.303 17.771,-6.621 6.582,-1.315 13.205,-1.976 19.865,-1.976 6.042,0 12.158,0.428 18.354,1.277 6.195,0.855 11.85,2.522 16.962,4.997 5.111,2.477 9.292,5.926 12.546,10.338 3.253,4.414 4.879,10.262 4.879,17.543 v 62.494 c 0,5.428 0.31,10.611 0.931,15.567 0.615,4.959 1.701,8.676 3.251,11.153 H 347.66 c -0.621,-1.86 -1.126,-3.755 -1.511,-5.693 -0.39,-1.933 -0.661,-3.908 -0.813,-5.923 -5.267,5.422 -11.465,9.217 -18.585,11.386 -7.127,2.163 -14.407,3.251 -21.842,3.251 -5.733,0 -11.077,-0.698 -16.033,-2.09 -4.958,-1.395 -9.293,-3.562 -13.01,-6.51 -3.718,-2.938 -6.622,-6.656 -8.713,-11.147 -2.091,-4.491 -3.138,-9.84 -3.138,-16.033 0,-6.813 1.199,-12.43 3.604,-16.84 2.399,-4.417 5.495,-7.939 9.295,-10.575 3.793,-2.632 8.129,-4.607 13.01,-5.923 4.878,-1.315 9.795,-2.358 14.752,-3.137 4.957,-0.772 9.835,-1.393 14.638,-1.857 4.801,-0.466 9.062,-1.164 12.779,-2.093 3.718,-0.929 6.658,-2.282 8.829,-4.065 2.165,-1.781 3.172,-4.375 3.02,-7.785 0,-3.56 -0.58,-6.389 -1.742,-8.479 -1.161,-2.09 -2.711,-3.719 -4.646,-4.88 -1.937,-1.161 -4.183,-1.936 -6.737,-2.325 -2.557,-0.382 -5.309,-0.58 -8.248,-0.58 -6.506,0 -11.617,1.395 -15.335,4.183 -3.716,2.788 -5.889,7.437 -6.506,13.94 h -32.991 c 0.462,-7.742 2.395,-14.173 5.807,-19.281 z m 65.169,46.583 c -2.09,0.696 -4.337,1.275 -6.736,1.741 -2.402,0.465 -4.918,0.853 -7.551,1.161 -2.635,0.313 -5.268,0.698 -7.899,1.163 -2.48,0.461 -4.919,1.086 -7.317,1.857 -2.404,0.779 -4.495,1.822 -6.274,3.138 -1.784,1.317 -3.216,2.985 -4.3,4.994 -1.085,2.014 -1.626,4.571 -1.626,7.668 0,2.94 0.541,5.422 1.626,7.431 1.084,2.017 2.558,3.604 4.416,4.765 1.858,1.161 4.025,1.976 6.507,2.438 2.475,0.466 5.031,0.698 7.665,0.698 6.505,0 11.537,-1.082 15.103,-3.253 3.561,-2.166 6.192,-4.762 7.899,-7.785 1.702,-3.019 2.749,-6.072 3.137,-9.174 0.384,-3.097 0.58,-5.576 0.58,-7.434 V 172.93 c -1.396,1.243 -3.138,2.21 -5.23,2.908 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5243"
|
|
||||||
d="m 444.542,234.874 c -5.187,0 -10.173,-0.361 -14.823,-1.069 -4.802,-0.732 -9.104,-2.183 -12.779,-4.313 -3.789,-2.185 -6.821,-5.341 -9.006,-9.375 -2.163,-3.986 -3.26,-9.232 -3.26,-15.59 v -68.859 h -17.981 c -1.104,0 -2,-0.896 -2,-1.999 v -22.073 c 0,-1.104 0.896,-1.999 2,-1.999 h 17.981 V 75.582 c 0,-1.104 0.896,-2 2,-2 h 32.992 c 1.104,0 2,0.896 2,2 v 34.014 h 22.162 c 1.104,0 2,0.896 2,1.999 v 22.073 c 0,1.104 -0.896,1.999 -2,1.999 h -22.162 v 57.479 c 0,6.229 1.198,8.731 2.202,9.733 1.004,1.007 3.506,2.205 9.738,2.205 1.804,0 3.542,-0.076 5.161,-0.225 1.604,-0.144 3.174,-0.367 4.669,-0.665 0.13,-0.026 0.261,-0.039 0.391,-0.039 0.458,0 0.907,0.159 1.27,0.454 0.463,0.379 0.73,0.946 0.73,1.546 v 25.555 c 0,0.979 -0.707,1.813 -1.672,1.974 -2.834,0.472 -6.041,0.794 -9.527,0.957 -3.613,0.157 -6.91,0.233 -10.086,0.233 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5245"
|
|
||||||
d="m 463.825,111.595 v 22.072 h -24.161 v 59.479 c 0,5.573 0.928,9.292 2.788,11.149 1.856,1.859 5.576,2.788 11.152,2.788 1.859,0 3.638,-0.076 5.343,-0.232 1.703,-0.152 3.33,-0.388 4.878,-0.696 v 25.557 c -2.788,0.465 -5.887,0.773 -9.293,0.931 -3.407,0.149 -6.737,0.23 -9.99,0.23 -5.111,0 -9.953,-0.35 -14.521,-1.048 -4.571,-0.695 -8.597,-2.047 -12.081,-4.063 -3.486,-2.011 -6.236,-4.88 -8.248,-8.597 -2.016,-3.714 -3.021,-8.595 -3.021,-14.639 v -70.859 h -19.98 v -22.072 h 19.98 V 75.583 h 32.992 v 36.012 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5247"
|
|
||||||
d="m 512.613,233.711 h -32.991 c -1.104,0 -2,-0.896 -2,-2 V 111.594 c 0,-1.104 0.896,-1.999 2,-1.999 h 31.366 c 1.104,0 2,0.896 2,1.999 v 15.069 c 0.967,-1.516 2.034,-2.978 3.199,-4.382 2.754,-3.312 5.949,-6.182 9.496,-8.522 3.545,-2.332 7.385,-4.169 11.415,-5.462 4.056,-1.298 8.327,-1.954 12.691,-1.954 2.341,0 4.953,0.418 7.766,1.243 0.852,0.25 1.437,1.032 1.437,1.92 v 30.67 c 0,0.6 -0.269,1.167 -0.732,1.547 -0.361,0.296 -0.808,0.452 -1.265,0.452 -0.133,0 -0.265,-0.013 -0.398,-0.039 -1.484,-0.3 -3.299,-0.565 -5.392,-0.787 -2.098,-0.224 -4.136,-0.339 -6.062,-0.339 -5.706,0 -10.572,0.95 -14.467,2.823 -3.862,1.86 -7.012,4.428 -9.361,7.629 -2.389,3.263 -4.115,7.12 -5.127,11.47 -1.043,4.479 -1.574,9.409 -1.574,14.647 v 54.132 c -10e-4,1.104 -0.897,2 -2.001,2 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5249"
|
|
||||||
d="M 510.988,111.595 V 133.9 h 0.465 c 1.546,-3.72 3.636,-7.163 6.272,-10.341 2.634,-3.172 5.652,-5.885 9.06,-8.131 3.405,-2.242 7.047,-3.985 10.923,-5.228 3.868,-1.237 7.898,-1.859 12.081,-1.859 2.168,0 4.566,0.39 7.202,1.163 v 30.67 c -1.551,-0.312 -3.41,-0.584 -5.576,-0.814 -2.17,-0.233 -4.26,-0.35 -6.274,-0.35 -6.041,0 -11.152,1.01 -15.332,3.021 -4.182,2.014 -7.55,4.761 -10.107,8.247 -2.555,3.487 -4.379,7.55 -5.462,12.198 -1.083,4.645 -1.625,9.682 -1.625,15.102 v 54.133 H 479.624 V 111.595 Z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5251"
|
|
||||||
d="M 603.923,233.711 H 570.93 c -1.104,0 -2,-0.896 -2,-2 V 111.594 c 0,-1.104 0.896,-1.999 2,-1.999 h 32.994 c 1.104,0 2,0.896 2,1.999 v 120.117 c -10e-4,1.104 -0.897,2 -2.001,2 z m 0,-138.705 H 570.93 c -1.104,0 -2,-0.896 -2,-1.999 V 65.825 c 0,-1.104 0.896,-2 2,-2 h 32.994 c 1.104,0 2,0.896 2,2 v 27.182 c -10e-4,1.103 -0.897,1.999 -2.001,1.999 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5253"
|
|
||||||
d="M 570.93,93.007 V 65.824 h 32.994 v 27.183 z m 32.994,18.588 V 231.712 H 570.93 V 111.595 Z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5255"
|
|
||||||
d="m 742.163,233.711 h -37.64 c -0.671,0 -1.297,-0.335 -1.667,-0.896 l -23.426,-35.352 -23.426,35.352 c -0.369,0.561 -0.995,0.896 -1.667,0.896 h -36.938 c -0.741,0 -1.424,-0.411 -1.77,-1.067 -0.345,-0.654 -0.3,-1.449 0.118,-2.061 l 42.435,-62.055 -38.71,-55.793 c -0.424,-0.613 -0.474,-1.408 -0.128,-2.069 0.343,-0.658 1.028,-1.071 1.771,-1.071 h 37.636 c 0.665,0 1.287,0.33 1.658,0.882 l 19.477,28.893 19.255,-28.884 c 0.372,-0.556 0.996,-0.891 1.665,-0.891 h 36.475 c 0.746,0 1.43,0.415 1.776,1.078 0.343,0.66 0.289,1.46 -0.139,2.071 l -38.69,55.082 43.578,62.744 c 0.424,0.61 0.474,1.408 0.128,2.066 -0.343,0.662 -1.026,1.075 -1.771,1.075 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5257"
|
|
||||||
d="m 621.115,111.595 h 37.637 l 21.144,31.365 20.911,-31.365 h 36.476 l -39.496,56.226 44.377,63.892 h -37.64 l -25.093,-37.87 -25.094,37.87 h -36.938 l 43.213,-63.193 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5259"
|
|
||||||
d="m 791.322,340.809 h -32.008 c -1.105,0 -2,-0.896 -2,-2 v -7.712 c 0,-1.105 0.896,-2.001 2,-2.001 h 21.13 V 11.71 h -21.13 c -1.105,0 -2,-0.896 -2,-2 V 2 c 0,-1.104 0.896,-2 2,-2 h 32.008 c 1.104,0 2,0.896 2,2 v 336.809 c 0,1.104 -0.896,2 -2,2 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5261"
|
|
||||||
d="M 782.443,331.097 V 9.711 h -23.13 v -7.71 h 32.008 v 336.807 h -32.008 v -7.711 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5263"
|
|
||||||
d="m 10.875,9.711 v 321.386 h 23.13 v 7.711 H 1.999 V 2.001 h 32.006 v 7.71 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5265"
|
|
||||||
d="m 99.988,111.595 v 16.264 h 0.463 c 4.338,-6.191 9.563,-10.998 15.684,-14.406 6.117,-3.402 13.129,-5.11 21.027,-5.11 7.588,0 14.521,1.475 20.793,4.415 6.274,2.945 11.038,8.131 14.291,15.567 3.56,-5.265 8.4,-9.913 14.521,-13.94 6.117,-4.025 13.358,-6.042 21.724,-6.042 6.351,0 12.234,0.776 17.66,2.325 5.418,1.549 10.065,4.027 13.938,7.434 3.869,3.41 6.889,7.863 9.062,13.357 2.167,5.504 3.253,12.122 3.253,19.869 v 80.385 H 219.41 v -68.074 c 0,-4.025 -0.154,-7.82 -0.465,-11.385 -0.313,-3.56 -1.161,-6.656 -2.555,-9.293 -1.395,-2.631 -3.45,-4.724 -6.157,-6.274 -2.711,-1.543 -6.391,-2.322 -11.037,-2.322 -4.646,0 -8.403,0.896 -11.269,2.671 -2.868,1.784 -5.112,4.109 -6.737,6.971 -1.626,2.869 -2.711,6.12 -3.252,9.762 -0.545,3.638 -0.814,7.318 -0.814,11.035 v 66.91 h -32.991 v -67.375 c 0,-3.562 -0.081,-7.087 -0.23,-10.57 -0.158,-3.487 -0.814,-6.7 -1.978,-9.645 -1.162,-2.94 -3.099,-5.304 -5.809,-7.088 -2.711,-1.775 -6.699,-2.671 -11.965,-2.671 -1.551,0 -3.603,0.349 -6.156,1.048 -2.556,0.697 -5.036,2.016 -7.435,3.949 -2.404,1.938 -4.454,4.726 -6.158,8.363 -1.705,3.642 -2.556,8.402 -2.556,14.287 v 69.701 H 68.856 V 111.595 Z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5267"
|
|
||||||
d="m 273.544,129.255 c 3.405,-5.113 7.744,-9.215 13.012,-12.316 5.264,-3.097 11.186,-5.303 17.771,-6.621 6.582,-1.315 13.205,-1.976 19.865,-1.976 6.042,0 12.158,0.428 18.354,1.277 6.195,0.855 11.85,2.522 16.962,4.997 5.111,2.477 9.292,5.926 12.546,10.338 3.253,4.414 4.879,10.262 4.879,17.543 v 62.494 c 0,5.428 0.31,10.611 0.931,15.567 0.615,4.959 1.701,8.676 3.251,11.153 H 347.66 c -0.621,-1.86 -1.126,-3.755 -1.511,-5.693 -0.39,-1.933 -0.661,-3.908 -0.813,-5.923 -5.267,5.422 -11.465,9.217 -18.585,11.386 -7.127,2.163 -14.407,3.251 -21.842,3.251 -5.733,0 -11.077,-0.698 -16.033,-2.09 -4.958,-1.395 -9.293,-3.562 -13.01,-6.51 -3.718,-2.938 -6.622,-6.656 -8.713,-11.147 -2.091,-4.491 -3.138,-9.84 -3.138,-16.033 0,-6.813 1.199,-12.43 3.604,-16.84 2.399,-4.417 5.495,-7.939 9.295,-10.575 3.793,-2.632 8.129,-4.607 13.01,-5.923 4.878,-1.315 9.795,-2.358 14.752,-3.137 4.957,-0.772 9.835,-1.393 14.638,-1.857 4.801,-0.466 9.062,-1.164 12.779,-2.093 3.718,-0.929 6.658,-2.282 8.829,-4.065 2.165,-1.781 3.172,-4.375 3.02,-7.785 0,-3.56 -0.58,-6.389 -1.742,-8.479 -1.161,-2.09 -2.711,-3.719 -4.646,-4.88 -1.937,-1.161 -4.183,-1.936 -6.737,-2.325 -2.557,-0.382 -5.309,-0.58 -8.248,-0.58 -6.506,0 -11.617,1.395 -15.335,4.183 -3.716,2.788 -5.889,7.437 -6.506,13.94 h -32.991 c 0.462,-7.742 2.395,-14.173 5.807,-19.281 z m 65.169,46.583 c -2.09,0.696 -4.337,1.275 -6.736,1.741 -2.402,0.465 -4.918,0.853 -7.551,1.161 -2.635,0.313 -5.268,0.698 -7.899,1.163 -2.48,0.461 -4.919,1.086 -7.317,1.857 -2.404,0.779 -4.495,1.822 -6.274,3.138 -1.784,1.317 -3.216,2.985 -4.3,4.994 -1.085,2.014 -1.626,4.571 -1.626,7.668 0,2.94 0.541,5.422 1.626,7.431 1.084,2.017 2.558,3.604 4.416,4.765 1.858,1.161 4.025,1.976 6.507,2.438 2.475,0.466 5.031,0.698 7.665,0.698 6.505,0 11.537,-1.082 15.103,-3.253 3.561,-2.166 6.192,-4.762 7.899,-7.785 1.702,-3.019 2.749,-6.072 3.137,-9.174 0.384,-3.097 0.58,-5.576 0.58,-7.434 V 172.93 c -1.396,1.243 -3.138,2.21 -5.23,2.908 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5269"
|
|
||||||
d="m 463.825,111.595 v 22.072 h -24.161 v 59.479 c 0,5.573 0.928,9.292 2.788,11.149 1.856,1.859 5.576,2.788 11.152,2.788 1.859,0 3.638,-0.076 5.343,-0.232 1.703,-0.152 3.33,-0.388 4.878,-0.696 v 25.557 c -2.788,0.465 -5.887,0.773 -9.293,0.931 -3.407,0.149 -6.737,0.23 -9.99,0.23 -5.111,0 -9.953,-0.35 -14.521,-1.048 -4.571,-0.695 -8.597,-2.047 -12.081,-4.063 -3.486,-2.011 -6.236,-4.88 -8.248,-8.597 -2.016,-3.714 -3.021,-8.595 -3.021,-14.639 v -70.859 h -19.98 v -22.072 h 19.98 V 75.583 h 32.992 v 36.012 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5271"
|
|
||||||
d="M 510.988,111.595 V 133.9 h 0.465 c 1.546,-3.72 3.636,-7.163 6.272,-10.341 2.634,-3.172 5.652,-5.885 9.06,-8.131 3.405,-2.242 7.047,-3.985 10.923,-5.228 3.868,-1.237 7.898,-1.859 12.081,-1.859 2.168,0 4.566,0.39 7.202,1.163 v 30.67 c -1.551,-0.312 -3.41,-0.584 -5.576,-0.814 -2.17,-0.233 -4.26,-0.35 -6.274,-0.35 -6.041,0 -11.152,1.01 -15.332,3.021 -4.182,2.014 -7.55,4.761 -10.107,8.247 -2.555,3.487 -4.379,7.55 -5.462,12.198 -1.083,4.645 -1.625,9.682 -1.625,15.102 v 54.133 H 479.624 V 111.595 Z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5273"
|
|
||||||
d="M 570.93,93.007 V 65.824 h 32.994 v 27.183 z m 32.994,18.588 V 231.712 H 570.93 V 111.595 Z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5275"
|
|
||||||
d="m 621.115,111.595 h 37.637 l 21.144,31.365 20.911,-31.365 h 36.476 l -39.496,56.226 44.377,63.892 h -37.64 l -25.093,-37.87 -25.094,37.87 h -36.938 l 43.213,-63.193 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5277"
|
|
||||||
d="M 782.443,331.097 V 9.711 h -23.13 v -7.71 h 32.008 v 336.807 h -32.008 v -7.711 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 22 KiB |
|
@ -1,219 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="1280"
|
|
||||||
height="960"
|
|
||||||
viewBox="0 0 338.66666 254.00001"
|
|
||||||
version="1.1"
|
|
||||||
id="svg4636"
|
|
||||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
|
||||||
sodipodi:docname="drawing.svg">
|
|
||||||
<defs
|
|
||||||
id="defs4630" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="1"
|
|
||||||
inkscape:cx="686.38953"
|
|
||||||
inkscape:cy="426.35799"
|
|
||||||
inkscape:document-units="px"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
units="px"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1050"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="30"
|
|
||||||
inkscape:window-maximized="1" />
|
|
||||||
<metadata
|
|
||||||
id="metadata4633">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title></dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(0,-42.999983)">
|
|
||||||
<circle
|
|
||||||
id="path4638"
|
|
||||||
cx="274.10834"
|
|
||||||
cy="259.42917"
|
|
||||||
r="75.40625"
|
|
||||||
style="opacity:0.25;fill:#eceff1;fill-opacity:1;stroke-width:0.26458335" />
|
|
||||||
<circle
|
|
||||||
id="path4638-6"
|
|
||||||
cx="303.0802"
|
|
||||||
cy="169.86771"
|
|
||||||
r="48.286457"
|
|
||||||
style="opacity:0.25;fill:#eceff1;fill-opacity:1;stroke-width:0.16942617" />
|
|
||||||
<circle
|
|
||||||
id="path4638-6-7"
|
|
||||||
cx="165.76144"
|
|
||||||
cy="82.290604"
|
|
||||||
r="25.135412"
|
|
||||||
style="opacity:0.25;fill:#eceff1;fill-opacity:1;stroke-width:0.08819444" />
|
|
||||||
<circle
|
|
||||||
id="path4638-6-5"
|
|
||||||
cx="38.100006"
|
|
||||||
cy="195.3998"
|
|
||||||
r="76.332291"
|
|
||||||
style="opacity:0.25;fill:#eceff1;fill-opacity:1;stroke-width:0.26783261" />
|
|
||||||
<circle
|
|
||||||
id="path4638-6-3"
|
|
||||||
cx="87.709373"
|
|
||||||
cy="148.70103"
|
|
||||||
r="41.01041"
|
|
||||||
style="opacity:0.25;fill:#eceff1;fill-opacity:1;stroke-width:0.14389619" />
|
|
||||||
<circle
|
|
||||||
id="path4638-6-56"
|
|
||||||
cx="220.3979"
|
|
||||||
cy="100.94374"
|
|
||||||
r="48.286453"
|
|
||||||
style="opacity:0.25;fill:#eceff1;fill-opacity:1;stroke-width:0.16942617" />
|
|
||||||
<g
|
|
||||||
id="g5310"
|
|
||||||
transform="matrix(0.18980272,0,0,0.18980272,163.39608,213.89968)"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero">
|
|
||||||
<path
|
|
||||||
id="path5231"
|
|
||||||
d="M 34.004,340.809 H 2 c -1.104,0 -2,-0.896 -2,-2 V 2 C 0,0.896 0.896,0 2,0 h 32.004 c 1.104,0 2,0.896 2,2 v 7.71 c 0,1.104 -0.896,2 -2,2 h -21.13 v 317.386 h 21.13 c 1.104,0 2,0.896 2,2.001 v 7.712 c 0,1.104 -0.896,2 -2,2 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5233"
|
|
||||||
d="m 10.875,9.711 v 321.386 h 23.13 v 7.711 H 1.999 V 2.001 h 32.006 v 7.71 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5235"
|
|
||||||
d="m 252.402,233.711 h -32.993 c -1.104,0 -2,-0.896 -2,-2 v -68.073 c 0,-3.949 -0.154,-7.722 -0.457,-11.213 -0.289,-3.282 -1.074,-6.153 -2.332,-8.53 -1.204,-2.276 -3.017,-4.119 -5.384,-5.476 -2.393,-1.362 -5.775,-2.056 -10.042,-2.056 -4.238,0 -7.674,0.798 -10.213,2.371 -2.565,1.596 -4.604,3.701 -6.053,6.258 -1.498,2.643 -2.51,5.694 -3.013,9.067 -0.526,3.513 -0.793,7.125 -0.793,10.741 v 66.91 c 0,1.104 -0.896,2 -2,2 h -32.991 c -1.104,0 -2,-0.896 -2,-2 v -67.373 c 0,-3.435 -0.078,-6.964 -0.228,-10.485 -0.148,-3.251 -0.767,-6.278 -1.841,-8.995 -1.018,-2.571 -2.667,-4.584 -5.047,-6.153 -2.372,-1.552 -6.029,-2.341 -10.865,-2.341 -1.372,0 -3.265,0.328 -5.629,0.976 -2.28,0.624 -4.536,1.826 -6.705,3.577 -2.152,1.732 -4.036,4.306 -5.605,7.655 -1.569,3.356 -2.367,7.877 -2.367,13.438 v 69.701 c 0,1.104 -0.895,2 -2,2 H 68.857 c -1.104,0 -2,-0.896 -2,-2 V 111.594 c 0,-1.104 0.896,-1.999 2,-1.999 h 31.13 c 1.104,0 2,0.896 2,1.999 v 11.007 c 3.834,-4.499 8.248,-8.152 13.173,-10.896 6.396,-3.559 13.799,-5.362 22.002,-5.362 7.846,0 15.127,1.548 21.642,4.604 5.794,2.722 10.424,7.26 13.791,13.52 3.449,-4.362 7.833,-8.306 13.071,-11.752 6.422,-4.228 14.102,-6.371 22.824,-6.371 6.499,0 12.625,0.807 18.209,2.399 5.686,1.628 10.635,4.271 14.712,7.857 4.088,3.605 7.318,8.357 9.601,14.123 2.25,5.719 3.391,12.649 3.391,20.604 v 80.384 c -0.001,1.104 -0.896,2 -2.001,2 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5237"
|
|
||||||
d="m 99.988,111.595 v 16.264 h 0.463 c 4.338,-6.191 9.563,-10.998 15.684,-14.406 6.117,-3.402 13.129,-5.11 21.027,-5.11 7.588,0 14.521,1.475 20.793,4.415 6.274,2.945 11.038,8.131 14.291,15.567 3.56,-5.265 8.4,-9.913 14.521,-13.94 6.117,-4.025 13.358,-6.042 21.724,-6.042 6.351,0 12.234,0.776 17.66,2.325 5.418,1.549 10.065,4.027 13.938,7.434 3.869,3.41 6.889,7.863 9.062,13.357 2.167,5.504 3.253,12.122 3.253,19.869 v 80.385 H 219.41 v -68.074 c 0,-4.025 -0.154,-7.82 -0.465,-11.385 -0.313,-3.56 -1.161,-6.656 -2.555,-9.293 -1.395,-2.631 -3.45,-4.724 -6.157,-6.274 -2.711,-1.543 -6.391,-2.322 -11.037,-2.322 -4.646,0 -8.403,0.896 -11.269,2.671 -2.868,1.784 -5.112,4.109 -6.737,6.971 -1.626,2.869 -2.711,6.12 -3.252,9.762 -0.545,3.638 -0.814,7.318 -0.814,11.035 v 66.91 h -32.991 v -67.375 c 0,-3.562 -0.081,-7.087 -0.23,-10.57 -0.158,-3.487 -0.814,-6.7 -1.978,-9.645 -1.162,-2.94 -3.099,-5.304 -5.809,-7.088 -2.711,-1.775 -6.699,-2.671 -11.965,-2.671 -1.551,0 -3.603,0.349 -6.156,1.048 -2.556,0.697 -5.036,2.016 -7.435,3.949 -2.404,1.938 -4.454,4.726 -6.158,8.363 -1.705,3.642 -2.556,8.402 -2.556,14.287 v 69.701 H 68.856 V 111.595 Z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5239"
|
|
||||||
d="m 304.909,236.733 c -5.883,0 -11.46,-0.729 -16.574,-2.163 -5.192,-1.464 -9.806,-3.774 -13.713,-6.871 -3.944,-3.117 -7.068,-7.111 -9.282,-11.871 -2.205,-4.733 -3.324,-10.412 -3.324,-16.876 0,-7.13 1.293,-13.117 3.846,-17.797 2.542,-4.674 5.877,-8.464 9.912,-11.263 3.97,-2.752 8.556,-4.842 13.63,-6.209 4.901,-1.322 9.937,-2.394 14.961,-3.184 4.986,-0.775 9.949,-1.404 14.754,-1.872 4.679,-0.452 8.88,-1.139 12.489,-2.039 3.412,-0.854 6.118,-2.09 8.042,-3.672 1.666,-1.37 2.416,-3.384 2.292,-6.151 -0.002,-3.289 -0.502,-5.816 -1.492,-7.595 -0.998,-1.798 -2.283,-3.15 -3.927,-4.138 -1.703,-1.02 -3.725,-1.713 -6.012,-2.062 -2.47,-0.37 -5.146,-0.557 -7.947,-0.557 -6.034,0 -10.789,1.271 -14.135,3.783 -3.233,2.424 -5.155,6.64 -5.714,12.527 -0.098,1.026 -0.961,1.812 -1.992,1.812 h -32.992 c -0.552,0 -1.079,-0.229 -1.457,-0.629 -0.376,-0.402 -0.572,-0.941 -0.54,-1.491 0.485,-8.073 2.55,-14.894 6.142,-20.272 3.548,-5.331 8.147,-9.682 13.661,-12.931 5.424,-3.191 11.612,-5.498 18.392,-6.857 6.684,-1.335 13.5,-2.013 20.26,-2.013 6.096,0 12.365,0.437 18.626,1.296 6.377,0.88 12.285,2.622 17.562,5.177 5.376,2.604 9.845,6.29 13.282,10.951 3.498,4.744 5.271,11.048 5.271,18.731 v 62.494 c 0,5.307 0.306,10.462 0.915,15.319 0.576,4.64 1.572,8.116 2.963,10.338 0.385,0.616 0.407,1.395 0.055,2.031 -0.353,0.635 -1.022,1.03 -1.75,1.03 h -33.457 c -0.861,0 -1.624,-0.55 -1.898,-1.367 -0.646,-1.941 -1.176,-3.939 -1.572,-5.936 -0.141,-0.696 -0.267,-1.402 -0.38,-2.12 -4.825,4.184 -10.349,7.24 -16.474,9.105 -7.299,2.218 -14.843,3.342 -22.423,3.342 z m 37.032,-60.072 c -0.809,0.409 -1.676,0.768 -2.596,1.074 -2.161,0.72 -4.511,1.326 -6.988,1.807 -2.442,0.475 -5.033,0.872 -7.699,1.186 -2.631,0.311 -5.251,0.697 -7.784,1.146 -2.329,0.433 -4.705,1.035 -7.051,1.792 -2.194,0.711 -4.114,1.667 -5.699,2.842 -1.531,1.128 -2.785,2.587 -3.731,4.335 -0.917,1.709 -1.385,3.97 -1.385,6.719 0,2.598 0.465,4.778 1.385,6.481 0.928,1.722 2.142,3.035 3.716,4.018 1.644,1.026 3.601,1.757 5.816,2.17 2.344,0.439 4.799,0.663 7.297,0.663 6.105,0 10.836,-0.996 14.063,-2.961 3.244,-1.973 5.666,-4.349 7.199,-7.062 1.568,-2.78 2.542,-5.62 2.892,-8.436 0.376,-3.019 0.565,-5.436 0.565,-7.187 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5241"
|
|
||||||
d="m 273.544,129.255 c 3.405,-5.113 7.744,-9.215 13.012,-12.316 5.264,-3.097 11.186,-5.303 17.771,-6.621 6.582,-1.315 13.205,-1.976 19.865,-1.976 6.042,0 12.158,0.428 18.354,1.277 6.195,0.855 11.85,2.522 16.962,4.997 5.111,2.477 9.292,5.926 12.546,10.338 3.253,4.414 4.879,10.262 4.879,17.543 v 62.494 c 0,5.428 0.31,10.611 0.931,15.567 0.615,4.959 1.701,8.676 3.251,11.153 H 347.66 c -0.621,-1.86 -1.126,-3.755 -1.511,-5.693 -0.39,-1.933 -0.661,-3.908 -0.813,-5.923 -5.267,5.422 -11.465,9.217 -18.585,11.386 -7.127,2.163 -14.407,3.251 -21.842,3.251 -5.733,0 -11.077,-0.698 -16.033,-2.09 -4.958,-1.395 -9.293,-3.562 -13.01,-6.51 -3.718,-2.938 -6.622,-6.656 -8.713,-11.147 -2.091,-4.491 -3.138,-9.84 -3.138,-16.033 0,-6.813 1.199,-12.43 3.604,-16.84 2.399,-4.417 5.495,-7.939 9.295,-10.575 3.793,-2.632 8.129,-4.607 13.01,-5.923 4.878,-1.315 9.795,-2.358 14.752,-3.137 4.957,-0.772 9.835,-1.393 14.638,-1.857 4.801,-0.466 9.062,-1.164 12.779,-2.093 3.718,-0.929 6.658,-2.282 8.829,-4.065 2.165,-1.781 3.172,-4.375 3.02,-7.785 0,-3.56 -0.58,-6.389 -1.742,-8.479 -1.161,-2.09 -2.711,-3.719 -4.646,-4.88 -1.937,-1.161 -4.183,-1.936 -6.737,-2.325 -2.557,-0.382 -5.309,-0.58 -8.248,-0.58 -6.506,0 -11.617,1.395 -15.335,4.183 -3.716,2.788 -5.889,7.437 -6.506,13.94 h -32.991 c 0.462,-7.742 2.395,-14.173 5.807,-19.281 z m 65.169,46.583 c -2.09,0.696 -4.337,1.275 -6.736,1.741 -2.402,0.465 -4.918,0.853 -7.551,1.161 -2.635,0.313 -5.268,0.698 -7.899,1.163 -2.48,0.461 -4.919,1.086 -7.317,1.857 -2.404,0.779 -4.495,1.822 -6.274,3.138 -1.784,1.317 -3.216,2.985 -4.3,4.994 -1.085,2.014 -1.626,4.571 -1.626,7.668 0,2.94 0.541,5.422 1.626,7.431 1.084,2.017 2.558,3.604 4.416,4.765 1.858,1.161 4.025,1.976 6.507,2.438 2.475,0.466 5.031,0.698 7.665,0.698 6.505,0 11.537,-1.082 15.103,-3.253 3.561,-2.166 6.192,-4.762 7.899,-7.785 1.702,-3.019 2.749,-6.072 3.137,-9.174 0.384,-3.097 0.58,-5.576 0.58,-7.434 V 172.93 c -1.396,1.243 -3.138,2.21 -5.23,2.908 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5243"
|
|
||||||
d="m 444.542,234.874 c -5.187,0 -10.173,-0.361 -14.823,-1.069 -4.802,-0.732 -9.104,-2.183 -12.779,-4.313 -3.789,-2.185 -6.821,-5.341 -9.006,-9.375 -2.163,-3.986 -3.26,-9.232 -3.26,-15.59 v -68.859 h -17.981 c -1.104,0 -2,-0.896 -2,-1.999 v -22.073 c 0,-1.104 0.896,-1.999 2,-1.999 h 17.981 V 75.582 c 0,-1.104 0.896,-2 2,-2 h 32.992 c 1.104,0 2,0.896 2,2 v 34.014 h 22.162 c 1.104,0 2,0.896 2,1.999 v 22.073 c 0,1.104 -0.896,1.999 -2,1.999 h -22.162 v 57.479 c 0,6.229 1.198,8.731 2.202,9.733 1.004,1.007 3.506,2.205 9.738,2.205 1.804,0 3.542,-0.076 5.161,-0.225 1.604,-0.144 3.174,-0.367 4.669,-0.665 0.13,-0.026 0.261,-0.039 0.391,-0.039 0.458,0 0.907,0.159 1.27,0.454 0.463,0.379 0.73,0.946 0.73,1.546 v 25.555 c 0,0.979 -0.707,1.813 -1.672,1.974 -2.834,0.472 -6.041,0.794 -9.527,0.957 -3.613,0.157 -6.91,0.233 -10.086,0.233 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5245"
|
|
||||||
d="m 463.825,111.595 v 22.072 h -24.161 v 59.479 c 0,5.573 0.928,9.292 2.788,11.149 1.856,1.859 5.576,2.788 11.152,2.788 1.859,0 3.638,-0.076 5.343,-0.232 1.703,-0.152 3.33,-0.388 4.878,-0.696 v 25.557 c -2.788,0.465 -5.887,0.773 -9.293,0.931 -3.407,0.149 -6.737,0.23 -9.99,0.23 -5.111,0 -9.953,-0.35 -14.521,-1.048 -4.571,-0.695 -8.597,-2.047 -12.081,-4.063 -3.486,-2.011 -6.236,-4.88 -8.248,-8.597 -2.016,-3.714 -3.021,-8.595 -3.021,-14.639 v -70.859 h -19.98 v -22.072 h 19.98 V 75.583 h 32.992 v 36.012 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5247"
|
|
||||||
d="m 512.613,233.711 h -32.991 c -1.104,0 -2,-0.896 -2,-2 V 111.594 c 0,-1.104 0.896,-1.999 2,-1.999 h 31.366 c 1.104,0 2,0.896 2,1.999 v 15.069 c 0.967,-1.516 2.034,-2.978 3.199,-4.382 2.754,-3.312 5.949,-6.182 9.496,-8.522 3.545,-2.332 7.385,-4.169 11.415,-5.462 4.056,-1.298 8.327,-1.954 12.691,-1.954 2.341,0 4.953,0.418 7.766,1.243 0.852,0.25 1.437,1.032 1.437,1.92 v 30.67 c 0,0.6 -0.269,1.167 -0.732,1.547 -0.361,0.296 -0.808,0.452 -1.265,0.452 -0.133,0 -0.265,-0.013 -0.398,-0.039 -1.484,-0.3 -3.299,-0.565 -5.392,-0.787 -2.098,-0.224 -4.136,-0.339 -6.062,-0.339 -5.706,0 -10.572,0.95 -14.467,2.823 -3.862,1.86 -7.012,4.428 -9.361,7.629 -2.389,3.263 -4.115,7.12 -5.127,11.47 -1.043,4.479 -1.574,9.409 -1.574,14.647 v 54.132 c -10e-4,1.104 -0.897,2 -2.001,2 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5249"
|
|
||||||
d="M 510.988,111.595 V 133.9 h 0.465 c 1.546,-3.72 3.636,-7.163 6.272,-10.341 2.634,-3.172 5.652,-5.885 9.06,-8.131 3.405,-2.242 7.047,-3.985 10.923,-5.228 3.868,-1.237 7.898,-1.859 12.081,-1.859 2.168,0 4.566,0.39 7.202,1.163 v 30.67 c -1.551,-0.312 -3.41,-0.584 -5.576,-0.814 -2.17,-0.233 -4.26,-0.35 -6.274,-0.35 -6.041,0 -11.152,1.01 -15.332,3.021 -4.182,2.014 -7.55,4.761 -10.107,8.247 -2.555,3.487 -4.379,7.55 -5.462,12.198 -1.083,4.645 -1.625,9.682 -1.625,15.102 v 54.133 H 479.624 V 111.595 Z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5251"
|
|
||||||
d="M 603.923,233.711 H 570.93 c -1.104,0 -2,-0.896 -2,-2 V 111.594 c 0,-1.104 0.896,-1.999 2,-1.999 h 32.994 c 1.104,0 2,0.896 2,1.999 v 120.117 c -10e-4,1.104 -0.897,2 -2.001,2 z m 0,-138.705 H 570.93 c -1.104,0 -2,-0.896 -2,-1.999 V 65.825 c 0,-1.104 0.896,-2 2,-2 h 32.994 c 1.104,0 2,0.896 2,2 v 27.182 c -10e-4,1.103 -0.897,1.999 -2.001,1.999 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5253"
|
|
||||||
d="M 570.93,93.007 V 65.824 h 32.994 v 27.183 z m 32.994,18.588 V 231.712 H 570.93 V 111.595 Z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5255"
|
|
||||||
d="m 742.163,233.711 h -37.64 c -0.671,0 -1.297,-0.335 -1.667,-0.896 l -23.426,-35.352 -23.426,35.352 c -0.369,0.561 -0.995,0.896 -1.667,0.896 h -36.938 c -0.741,0 -1.424,-0.411 -1.77,-1.067 -0.345,-0.654 -0.3,-1.449 0.118,-2.061 l 42.435,-62.055 -38.71,-55.793 c -0.424,-0.613 -0.474,-1.408 -0.128,-2.069 0.343,-0.658 1.028,-1.071 1.771,-1.071 h 37.636 c 0.665,0 1.287,0.33 1.658,0.882 l 19.477,28.893 19.255,-28.884 c 0.372,-0.556 0.996,-0.891 1.665,-0.891 h 36.475 c 0.746,0 1.43,0.415 1.776,1.078 0.343,0.66 0.289,1.46 -0.139,2.071 l -38.69,55.082 43.578,62.744 c 0.424,0.61 0.474,1.408 0.128,2.066 -0.343,0.662 -1.026,1.075 -1.771,1.075 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5257"
|
|
||||||
d="m 621.115,111.595 h 37.637 l 21.144,31.365 20.911,-31.365 h 36.476 l -39.496,56.226 44.377,63.892 h -37.64 l -25.093,-37.87 -25.094,37.87 h -36.938 l 43.213,-63.193 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5259"
|
|
||||||
d="m 791.322,340.809 h -32.008 c -1.105,0 -2,-0.896 -2,-2 v -7.712 c 0,-1.105 0.896,-2.001 2,-2.001 h 21.13 V 11.71 h -21.13 c -1.105,0 -2,-0.896 -2,-2 V 2 c 0,-1.104 0.896,-2 2,-2 h 32.008 c 1.104,0 2,0.896 2,2 v 336.809 c 0,1.104 -0.896,2 -2,2 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5261"
|
|
||||||
d="M 782.443,331.097 V 9.711 h -23.13 v -7.71 h 32.008 v 336.807 h -32.008 v -7.711 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5263"
|
|
||||||
d="m 10.875,9.711 v 321.386 h 23.13 v 7.711 H 1.999 V 2.001 h 32.006 v 7.71 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5265"
|
|
||||||
d="m 99.988,111.595 v 16.264 h 0.463 c 4.338,-6.191 9.563,-10.998 15.684,-14.406 6.117,-3.402 13.129,-5.11 21.027,-5.11 7.588,0 14.521,1.475 20.793,4.415 6.274,2.945 11.038,8.131 14.291,15.567 3.56,-5.265 8.4,-9.913 14.521,-13.94 6.117,-4.025 13.358,-6.042 21.724,-6.042 6.351,0 12.234,0.776 17.66,2.325 5.418,1.549 10.065,4.027 13.938,7.434 3.869,3.41 6.889,7.863 9.062,13.357 2.167,5.504 3.253,12.122 3.253,19.869 v 80.385 H 219.41 v -68.074 c 0,-4.025 -0.154,-7.82 -0.465,-11.385 -0.313,-3.56 -1.161,-6.656 -2.555,-9.293 -1.395,-2.631 -3.45,-4.724 -6.157,-6.274 -2.711,-1.543 -6.391,-2.322 -11.037,-2.322 -4.646,0 -8.403,0.896 -11.269,2.671 -2.868,1.784 -5.112,4.109 -6.737,6.971 -1.626,2.869 -2.711,6.12 -3.252,9.762 -0.545,3.638 -0.814,7.318 -0.814,11.035 v 66.91 h -32.991 v -67.375 c 0,-3.562 -0.081,-7.087 -0.23,-10.57 -0.158,-3.487 -0.814,-6.7 -1.978,-9.645 -1.162,-2.94 -3.099,-5.304 -5.809,-7.088 -2.711,-1.775 -6.699,-2.671 -11.965,-2.671 -1.551,0 -3.603,0.349 -6.156,1.048 -2.556,0.697 -5.036,2.016 -7.435,3.949 -2.404,1.938 -4.454,4.726 -6.158,8.363 -1.705,3.642 -2.556,8.402 -2.556,14.287 v 69.701 H 68.856 V 111.595 Z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5267"
|
|
||||||
d="m 273.544,129.255 c 3.405,-5.113 7.744,-9.215 13.012,-12.316 5.264,-3.097 11.186,-5.303 17.771,-6.621 6.582,-1.315 13.205,-1.976 19.865,-1.976 6.042,0 12.158,0.428 18.354,1.277 6.195,0.855 11.85,2.522 16.962,4.997 5.111,2.477 9.292,5.926 12.546,10.338 3.253,4.414 4.879,10.262 4.879,17.543 v 62.494 c 0,5.428 0.31,10.611 0.931,15.567 0.615,4.959 1.701,8.676 3.251,11.153 H 347.66 c -0.621,-1.86 -1.126,-3.755 -1.511,-5.693 -0.39,-1.933 -0.661,-3.908 -0.813,-5.923 -5.267,5.422 -11.465,9.217 -18.585,11.386 -7.127,2.163 -14.407,3.251 -21.842,3.251 -5.733,0 -11.077,-0.698 -16.033,-2.09 -4.958,-1.395 -9.293,-3.562 -13.01,-6.51 -3.718,-2.938 -6.622,-6.656 -8.713,-11.147 -2.091,-4.491 -3.138,-9.84 -3.138,-16.033 0,-6.813 1.199,-12.43 3.604,-16.84 2.399,-4.417 5.495,-7.939 9.295,-10.575 3.793,-2.632 8.129,-4.607 13.01,-5.923 4.878,-1.315 9.795,-2.358 14.752,-3.137 4.957,-0.772 9.835,-1.393 14.638,-1.857 4.801,-0.466 9.062,-1.164 12.779,-2.093 3.718,-0.929 6.658,-2.282 8.829,-4.065 2.165,-1.781 3.172,-4.375 3.02,-7.785 0,-3.56 -0.58,-6.389 -1.742,-8.479 -1.161,-2.09 -2.711,-3.719 -4.646,-4.88 -1.937,-1.161 -4.183,-1.936 -6.737,-2.325 -2.557,-0.382 -5.309,-0.58 -8.248,-0.58 -6.506,0 -11.617,1.395 -15.335,4.183 -3.716,2.788 -5.889,7.437 -6.506,13.94 h -32.991 c 0.462,-7.742 2.395,-14.173 5.807,-19.281 z m 65.169,46.583 c -2.09,0.696 -4.337,1.275 -6.736,1.741 -2.402,0.465 -4.918,0.853 -7.551,1.161 -2.635,0.313 -5.268,0.698 -7.899,1.163 -2.48,0.461 -4.919,1.086 -7.317,1.857 -2.404,0.779 -4.495,1.822 -6.274,3.138 -1.784,1.317 -3.216,2.985 -4.3,4.994 -1.085,2.014 -1.626,4.571 -1.626,7.668 0,2.94 0.541,5.422 1.626,7.431 1.084,2.017 2.558,3.604 4.416,4.765 1.858,1.161 4.025,1.976 6.507,2.438 2.475,0.466 5.031,0.698 7.665,0.698 6.505,0 11.537,-1.082 15.103,-3.253 3.561,-2.166 6.192,-4.762 7.899,-7.785 1.702,-3.019 2.749,-6.072 3.137,-9.174 0.384,-3.097 0.58,-5.576 0.58,-7.434 V 172.93 c -1.396,1.243 -3.138,2.21 -5.23,2.908 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5269"
|
|
||||||
d="m 463.825,111.595 v 22.072 h -24.161 v 59.479 c 0,5.573 0.928,9.292 2.788,11.149 1.856,1.859 5.576,2.788 11.152,2.788 1.859,0 3.638,-0.076 5.343,-0.232 1.703,-0.152 3.33,-0.388 4.878,-0.696 v 25.557 c -2.788,0.465 -5.887,0.773 -9.293,0.931 -3.407,0.149 -6.737,0.23 -9.99,0.23 -5.111,0 -9.953,-0.35 -14.521,-1.048 -4.571,-0.695 -8.597,-2.047 -12.081,-4.063 -3.486,-2.011 -6.236,-4.88 -8.248,-8.597 -2.016,-3.714 -3.021,-8.595 -3.021,-14.639 v -70.859 h -19.98 v -22.072 h 19.98 V 75.583 h 32.992 v 36.012 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5271"
|
|
||||||
d="M 510.988,111.595 V 133.9 h 0.465 c 1.546,-3.72 3.636,-7.163 6.272,-10.341 2.634,-3.172 5.652,-5.885 9.06,-8.131 3.405,-2.242 7.047,-3.985 10.923,-5.228 3.868,-1.237 7.898,-1.859 12.081,-1.859 2.168,0 4.566,0.39 7.202,1.163 v 30.67 c -1.551,-0.312 -3.41,-0.584 -5.576,-0.814 -2.17,-0.233 -4.26,-0.35 -6.274,-0.35 -6.041,0 -11.152,1.01 -15.332,3.021 -4.182,2.014 -7.55,4.761 -10.107,8.247 -2.555,3.487 -4.379,7.55 -5.462,12.198 -1.083,4.645 -1.625,9.682 -1.625,15.102 v 54.133 H 479.624 V 111.595 Z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5273"
|
|
||||||
d="M 570.93,93.007 V 65.824 h 32.994 v 27.183 z m 32.994,18.588 V 231.712 H 570.93 V 111.595 Z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5275"
|
|
||||||
d="m 621.115,111.595 h 37.637 l 21.144,31.365 20.911,-31.365 h 36.476 l -39.496,56.226 44.377,63.892 h -37.64 l -25.093,-37.87 -25.094,37.87 h -36.938 l 43.213,-63.193 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
<path
|
|
||||||
id="path5277"
|
|
||||||
d="M 782.443,331.097 V 9.711 h -23.13 v -7.71 h 32.008 v 336.807 h -32.008 v -7.711 z"
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 22 KiB |
|
@ -11,12 +11,9 @@ finish-args:
|
||||||
- --device=dri
|
- --device=dri
|
||||||
- --filesystem=xdg-download
|
- --filesystem=xdg-download
|
||||||
- --talk-name=org.freedesktop.Notifications
|
- --talk-name=org.freedesktop.Notifications
|
||||||
- --talk-name=org.kde.StatusNotifierWatcher
|
|
||||||
modules:
|
modules:
|
||||||
- name: spectral
|
- name: spectral
|
||||||
buildsystem: qmake
|
buildsystem: qmake
|
||||||
config-opts:
|
|
||||||
- "BUNDLE_FONT=true"
|
|
||||||
sources:
|
sources:
|
||||||
- type: dir
|
- type: dir
|
||||||
path: ../
|
path: ../
|
||||||
|
|
6
font.qrc
|
@ -1,6 +0,0 @@
|
||||||
<RCC>
|
|
||||||
<qresource prefix="/">
|
|
||||||
<file>assets/font/roboto.ttf</file>
|
|
||||||
<file>assets/font/twemoji.ttf</file>
|
|
||||||
</qresource>
|
|
||||||
</RCC>
|
|
|
@ -6,8 +6,8 @@ MouseArea {
|
||||||
signal primaryClicked()
|
signal primaryClicked()
|
||||||
signal secondaryClicked()
|
signal secondaryClicked()
|
||||||
|
|
||||||
acceptedButtons: MSettings.pressAndHold ? Qt.LeftButton : (Qt.LeftButton | Qt.RightButton)
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
|
||||||
onClicked: mouse.button == Qt.RightButton ? secondaryClicked() : primaryClicked()
|
onClicked: mouse.button == Qt.RightButton ? secondaryClicked() : primaryClicked()
|
||||||
onPressAndHold: MSettings.pressAndHold ? secondaryClicked() : {}
|
onPressAndHold: secondaryClicked()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,62 @@
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
|
import QtQuick.Controls.Material 2.3
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
|
id: textField
|
||||||
|
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
|
|
||||||
|
topPadding: 8
|
||||||
|
bottomPadding: 8
|
||||||
|
|
||||||
|
background: Item {
|
||||||
|
Label {
|
||||||
|
id: floatingPlaceholder
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.topMargin: textField.topPadding
|
||||||
|
anchors.leftMargin: textField.leftPadding
|
||||||
|
transformOrigin: Item.TopLeft
|
||||||
|
visible: false
|
||||||
|
color: Material.accent
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "shown"
|
||||||
|
when: textField.text.length !== 0
|
||||||
|
PropertyChanges { target: floatingPlaceholder; scale: 0.8 }
|
||||||
|
PropertyChanges { target: floatingPlaceholder; anchors.topMargin: -floatingPlaceholder.height * 0.4 }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
transitions: [
|
||||||
|
Transition {
|
||||||
|
to: "shown"
|
||||||
|
SequentialAnimation {
|
||||||
|
PropertyAction { target: floatingPlaceholder; property: "text"; value: textField.placeholderText }
|
||||||
|
PropertyAction { target: floatingPlaceholder; property: "visible"; value: true }
|
||||||
|
PropertyAction { target: textField; property: "placeholderTextColor"; value: "transparent" }
|
||||||
|
ParallelAnimation {
|
||||||
|
NumberAnimation { target: floatingPlaceholder; property: "scale"; duration: 250; easing.type: Easing.InOutQuad }
|
||||||
|
NumberAnimation { target: floatingPlaceholder; property: "anchors.topMargin"; duration: 250; easing.type: Easing.InOutQuad }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Transition {
|
||||||
|
from: "shown"
|
||||||
|
SequentialAnimation {
|
||||||
|
ParallelAnimation {
|
||||||
|
NumberAnimation { target: floatingPlaceholder; property: "scale"; duration: 250; easing.type: Easing.InOutQuad }
|
||||||
|
NumberAnimation { target: floatingPlaceholder; property: "anchors.topMargin"; duration: 250; easing.type: Easing.InOutQuad }
|
||||||
|
}
|
||||||
|
PropertyAction { target: textField; property: "placeholderTextColor"; value: "grey" }
|
||||||
|
PropertyAction { target: floatingPlaceholder; property: "visible"; value: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,8 +36,8 @@ Item {
|
||||||
visible: !realSource || image.status != Image.Ready
|
visible: !realSource || image.status != Image.Ready
|
||||||
|
|
||||||
radius: height / 2
|
radius: height / 2
|
||||||
|
|
||||||
color: stringToColor(hint)
|
color: stringToColor(hint)
|
||||||
|
antialiasing: true
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
|
ApplicationWindow {
|
||||||
|
property string eventId
|
||||||
|
property url localPath
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
flags: Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
|
||||||
|
visible: true
|
||||||
|
visibility: Qt.WindowFullScreen
|
||||||
|
|
||||||
|
title: "Image View - " + eventId
|
||||||
|
|
||||||
|
color: "#BB000000"
|
||||||
|
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Escape"
|
||||||
|
onActivated: root.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedImage {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
width: Math.min(sourceSize.width, root.width)
|
||||||
|
height: Math.min(sourceSize.height, root.height)
|
||||||
|
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
cache: false
|
||||||
|
|
||||||
|
source: localPath
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemDelegate {
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
width: 64
|
||||||
|
height: 64
|
||||||
|
|
||||||
|
contentItem: MaterialIcon {
|
||||||
|
icon: "\ue5cd"
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: root.destroy()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,535 +0,0 @@
|
||||||
/****************************************************************************
|
|
||||||
**
|
|
||||||
** Copyright (C) 2016 The Qt Company Ltd.
|
|
||||||
** Contact: https://www.qt.io/licensing/
|
|
||||||
**
|
|
||||||
** This file is part of the Qt Quick Controls module of the Qt Toolkit.
|
|
||||||
**
|
|
||||||
** $QT_BEGIN_LICENSE:LGPL$
|
|
||||||
** Commercial License Usage
|
|
||||||
** Licensees holding valid commercial Qt licenses may use this file in
|
|
||||||
** accordance with the commercial license agreement provided with the
|
|
||||||
** Software or, alternatively, in accordance with the terms contained in
|
|
||||||
** a written agreement between you and The Qt Company. For licensing terms
|
|
||||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
||||||
** information use the contact form at https://www.qt.io/contact-us.
|
|
||||||
**
|
|
||||||
** GNU Lesser General Public License Usage
|
|
||||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
||||||
** General Public License version 3 as published by the Free Software
|
|
||||||
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
|
||||||
** packaging of this file. Please review the following information to
|
|
||||||
** ensure the GNU Lesser General Public License version 3 requirements
|
|
||||||
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
|
||||||
**
|
|
||||||
** GNU General Public License Usage
|
|
||||||
** Alternatively, this file may be used under the terms of the GNU
|
|
||||||
** General Public License version 2.0 or (at your option) the GNU General
|
|
||||||
** Public license version 3 or any later version approved by the KDE Free
|
|
||||||
** Qt Foundation. The licenses are as published by the Free Software
|
|
||||||
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
|
||||||
** included in the packaging of this file. Please review the following
|
|
||||||
** information to ensure the GNU General Public License requirements will
|
|
||||||
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
|
||||||
** https://www.gnu.org/licenses/gpl-3.0.html.
|
|
||||||
**
|
|
||||||
** $QT_END_LICENSE$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
import QtQuick.Window 2.1
|
|
||||||
import Spectral.Setting 0.1
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property int orientation: Qt.Horizontal
|
|
||||||
|
|
||||||
/*!
|
|
||||||
This property holds the delegate that will be instantiated between each
|
|
||||||
child item. Inside the delegate the following properties are available:
|
|
||||||
|
|
||||||
\table
|
|
||||||
\row \li readonly property bool styleData.index \li Specifies the index of the splitter handle. The handle
|
|
||||||
between the first and the second item will get index 0,
|
|
||||||
the next handle index 1 etc.
|
|
||||||
\row \li readonly property bool styleData.hovered \li The handle is being hovered.
|
|
||||||
\row \li readonly property bool styleData.pressed \li The handle is being pressed.
|
|
||||||
\row \li readonly property bool styleData.resizing \li The handle is being dragged.
|
|
||||||
\endtable
|
|
||||||
|
|
||||||
*/
|
|
||||||
property Component handleDelegate: Rectangle {
|
|
||||||
width: 1
|
|
||||||
height: 1
|
|
||||||
color: MSettings.darkTheme ? "#424242" : "#E1E1E1"
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
This propery is \c true when the user is resizing any of the items by
|
|
||||||
dragging on the splitter handles.
|
|
||||||
*/
|
|
||||||
property bool resizing: false
|
|
||||||
|
|
||||||
/*! \internal */
|
|
||||||
default property alias __contents: contents.data
|
|
||||||
/*! \internal */
|
|
||||||
property alias __items: splitterItems.children
|
|
||||||
/*! \internal */
|
|
||||||
property alias __handles: splitterHandles.children
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
Component.onCompleted: d.init()
|
|
||||||
onWidthChanged: d.updateLayout()
|
|
||||||
onHeightChanged: d.updateLayout()
|
|
||||||
onOrientationChanged: d.changeOrientation()
|
|
||||||
|
|
||||||
/*! \qmlmethod void SplitView::addItem(Item item)
|
|
||||||
Add an item to the end of the view.
|
|
||||||
\since QtQuick.Controls 1.12 */
|
|
||||||
function addItem(item) {
|
|
||||||
d.updateLayoutGuard = true
|
|
||||||
d.addItem_impl(item)
|
|
||||||
d.calculateImplicitSize()
|
|
||||||
d.updateLayoutGuard = false
|
|
||||||
d.updateFillIndex()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! \qmlmethod void SplitView::removeItem(Item item)
|
|
||||||
Remove \a item from the view.
|
|
||||||
\since QtQuick.Controls 1.4 */
|
|
||||||
function removeItem(item) {
|
|
||||||
d.updateLayoutGuard = true
|
|
||||||
var result = d.removeItem_impl(item)
|
|
||||||
if (result !== null) {
|
|
||||||
d.calculateImplicitSize()
|
|
||||||
d.updateLayoutGuard = false
|
|
||||||
d.updateFillIndex()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
d.updateLayoutGuard = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemPalette { id: pal }
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: d
|
|
||||||
|
|
||||||
readonly property string leftMargin: horizontal ? "leftMargin" : "topMargin"
|
|
||||||
readonly property string topMargin: horizontal ? "topMargin" : "leftMargin"
|
|
||||||
readonly property string rightMargin: horizontal ? "rightMargin" : "bottomMargin"
|
|
||||||
|
|
||||||
property bool horizontal: orientation == Qt.Horizontal
|
|
||||||
readonly property string minimum: horizontal ? "minimumWidth" : "minimumHeight"
|
|
||||||
readonly property string maximum: horizontal ? "maximumWidth" : "maximumHeight"
|
|
||||||
readonly property string otherMinimum: horizontal ? "minimumHeight" : "minimumWidth"
|
|
||||||
readonly property string otherMaximum: horizontal ? "maximumHeight" : "maximumWidth"
|
|
||||||
readonly property string offset: horizontal ? "x" : "y"
|
|
||||||
readonly property string otherOffset: horizontal ? "y" : "x"
|
|
||||||
readonly property string size: horizontal ? "width" : "height"
|
|
||||||
readonly property string otherSize: horizontal ? "height" : "width"
|
|
||||||
readonly property string implicitSize: horizontal ? "implicitWidth" : "implicitHeight"
|
|
||||||
readonly property string implicitOtherSize: horizontal ? "implicitHeight" : "implicitWidth"
|
|
||||||
|
|
||||||
property int fillIndex: -1
|
|
||||||
property bool updateLayoutGuard: true
|
|
||||||
|
|
||||||
function extraMarginSize(item, other) {
|
|
||||||
if (typeof(other) === 'undefined')
|
|
||||||
other = false;
|
|
||||||
if (other === horizontal)
|
|
||||||
// vertical
|
|
||||||
return item.Layout.topMargin + item.Layout.bottomMargin
|
|
||||||
return item.Layout.leftMargin + item.Layout.rightMargin
|
|
||||||
}
|
|
||||||
|
|
||||||
function addItem_impl(item)
|
|
||||||
{
|
|
||||||
// temporarily set fillIndex to new item
|
|
||||||
fillIndex = __items.length
|
|
||||||
if (splitterItems.children.length > 0)
|
|
||||||
handleLoader.createObject(splitterHandles, {"__handleIndex":splitterItems.children.length - 1})
|
|
||||||
|
|
||||||
item.parent = splitterItems
|
|
||||||
d.initItemConnections(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
function initItemConnections(item)
|
|
||||||
{
|
|
||||||
// should match disconnections in terminateItemConnections
|
|
||||||
item.widthChanged.connect(d.updateLayout)
|
|
||||||
item.heightChanged.connect(d.updateLayout)
|
|
||||||
item.Layout.maximumWidthChanged.connect(d.updateLayout)
|
|
||||||
item.Layout.minimumWidthChanged.connect(d.updateLayout)
|
|
||||||
item.Layout.maximumHeightChanged.connect(d.updateLayout)
|
|
||||||
item.Layout.minimumHeightChanged.connect(d.updateLayout)
|
|
||||||
item.Layout.leftMarginChanged.connect(d.updateLayout)
|
|
||||||
item.Layout.topMarginChanged.connect(d.updateLayout)
|
|
||||||
item.Layout.rightMarginChanged.connect(d.updateLayout)
|
|
||||||
item.Layout.bottomMarginChanged.connect(d.updateLayout)
|
|
||||||
item.visibleChanged.connect(d.updateFillIndex)
|
|
||||||
item.Layout.fillWidthChanged.connect(d.updateFillIndex)
|
|
||||||
item.Layout.fillHeightChanged.connect(d.updateFillIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
function terminateItemConnections(item)
|
|
||||||
{
|
|
||||||
// should match connections in initItemConnections
|
|
||||||
item.widthChanged.disconnect(d.updateLayout)
|
|
||||||
item.heightChanged.disconnect(d.updateLayout)
|
|
||||||
item.Layout.maximumWidthChanged.disconnect(d.updateLayout)
|
|
||||||
item.Layout.minimumWidthChanged.disconnect(d.updateLayout)
|
|
||||||
item.Layout.maximumHeightChanged.disconnect(d.updateLayout)
|
|
||||||
item.Layout.minimumHeightChanged.disconnect(d.updateLayout)
|
|
||||||
item.visibleChanged.disconnect(d.updateFillIndex)
|
|
||||||
item.Layout.fillWidthChanged.disconnect(d.updateFillIndex)
|
|
||||||
item.Layout.fillHeightChanged.disconnect(d.updateFillIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeItem_impl(item)
|
|
||||||
{
|
|
||||||
var pos = itemPos(item)
|
|
||||||
|
|
||||||
// Check pos range
|
|
||||||
if (pos < 0 || pos >= __items.length)
|
|
||||||
return null
|
|
||||||
|
|
||||||
// Temporary unset the fillIndex
|
|
||||||
fillIndex = __items.length - 1
|
|
||||||
|
|
||||||
// Remove the handle at the left/right of the item that
|
|
||||||
// is going to be removed
|
|
||||||
var handlePos = -1
|
|
||||||
var hasPrevious = pos > 0
|
|
||||||
var hasNext = (pos + 1) < __items.length
|
|
||||||
|
|
||||||
if (hasPrevious)
|
|
||||||
handlePos = pos-1
|
|
||||||
else if (hasNext)
|
|
||||||
handlePos = pos
|
|
||||||
if (handlePos >= 0) {
|
|
||||||
var handle = __handles[handlePos]
|
|
||||||
handle.visible = false
|
|
||||||
handle.parent = null
|
|
||||||
handle.destroy()
|
|
||||||
for (var i = handlePos; i < __handles.length; ++i)
|
|
||||||
__handles[i].__handleIndex = i
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the item.
|
|
||||||
// Disconnect the item to be removed
|
|
||||||
terminateItemConnections(item)
|
|
||||||
item.parent = null
|
|
||||||
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
|
|
||||||
function itemPos(item)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < __items.length; ++i)
|
|
||||||
if (item === __items[i])
|
|
||||||
return i
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
function init()
|
|
||||||
{
|
|
||||||
for (var i=0; i<__contents.length; ++i) {
|
|
||||||
var item = __contents[i];
|
|
||||||
if (!item.hasOwnProperty("x"))
|
|
||||||
continue
|
|
||||||
addItem_impl(item)
|
|
||||||
i-- // item was removed from list
|
|
||||||
}
|
|
||||||
|
|
||||||
d.calculateImplicitSize()
|
|
||||||
d.updateLayoutGuard = false
|
|
||||||
d.updateFillIndex()
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateFillIndex()
|
|
||||||
{
|
|
||||||
if (lastItem.visible !== root.visible)
|
|
||||||
return
|
|
||||||
var policy = (root.orientation === Qt.Horizontal) ? "fillWidth" : "fillHeight"
|
|
||||||
for (var i=0; i<__items.length-1; ++i) {
|
|
||||||
if (__items[i].Layout[policy] === true)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
d.fillIndex = i
|
|
||||||
d.updateLayout()
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeOrientation()
|
|
||||||
{
|
|
||||||
if (__items.length == 0)
|
|
||||||
return;
|
|
||||||
d.updateLayoutGuard = true
|
|
||||||
|
|
||||||
// Swap width/height for items and handles:
|
|
||||||
for (var i=0; i<__items.length; ++i) {
|
|
||||||
var item = __items[i]
|
|
||||||
var tmp = item.x
|
|
||||||
item.x = item.y
|
|
||||||
item.y = tmp
|
|
||||||
tmp = item.width
|
|
||||||
item.width = item.height
|
|
||||||
item.height = tmp
|
|
||||||
|
|
||||||
var handle = __handles[i]
|
|
||||||
if (handle) {
|
|
||||||
tmp = handle.x
|
|
||||||
handle.x = handle.y
|
|
||||||
handle.y = handle.x
|
|
||||||
tmp = handle.width
|
|
||||||
handle.width = handle.height
|
|
||||||
handle.height = tmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change d.horizontal explicit, since the binding will change too late:
|
|
||||||
d.horizontal = orientation == Qt.Horizontal
|
|
||||||
d.updateLayoutGuard = false
|
|
||||||
d.updateFillIndex()
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateImplicitSize()
|
|
||||||
{
|
|
||||||
var implicitSize = 0
|
|
||||||
var implicitOtherSize = 0
|
|
||||||
|
|
||||||
for (var i=0; i<__items.length; ++i) {
|
|
||||||
var item = __items[i];
|
|
||||||
implicitSize += clampedMinMax(item[d.size], item.Layout[minimum], item.Layout[maximum]) + extraMarginSize(item)
|
|
||||||
var os = clampedMinMax(item[otherSize], item.Layout[otherMinimum], item.Layout[otherMaximum]) + extraMarginSize(item, true)
|
|
||||||
implicitOtherSize = Math.max(implicitOtherSize, os)
|
|
||||||
|
|
||||||
var handle = __handles[i]
|
|
||||||
if (handle)
|
|
||||||
implicitSize += handle[d.size] //### Can handles have margins??
|
|
||||||
}
|
|
||||||
|
|
||||||
root[d.implicitSize] = implicitSize
|
|
||||||
root[d.implicitOtherSize] = implicitOtherSize
|
|
||||||
}
|
|
||||||
|
|
||||||
function clampedMinMax(value, minimum, maximum)
|
|
||||||
{
|
|
||||||
if (value < minimum)
|
|
||||||
value = minimum
|
|
||||||
if (value > maximum)
|
|
||||||
value = maximum
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
function accumulatedSize(firstIndex, lastIndex, includeFillItemMinimum)
|
|
||||||
{
|
|
||||||
// Go through items and handles, and
|
|
||||||
// calculate their accummulated width.
|
|
||||||
var w = 0
|
|
||||||
for (var i=firstIndex; i<lastIndex; ++i) {
|
|
||||||
|
|
||||||
var item = __items[i]
|
|
||||||
if (item.visible || i == d.fillIndex) {
|
|
||||||
if (i !== d.fillIndex)
|
|
||||||
w += item[d.size] + extraMarginSize(item)
|
|
||||||
else if (includeFillItemMinimum && item.Layout[minimum] !== undefined)
|
|
||||||
w += item.Layout[minimum] + extraMarginSize(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
var handle = __handles[i]
|
|
||||||
if (handle && handle.visible)
|
|
||||||
w += handle[d.size]
|
|
||||||
}
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLayout()
|
|
||||||
{
|
|
||||||
// This function will reposition both handles and
|
|
||||||
// items according to the their width/height:
|
|
||||||
if (__items.length === 0)
|
|
||||||
return;
|
|
||||||
if (!lastItem.visible)
|
|
||||||
return;
|
|
||||||
if (d.updateLayoutGuard === true)
|
|
||||||
return
|
|
||||||
d.updateLayoutGuard = true
|
|
||||||
|
|
||||||
// Ensure all items within their min/max:
|
|
||||||
for (var i=0; i<__items.length; ++i) {
|
|
||||||
if (i !== d.fillIndex) {
|
|
||||||
var item = __items[i];
|
|
||||||
var clampedSize = clampedMinMax(item[d.size], item.Layout[d.minimum], item.Layout[d.maximum])
|
|
||||||
if (clampedSize != item[d.size])
|
|
||||||
item[d.size] = clampedSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set size of fillItem to remaining available space.
|
|
||||||
// Special case: If SplitView size is zero, we leave fillItem with the size
|
|
||||||
// it already got, and assume that SplitView ends up with implicit size as size:
|
|
||||||
if (root[d.size] != 0) {
|
|
||||||
var fillItem = __items[fillIndex]
|
|
||||||
var superfluous = root[d.size] - d.accumulatedSize(0, __items.length, false)
|
|
||||||
fillItem[d.size] = clampedMinMax(superfluous - extraMarginSize(fillItem), fillItem.Layout[minimum], fillItem.Layout[maximum]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position items and handles according to their width:
|
|
||||||
var lastVisibleItem, lastVisibleHandle, handle
|
|
||||||
var pos = 0;
|
|
||||||
for (i=0; i<__items.length; ++i) {
|
|
||||||
// Position item to the right of the previous visible handle:
|
|
||||||
item = __items[i];
|
|
||||||
if (item.visible || i == d.fillIndex) {
|
|
||||||
pos += item.Layout[leftMargin]
|
|
||||||
item[d.offset] = pos
|
|
||||||
item[d.otherOffset] = item.Layout[topMargin]
|
|
||||||
item[d.otherSize] = clampedMinMax(root[otherSize], item.Layout[otherMinimum], item.Layout[otherMaximum]) - extraMarginSize(item, true)
|
|
||||||
lastVisibleItem = item
|
|
||||||
pos += Math.max(0, item[d.size]) + item.Layout[rightMargin]
|
|
||||||
}
|
|
||||||
|
|
||||||
handle = __handles[i]
|
|
||||||
if (handle && handle.visible) {
|
|
||||||
handle[d.offset] = pos
|
|
||||||
handle[d.otherOffset] = 0 //### can handles have margins?
|
|
||||||
handle[d.otherSize] = root[d.otherSize]
|
|
||||||
lastVisibleHandle = handle
|
|
||||||
pos += handle[d.size]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d.updateLayoutGuard = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: handleLoader
|
|
||||||
Loader {
|
|
||||||
id: itemHandle
|
|
||||||
|
|
||||||
property int __handleIndex: -1
|
|
||||||
property QtObject styleData: QtObject {
|
|
||||||
readonly property int index: __handleIndex
|
|
||||||
readonly property alias hovered: mouseArea.containsMouse
|
|
||||||
readonly property alias pressed: mouseArea.pressed
|
|
||||||
readonly property bool resizing: mouseArea.drag.active
|
|
||||||
onResizingChanged: root.resizing = resizing
|
|
||||||
}
|
|
||||||
property bool resizeLeftItem: (d.fillIndex > __handleIndex)
|
|
||||||
visible: __items[__handleIndex + (resizeLeftItem ? 0 : 1)].visible
|
|
||||||
sourceComponent: handleDelegate
|
|
||||||
onWidthChanged: d.updateLayout()
|
|
||||||
onHeightChanged: d.updateLayout()
|
|
||||||
onXChanged: moveHandle()
|
|
||||||
onYChanged: moveHandle()
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: mouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
property real defaultMargin: Screen.pixelDensity * 2
|
|
||||||
anchors.leftMargin: (parent.width <= 1) ? -defaultMargin : 0
|
|
||||||
anchors.rightMargin: (parent.width <= 1) ? -defaultMargin : 0
|
|
||||||
anchors.topMargin: (parent.height <= 1) ? -defaultMargin : 0
|
|
||||||
anchors.bottomMargin: (parent.height <= 1) ? -defaultMargin : 0
|
|
||||||
hoverEnabled: true
|
|
||||||
drag.threshold: 0
|
|
||||||
drag.target: parent
|
|
||||||
drag.axis: root.orientation === Qt.Horizontal ? Drag.XAxis : Drag.YAxis
|
|
||||||
cursorShape: root.orientation === Qt.Horizontal ? Qt.SplitHCursor : Qt.SplitVCursor
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveHandle() {
|
|
||||||
// Moving the handle means resizing an item. Which one,
|
|
||||||
// left or right, depends on where the fillItem is.
|
|
||||||
// 'updateLayout' will be overridden in case new width violates max/min.
|
|
||||||
// 'updateLayout' will be triggered when an item changes width.
|
|
||||||
if (d.updateLayoutGuard)
|
|
||||||
return
|
|
||||||
|
|
||||||
var leftHandle, leftItem, rightItem, rightHandle
|
|
||||||
var leftEdge, rightEdge, newWidth, leftStopX, rightStopX
|
|
||||||
var i
|
|
||||||
|
|
||||||
if (resizeLeftItem) {
|
|
||||||
// Ensure that the handle is not crossing other handles. So
|
|
||||||
// find the first visible handle to the left to determine the left edge:
|
|
||||||
leftEdge = 0
|
|
||||||
for (i=__handleIndex-1; i>=0; --i) {
|
|
||||||
leftHandle = __handles[i]
|
|
||||||
if (leftHandle.visible) {
|
|
||||||
leftEdge = leftHandle[d.offset] + leftHandle[d.size]
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure: leftStopX >= itemHandle[d.offset] >= rightStopX
|
|
||||||
var min = d.accumulatedSize(__handleIndex+1, __items.length, true)
|
|
||||||
rightStopX = root[d.size] - min - itemHandle[d.size]
|
|
||||||
leftStopX = Math.max(leftEdge, itemHandle[d.offset])
|
|
||||||
itemHandle[d.offset] = Math.min(rightStopX, Math.max(leftStopX, itemHandle[d.offset]))
|
|
||||||
|
|
||||||
newWidth = itemHandle[d.offset] - leftEdge
|
|
||||||
leftItem = __items[__handleIndex]
|
|
||||||
// The next line will trigger 'updateLayout':
|
|
||||||
leftItem[d.size] = newWidth
|
|
||||||
} else {
|
|
||||||
// Resize item to the right.
|
|
||||||
// Ensure that the handle is not crossing other handles. So
|
|
||||||
// find the first visible handle to the right to determine the right edge:
|
|
||||||
rightEdge = root[d.size]
|
|
||||||
for (i=__handleIndex+1; i<__handles.length; ++i) {
|
|
||||||
rightHandle = __handles[i]
|
|
||||||
if (rightHandle.visible) {
|
|
||||||
rightEdge = rightHandle[d.offset]
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure: leftStopX <= itemHandle[d.offset] <= rightStopX
|
|
||||||
min = d.accumulatedSize(0, __handleIndex+1, true)
|
|
||||||
leftStopX = min - itemHandle[d.size]
|
|
||||||
rightStopX = Math.min((rightEdge - itemHandle[d.size]), itemHandle[d.offset])
|
|
||||||
itemHandle[d.offset] = Math.max(leftStopX, Math.min(itemHandle[d.offset], rightStopX))
|
|
||||||
|
|
||||||
newWidth = rightEdge - (itemHandle[d.offset] + itemHandle[d.size])
|
|
||||||
rightItem = __items[__handleIndex+1]
|
|
||||||
// The next line will trigger 'updateLayout':
|
|
||||||
rightItem[d.size] = newWidth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: contents
|
|
||||||
visible: false
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
Item {
|
|
||||||
id: splitterItems
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
Item {
|
|
||||||
id: splitterHandles
|
|
||||||
anchors.fill: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: lastItem
|
|
||||||
onVisibleChanged: d.updateFillIndex()
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onDestruction: {
|
|
||||||
for (var i=0; i<splitterItems.children.length; ++i) {
|
|
||||||
var item = splitterItems.children[i];
|
|
||||||
d.terminateItemConnections(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,7 +9,10 @@ import Spectral 0.1
|
||||||
import Spectral.Setting 0.1
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
import Spectral.Component 2.0
|
||||||
|
import Spectral.Dialog 2.0
|
||||||
|
import Spectral.Menu.Timeline 2.0
|
||||||
import Spectral.Font 0.1
|
import Spectral.Font 0.1
|
||||||
|
import Spectral.Effect 2.0
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote" || aboveEventType === "other")
|
readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote" || aboveEventType === "other")
|
||||||
|
@ -52,6 +55,20 @@ ColumnLayout {
|
||||||
visible: avatarVisible
|
visible: avatarVisible
|
||||||
hint: author.displayName
|
hint: author.displayName
|
||||||
source: author.avatarMediaId
|
source: author.avatarMediaId
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: userDetailDialog
|
||||||
|
|
||||||
|
UserDetailDialog {}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
circular: true
|
||||||
|
|
||||||
|
onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author}).open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
@ -94,7 +111,7 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: progressInfo.active ? (progressInfo.progress + "/" + progressInfo.total) : content.info.size
|
text: progressInfo.active ? (progressInfo.progress + "/" + progressInfo.total) : content.info ? content.info.size : "Unknown"
|
||||||
color: MPalette.lighter
|
color: MPalette.lighter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,52 +126,59 @@ ColumnLayout {
|
||||||
|
|
||||||
id: messageMouseArea
|
id: messageMouseArea
|
||||||
|
|
||||||
onSecondaryClicked: messageContextMenu.popup()
|
onSecondaryClicked: {
|
||||||
|
var contextMenu = fileDelegateContextMenu.createObject(ApplicationWindow.overlay)
|
||||||
Menu {
|
contextMenu.viewSource.connect(function() {
|
||||||
id: messageContextMenu
|
messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
|
||||||
|
})
|
||||||
MenuItem {
|
contextMenu.downloadAndOpen.connect(downloadAndOpen)
|
||||||
text: "View Source"
|
contextMenu.saveFileAs.connect(saveFileAs)
|
||||||
|
contextMenu.reply.connect(function() {
|
||||||
onTriggered: {
|
|
||||||
sourceDialog.sourceText = toolTip
|
|
||||||
sourceDialog.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Open Externally"
|
|
||||||
|
|
||||||
onTriggered: downloadAndOpen()
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Save As"
|
|
||||||
|
|
||||||
onTriggered: saveFileAs()
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Reply"
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
roomPanelInput.replyUser = author
|
roomPanelInput.replyUser = author
|
||||||
roomPanelInput.replyEventID = eventId
|
roomPanelInput.replyEventID = eventId
|
||||||
roomPanelInput.replyContent = message
|
roomPanelInput.replyContent = message
|
||||||
roomPanelInput.isReply = true
|
roomPanelInput.isReply = true
|
||||||
roomPanelInput.focus()
|
roomPanelInput.focus()
|
||||||
|
})
|
||||||
|
contextMenu.redact.connect(function() {
|
||||||
|
currentRoom.redactEvent(eventId)
|
||||||
|
})
|
||||||
|
contextMenu.popup()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Redact"
|
|
||||||
|
|
||||||
onTriggered: currentRoom.redactEvent(eventId)
|
Component {
|
||||||
|
id: messageSourceDialog
|
||||||
|
|
||||||
|
MessageSourceDialog {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: openFileDialog
|
||||||
|
|
||||||
|
OpenFileDialog {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: fileDelegateContextMenu
|
||||||
|
|
||||||
|
FileDelegateContextMenu {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveFileAs() { currentRoom.saveFileAs(eventId) }
|
function saveFileAs() {
|
||||||
|
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay, {"selectFolder": true})
|
||||||
|
|
||||||
|
fileDialog.chosen.connect(function(path) {
|
||||||
|
if (!path) return
|
||||||
|
|
||||||
|
currentRoom.downloadFile(eventId, path + "/" + (content.filename || content.body))
|
||||||
|
})
|
||||||
|
|
||||||
|
fileDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
function downloadAndOpen()
|
function downloadAndOpen()
|
||||||
{
|
{
|
||||||
|
@ -162,7 +186,7 @@ ColumnLayout {
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
openOnFinished = true
|
openOnFinished = true
|
||||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_") + (message || ".tmp"))
|
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + (message || ".tmp"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,9 @@ import Spectral 0.1
|
||||||
import Spectral.Setting 0.1
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
import Spectral.Component 2.0
|
||||||
|
import Spectral.Dialog 2.0
|
||||||
|
import Spectral.Menu.Timeline 2.0
|
||||||
|
import Spectral.Effect 2.0
|
||||||
import Spectral.Font 0.1
|
import Spectral.Font 0.1
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
@ -16,13 +19,17 @@ ColumnLayout {
|
||||||
readonly property bool sentByMe: author === currentRoom.localUser
|
readonly property bool sentByMe: author === currentRoom.localUser
|
||||||
|
|
||||||
property bool openOnFinished: false
|
property bool openOnFinished: false
|
||||||
|
property bool showOnFinished: false
|
||||||
readonly property bool downloaded: progressInfo && progressInfo.completed
|
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||||
|
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
onDownloadedChanged: if (downloaded && openOnFinished) openSavedFile()
|
onDownloadedChanged: {
|
||||||
|
if (downloaded && showOnFinished) showSavedFile()
|
||||||
|
if (downloaded && openOnFinished) openSavedFile()
|
||||||
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.leftMargin: 48
|
Layout.leftMargin: 48
|
||||||
|
@ -52,6 +59,20 @@ ColumnLayout {
|
||||||
visible: avatarVisible
|
visible: avatarVisible
|
||||||
hint: author.displayName
|
hint: author.displayName
|
||||||
source: author.avatarMediaId
|
source: author.avatarMediaId
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: userDetailDialog
|
||||||
|
|
||||||
|
UserDetailDialog {}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
circular: true
|
||||||
|
|
||||||
|
onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author}).open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
@ -69,16 +90,26 @@ ColumnLayout {
|
||||||
verticalAlignment: Label.AlignVCenter
|
verticalAlignment: Label.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
Layout.preferredWidth: 64
|
||||||
|
Layout.preferredHeight: 64
|
||||||
|
|
||||||
|
visible: img.status == Image.Loading
|
||||||
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
Layout.maximumWidth: messageListView.width - (!sentByMe ? 32 + messageRow.spacing : 0) - 48
|
Layout.maximumWidth: messageListView.width - (!sentByMe ? 32 + messageRow.spacing : 0) - 48
|
||||||
|
|
||||||
id: img
|
id: img
|
||||||
|
|
||||||
source: downloaded ? progressInfo.localPath : "image://mxc/" +
|
source: "image://mxc/" +
|
||||||
(content.info && content.info.thumbnail_info ?
|
(content.info && content.info.thumbnail_info ?
|
||||||
content.thumbnailMediaId : content.mediaId)
|
content.thumbnailMediaId : content.mediaId)
|
||||||
sourceSize.width: 200
|
|
||||||
sourceSize.height: 200
|
sourceSize.width: 720
|
||||||
|
sourceSize.height: 720
|
||||||
|
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
|
||||||
layer.enabled: true
|
layer.enabled: true
|
||||||
layer.effect: OpacityMask {
|
layer.effect: OpacityMask {
|
||||||
|
@ -94,61 +125,110 @@ ColumnLayout {
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
radius: 24
|
radius: 24
|
||||||
|
antialiasing: true
|
||||||
|
|
||||||
border.width: 2
|
border.width: 4
|
||||||
border.color: MPalette.banner
|
border.color: MPalette.banner
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoMouseArea {
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
visible: progressInfo.active && !downloaded
|
||||||
|
|
||||||
|
color: "#BB000000"
|
||||||
|
|
||||||
|
ProgressBar {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
width: parent.width * 0.8
|
||||||
|
|
||||||
|
from: 0
|
||||||
|
to: progressInfo.total
|
||||||
|
value: progressInfo.progress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
id: messageMouseArea
|
id: messageMouseArea
|
||||||
|
|
||||||
onSecondaryClicked: messageContextMenu.popup()
|
onPrimaryClicked: downloadAndShow()
|
||||||
|
|
||||||
Menu {
|
onSecondaryClicked: {
|
||||||
id: messageContextMenu
|
var contextMenu = imageDelegateContextMenu.createObject(ApplicationWindow.overlay)
|
||||||
|
contextMenu.viewSource.connect(function() {
|
||||||
MenuItem {
|
messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
|
||||||
text: "View Source"
|
})
|
||||||
|
contextMenu.downloadAndOpen.connect(downloadAndOpen)
|
||||||
onTriggered: {
|
contextMenu.saveFileAs.connect(saveFileAs)
|
||||||
sourceDialog.sourceText = toolTip
|
contextMenu.reply.connect(function() {
|
||||||
sourceDialog.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Open Externally"
|
|
||||||
|
|
||||||
onTriggered: downloadAndOpen()
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Save As"
|
|
||||||
|
|
||||||
onTriggered: saveFileAs()
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Reply"
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
roomPanelInput.replyUser = author
|
roomPanelInput.replyUser = author
|
||||||
roomPanelInput.replyEventID = eventId
|
roomPanelInput.replyEventID = eventId
|
||||||
roomPanelInput.replyContent = message
|
roomPanelInput.replyContent = message
|
||||||
roomPanelInput.isReply = true
|
roomPanelInput.isReply = true
|
||||||
roomPanelInput.focus()
|
roomPanelInput.focus()
|
||||||
|
})
|
||||||
|
contextMenu.redact.connect(function() {
|
||||||
|
currentRoom.redactEvent(eventId)
|
||||||
|
})
|
||||||
|
contextMenu.popup()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Redact"
|
|
||||||
|
|
||||||
onTriggered: currentRoom.redactEvent(eventId)
|
Component {
|
||||||
|
id: messageSourceDialog
|
||||||
|
|
||||||
|
MessageSourceDialog {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: openFileDialog
|
||||||
|
|
||||||
|
OpenFileDialog {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: imageDelegateContextMenu
|
||||||
|
|
||||||
|
FileDelegateContextMenu {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: fullScreenImage
|
||||||
|
|
||||||
|
FullScreenImage {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveFileAs() { currentRoom.saveFileAs(eventId) }
|
function saveFileAs() {
|
||||||
|
var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay, {"selectFolder": true})
|
||||||
|
|
||||||
|
fileDialog.chosen.connect(function(path) {
|
||||||
|
if (!path) return
|
||||||
|
|
||||||
|
currentRoom.downloadFile(eventId, path + "/" + (content.filename || content.body))
|
||||||
|
})
|
||||||
|
|
||||||
|
fileDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadAndShow()
|
||||||
|
{
|
||||||
|
if (downloaded) showSavedFile()
|
||||||
|
else
|
||||||
|
{
|
||||||
|
showOnFinished = true
|
||||||
|
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + (message || ".tmp"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSavedFile()
|
||||||
|
{
|
||||||
|
fullScreenImage.createObject(parent, {"eventId": eventId, "localPath": progressInfo.localPath}).show()
|
||||||
|
}
|
||||||
|
|
||||||
function downloadAndOpen()
|
function downloadAndOpen()
|
||||||
{
|
{
|
||||||
|
@ -156,7 +236,7 @@ ColumnLayout {
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
openOnFinished = true
|
openOnFinished = true
|
||||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_") + (message || ".tmp"))
|
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + (message || ".tmp"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,9 @@ import Spectral 0.1
|
||||||
import Spectral.Setting 0.1
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
import Spectral.Component 2.0
|
||||||
import Spectral.Font 0.1
|
import Spectral.Dialog 2.0
|
||||||
|
import Spectral.Menu.Timeline 2.0
|
||||||
|
import Spectral.Effect 2.0
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote" || aboveEventType === "other")
|
readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote" || aboveEventType === "other")
|
||||||
|
@ -48,6 +50,20 @@ ColumnLayout {
|
||||||
visible: avatarVisible
|
visible: avatarVisible
|
||||||
hint: author.displayName
|
hint: author.displayName
|
||||||
source: author.avatarMediaId
|
source: author.avatarMediaId
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: userDetailDialog
|
||||||
|
|
||||||
|
UserDetailDialog {}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
circular: true
|
||||||
|
|
||||||
|
onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author}).open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
@ -74,43 +90,42 @@ ColumnLayout {
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: sentByMe ? "#009DC2" : eventType === "notice" ? "#4285F4" : "#673AB7"
|
color: sentByMe ? "#009DC2" : eventType === "notice" ? "#4285F4" : "#673AB7"
|
||||||
radius: 18
|
radius: 18
|
||||||
|
antialiasing: true
|
||||||
|
|
||||||
AutoMouseArea {
|
AutoMouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
id: messageMouseArea
|
id: messageMouseArea
|
||||||
|
|
||||||
onSecondaryClicked: messageContextMenu.popup()
|
onSecondaryClicked: {
|
||||||
|
var contextMenu = messageDelegateContextMenu.createObject(ApplicationWindow.overlay)
|
||||||
Menu {
|
contextMenu.viewSource.connect(function() {
|
||||||
readonly property string selectedText: contentLabel.selectedText
|
messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
|
||||||
|
})
|
||||||
id: messageContextMenu
|
contextMenu.reply.connect(function() {
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: "View Source"
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
sourceDialog.sourceText = toolTip
|
|
||||||
sourceDialog.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Reply"
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
roomPanelInput.replyUser = author
|
roomPanelInput.replyUser = author
|
||||||
roomPanelInput.replyEventID = eventId
|
roomPanelInput.replyEventID = eventId
|
||||||
roomPanelInput.replyContent = messageContextMenu.selectedText || message
|
roomPanelInput.replyContent = contentLabel.selectedText || message
|
||||||
roomPanelInput.isReply = true
|
roomPanelInput.isReply = true
|
||||||
roomPanelInput.focus()
|
roomPanelInput.focus()
|
||||||
|
})
|
||||||
|
contextMenu.redact.connect(function() {
|
||||||
|
currentRoom.redactEvent(eventId)
|
||||||
|
})
|
||||||
|
contextMenu.popup()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Redact"
|
|
||||||
|
|
||||||
onTriggered: currentRoom.redactEvent(eventId)
|
|
||||||
|
Component {
|
||||||
|
id: messageDelegateContextMenu
|
||||||
|
|
||||||
|
MessageDelegateContextMenu {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: messageSourceDialog
|
||||||
|
|
||||||
|
MessageSourceDialog {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,19 +138,10 @@ ColumnLayout {
|
||||||
|
|
||||||
padding: 8
|
padding: 8
|
||||||
|
|
||||||
background: Item {
|
background: RippleEffect {
|
||||||
Rectangle {
|
|
||||||
anchors.leftMargin: 0
|
|
||||||
width: 2
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
onClicked: goToEvent(replyEventId)
|
onPrimaryClicked: goToEvent(replyEventId)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem: RowLayout {
|
contentItem: RowLayout {
|
||||||
|
@ -169,7 +175,7 @@ ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
color: "white"
|
color: "white"
|
||||||
text: replyDisplay || ""
|
text: "<style>a{color: white;} .user-pill{}</style>" + (replyDisplay || "")
|
||||||
|
|
||||||
wrapMode: Label.Wrap
|
wrapMode: Label.Wrap
|
||||||
textFormat: Label.RichText
|
textFormat: Label.RichText
|
||||||
|
@ -178,6 +184,14 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
|
||||||
|
visible: replyEventId || ""
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
|
||||||
TextEdit {
|
TextEdit {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
@ -187,7 +201,7 @@ ColumnLayout {
|
||||||
|
|
||||||
color: "white"
|
color: "white"
|
||||||
|
|
||||||
font.family: CommonFont.font.family
|
font.family: window.font.family
|
||||||
font.pixelSize: 14
|
font.pixelSize: 14
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
|
|
@ -23,5 +23,6 @@ Label {
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: MPalette.banner
|
color: MPalette.banner
|
||||||
radius: 4
|
radius: 4
|
||||||
|
antialiasing: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,5 +5,5 @@ SideNavButton 2.0 SideNavButton.qml
|
||||||
ScrollHelper 2.0 ScrollHelper.qml
|
ScrollHelper 2.0 ScrollHelper.qml
|
||||||
AutoListView 2.0 AutoListView.qml
|
AutoListView 2.0 AutoListView.qml
|
||||||
AutoTextField 2.0 AutoTextField.qml
|
AutoTextField 2.0 AutoTextField.qml
|
||||||
SplitView 2.0 SplitView.qml
|
|
||||||
Avatar 2.0 Avatar.qml
|
Avatar 2.0 Avatar.qml
|
||||||
|
FullScreenImage 2.0 FullScreenImage.qml
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
property var room
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 360
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: "Invitation Received"
|
||||||
|
modal: true
|
||||||
|
|
||||||
|
contentItem: Label {
|
||||||
|
text: "Accept this invitation?"
|
||||||
|
}
|
||||||
|
|
||||||
|
footer: DialogButtonBox {
|
||||||
|
Button {
|
||||||
|
text: "Accept"
|
||||||
|
flat: true
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
room.acceptInvitation()
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Reject"
|
||||||
|
flat: true
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
room.forget()
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Cancel"
|
||||||
|
flat: true
|
||||||
|
|
||||||
|
onClicked: close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,345 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
|
import Spectral.Component 2.0
|
||||||
|
import Spectral.Effect 2.0
|
||||||
|
|
||||||
|
import Spectral 0.1
|
||||||
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
width: 480
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
contentItem: Column {
|
||||||
|
id: detailColumn
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: AccountListModel{
|
||||||
|
controller: spectralController
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
width: detailColumn.width
|
||||||
|
height: 72
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 12
|
||||||
|
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
Avatar {
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
source: user.avatarMediaId
|
||||||
|
hint: user.displayName || "No Name"
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: user.displayName || "No Name"
|
||||||
|
color: MPalette.foreground
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.bold: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: connection === spectralController.connection ? "Active" : "Online"
|
||||||
|
color: MPalette.lighter
|
||||||
|
font.pixelSize: 13
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: contextMenu
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Logout"
|
||||||
|
|
||||||
|
onClicked: spectralController.logout(connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onPrimaryClicked: spectralController.connection = connection
|
||||||
|
onSecondaryClicked: contextMenu.popup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
MenuSeparator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
|
||||||
|
contentItem: MaterialIcon {
|
||||||
|
icon: "\ue145"
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: loginDialog.createObject(ApplicationWindow.overlay).open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
|
||||||
|
color: MPalette.foreground
|
||||||
|
icon: "\ue7ff"
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
color: MPalette.foreground
|
||||||
|
text: "Start a Chat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onPrimaryClicked: joinRoomDialog.createObject(ApplicationWindow.overlay).open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
|
||||||
|
color: MPalette.foreground
|
||||||
|
icon: "\ue7fc"
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
color: MPalette.foreground
|
||||||
|
text: "Create a Room"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onPrimaryClicked: createRoomDialog.createObject(ApplicationWindow.overlay).open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuSeparator {
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
|
||||||
|
color: MPalette.foreground
|
||||||
|
icon: "\ue3a9"
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
color: MPalette.foreground
|
||||||
|
text: "Night Mode"
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch {
|
||||||
|
id: darkThemeSwitch
|
||||||
|
|
||||||
|
checked: MSettings.darkTheme
|
||||||
|
onCheckedChanged: MSettings.darkTheme = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onPrimaryClicked: darkThemeSwitch.checked = !darkThemeSwitch.checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
|
||||||
|
color: MPalette.foreground
|
||||||
|
icon: "\ue5d2"
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
color: MPalette.foreground
|
||||||
|
text: "Enable System Tray"
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch {
|
||||||
|
id: trayIconSwitch
|
||||||
|
|
||||||
|
checked: MSettings.showTray
|
||||||
|
onCheckedChanged: MSettings.showTray = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onPrimaryClicked: trayIconSwitch.checked = !trayIconSwitch.checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
|
||||||
|
color: MPalette.foreground
|
||||||
|
icon: "\ue7f5"
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
color: MPalette.foreground
|
||||||
|
text: "Enable Notifications"
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch {
|
||||||
|
id: notificationsSwitch
|
||||||
|
|
||||||
|
checked: MSettings.showNotification
|
||||||
|
onCheckedChanged: MSettings.showNotification = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onPrimaryClicked: notificationsSwitch.checked = !notificationsSwitch.checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuSeparator {
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
|
||||||
|
color: MPalette.foreground
|
||||||
|
icon: "\ue167"
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
color: MPalette.foreground
|
||||||
|
text: "Font Family"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onPrimaryClicked: fontFamilyDialog.createObject(ApplicationWindow.overlay).open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
|
||||||
|
color: MPalette.foreground
|
||||||
|
icon: "\ue8aa"
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
color: MPalette.foreground
|
||||||
|
text: "Chat Background"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onPrimaryClicked: {
|
||||||
|
var fileDialog = chatBackgroundDialog.createObject(ApplicationWindow.overlay)
|
||||||
|
|
||||||
|
fileDialog.chosen.connect(function(path) {
|
||||||
|
if (!path) return
|
||||||
|
|
||||||
|
MSettings.timelineBackground = path
|
||||||
|
})
|
||||||
|
fileDialog.rejected.connect(function(path) {
|
||||||
|
MSettings.timelineBackground = ""
|
||||||
|
})
|
||||||
|
|
||||||
|
fileDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
|
import Spectral.Component 2.0
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 360
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: "Create a Room"
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
AutoTextField {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
id: roomNameField
|
||||||
|
|
||||||
|
placeholderText: "Room Name"
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoTextField {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
id: roomTopicField
|
||||||
|
|
||||||
|
placeholderText: "Room Topic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||||
|
|
||||||
|
onAccepted: spectralController.createRoom(spectralController.connection, roomNameField.text, roomTopicField.text)
|
||||||
|
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
|
import Spectral.Component 2.0
|
||||||
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 360
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: "Enter Font Family"
|
||||||
|
|
||||||
|
contentItem: AutoTextField {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
id:fontFamilyField
|
||||||
|
|
||||||
|
text: MSettings.fontFamily
|
||||||
|
placeholderText: "Font Family"
|
||||||
|
}
|
||||||
|
|
||||||
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||||
|
|
||||||
|
onAccepted: MSettings.fontFamily = fontFamilyField.text
|
||||||
|
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
|
import Spectral.Component 2.0
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
property var room
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 360
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: "Invite User"
|
||||||
|
|
||||||
|
modal: true
|
||||||
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||||
|
|
||||||
|
contentItem: AutoTextField {
|
||||||
|
id: inviteUserDialogTextField
|
||||||
|
placeholderText: "User ID"
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccepted: room.inviteToRoom(inviteUserDialogTextField.text)
|
||||||
|
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
|
import Spectral.Component 2.0
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 360
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: "Start a Chat"
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
AutoTextField {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
id: identifierField
|
||||||
|
|
||||||
|
placeholderText: "Room Alias/User ID"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
var identifier = identifierField.text
|
||||||
|
var firstChar = identifier.charAt(0)
|
||||||
|
if (firstChar == "@") {
|
||||||
|
spectralController.createDirectChat(spectralController.connection, identifier)
|
||||||
|
} else if (firstChar == "!" || firstChar == "#") {
|
||||||
|
spectralController.joinRoom(spectralController.connection, identifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
|
import Spectral.Component 2.0
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 360
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: "Login"
|
||||||
|
|
||||||
|
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||||
|
|
||||||
|
onAccepted: doLogin()
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
AutoTextField {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
id: serverField
|
||||||
|
|
||||||
|
placeholderText: "Server Address"
|
||||||
|
text: "https://matrix.org"
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoTextField {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
id: usernameField
|
||||||
|
|
||||||
|
placeholderText: "Username"
|
||||||
|
|
||||||
|
onAccepted: passwordField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoTextField {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
id: passwordField
|
||||||
|
|
||||||
|
placeholderText: "Password"
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
|
||||||
|
onAccepted: root.accept()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doLogin() {
|
||||||
|
spectralController.loginWithCredentials(serverField.text, usernameField.text, passwordField.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
property string sourceText
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 480
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
modal: true
|
||||||
|
padding: 16
|
||||||
|
|
||||||
|
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
|
||||||
|
|
||||||
|
contentItem: ScrollView {
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: sourceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Dialogs 1.2
|
||||||
|
|
||||||
|
FileDialog {
|
||||||
|
signal chosen(string path)
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: "Please choose a file"
|
||||||
|
selectMultiple: false
|
||||||
|
|
||||||
|
onAccepted: chosen(selectFolder ? folder : fileUrl)
|
||||||
|
}
|
|
@ -0,0 +1,218 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
|
import Spectral.Component 2.0
|
||||||
|
import Spectral.Effect 2.0
|
||||||
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
property var room
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 480
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
title: "Room Settings - " + (room ? room.displayName : "")
|
||||||
|
modal: true
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
spacing: 16
|
||||||
|
|
||||||
|
Avatar {
|
||||||
|
Layout.preferredWidth: 72
|
||||||
|
Layout.preferredHeight: 72
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
hint: room ? room.displayName : "No name"
|
||||||
|
source: room ? room.avatarMediaId : null
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: 4
|
||||||
|
|
||||||
|
AutoTextField {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: room ? room.name : ""
|
||||||
|
placeholderText: "Room Name"
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoTextField {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: room ? room.topic : ""
|
||||||
|
placeholderText: "Room Topic"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
visible: room ? room.predecessorId : false
|
||||||
|
|
||||||
|
padding: 8
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
|
||||||
|
icon: "\ue8d4"
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
font.bold: true
|
||||||
|
color: MPalette.foreground
|
||||||
|
text: "This room is a continuation of another conversation."
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
color: MPalette.lighter
|
||||||
|
text: "Click here to see older messages."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: MPalette.banner
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
roomListForm.enteredRoom = spectralController.connection.room(room.predecessorId)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
visible: room ? room.successorId : false
|
||||||
|
|
||||||
|
padding: 8
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
|
||||||
|
icon: "\ue8d4"
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
font.bold: true
|
||||||
|
color: MPalette.foreground
|
||||||
|
text: "This room has been replaced and is no longer active."
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
color: MPalette.lighter
|
||||||
|
text: "The conversation continues here."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: MPalette.banner
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
roomListForm.enteredRoom = spectralController.connection.room(room.successorId)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuSeparator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.preferredWidth: 100
|
||||||
|
|
||||||
|
wrapMode: Label.Wrap
|
||||||
|
text: "Main Alias"
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
model: room ? room.aliases : null
|
||||||
|
|
||||||
|
currentIndex: room ? room.aliases.indexOf(room.canonicalAlias) : -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.preferredWidth: 100
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
wrapMode: Label.Wrap
|
||||||
|
text: "Aliases"
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: room ? room.aliases : null
|
||||||
|
|
||||||
|
delegate: Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: modelData
|
||||||
|
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,164 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
|
import Spectral.Component 2.0
|
||||||
|
import Spectral.Effect 2.0
|
||||||
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
property var room
|
||||||
|
property var user
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 360
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
modal: true
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
spacing: 16
|
||||||
|
|
||||||
|
Avatar {
|
||||||
|
Layout.preferredWidth: 72
|
||||||
|
Layout.preferredHeight: 72
|
||||||
|
|
||||||
|
hint: user ? user.displayName : "No name"
|
||||||
|
source: user ? user.avatarMediaId : null
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
font.pixelSize: 18
|
||||||
|
font.bold: true
|
||||||
|
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
text: user ? user.displayName : "No Name"
|
||||||
|
color: MPalette.foreground
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: "Online"
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuSeparator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.preferredWidth: 32
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
icon: "\ue88f"
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
text: user ? user.id : "No ID"
|
||||||
|
color: MPalette.accent
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
wrapMode: Label.Wrap
|
||||||
|
text: "User ID"
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuSeparator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.preferredWidth: 32
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
icon: room.connection.isIgnored(user) ? "\ue7f5" : "\ue7f6"
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
wrapMode: Label.Wrap
|
||||||
|
text: room.connection.isIgnored(user) ? "Unignore this user" : "Ignore this user"
|
||||||
|
|
||||||
|
color: MPalette.accent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: RippleEffect {
|
||||||
|
onPrimaryClicked: {
|
||||||
|
root.close()
|
||||||
|
room.connection.isIgnored(user) ? room.connection.removeFromIgnoredUsers(user) : room.connection.addToIgnoredUsers(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.preferredWidth: 32
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
icon: "\ue5d9"
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
wrapMode: Label.Wrap
|
||||||
|
text: "Kick this user"
|
||||||
|
|
||||||
|
color: MPalette.accent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: RippleEffect {
|
||||||
|
onPrimaryClicked: room.kickMember(user.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
module Spectral.Dialog
|
||||||
|
RoomSettingsDialog 2.0 RoomSettingsDialog.qml
|
||||||
|
UserDetailDialog 2.0 UserDetailDialog.qml
|
||||||
|
MessageSourceDialog 2.0 MessageSourceDialog.qml
|
||||||
|
LoginDialog 2.0 LoginDialog.qml
|
||||||
|
CreateRoomDialog 2.0 CreateRoomDialog.qml
|
||||||
|
JoinRoomDialog 2.0 JoinRoomDialog.qml
|
||||||
|
InviteUserDialog 2.0 InviteUserDialog.qml
|
||||||
|
AcceptInvitationDialog 2.0 AcceptInvitationDialog.qml
|
||||||
|
FontFamilyDialog 2.0 FontFamilyDialog.qml
|
||||||
|
AccountDetailDialog 2.0 AccountDetailDialog.qml
|
||||||
|
OpenFileDialog 2.0 OpenFileDialog.qml
|
|
@ -1,5 +0,0 @@
|
||||||
pragma Singleton
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12
|
|
||||||
|
|
||||||
Label {}
|
|
|
@ -1,3 +1,2 @@
|
||||||
module Spectral.Font
|
module Spectral.Font
|
||||||
singleton MaterialFont 0.1 MaterialFont.qml
|
singleton MaterialFont 0.1 MaterialFont.qml
|
||||||
singleton CommonFont 0.1 CommonFont.qml
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Controls.Material 2.12
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
property var room
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Favourite"
|
||||||
|
checkable: true
|
||||||
|
checked: room.isFavourite
|
||||||
|
|
||||||
|
onTriggered: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Deprioritize"
|
||||||
|
checkable: true
|
||||||
|
checked: room.isLowPriority
|
||||||
|
|
||||||
|
onTriggered: room.isLowPriority ? room.removeTag("m.lowpriority") : room.addTag("m.lowpriority", 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuSeparator {}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Mark as Read"
|
||||||
|
|
||||||
|
onTriggered: room.markAllMessagesAsRead()
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Leave Room"
|
||||||
|
Material.foreground: Material.Red
|
||||||
|
|
||||||
|
onTriggered: room.forget()
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
|
import Spectral.Dialog 2.0
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
signal viewSource()
|
||||||
|
signal downloadAndOpen()
|
||||||
|
signal saveFileAs()
|
||||||
|
signal reply()
|
||||||
|
signal redact()
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "View Source"
|
||||||
|
|
||||||
|
onTriggered: viewSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Open Externally"
|
||||||
|
|
||||||
|
onTriggered: downloadAndOpen()
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Save As"
|
||||||
|
|
||||||
|
onTriggered: saveFileAs()
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Reply"
|
||||||
|
|
||||||
|
onTriggered: reply()
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Redact"
|
||||||
|
|
||||||
|
onTriggered: redact()
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
|
import Spectral.Dialog 2.0
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
readonly property string selectedText: contentLabel.selectedText
|
||||||
|
|
||||||
|
signal viewSource()
|
||||||
|
signal reply()
|
||||||
|
signal redact()
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "View Source"
|
||||||
|
|
||||||
|
onTriggered: viewSource()
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Reply"
|
||||||
|
|
||||||
|
onTriggered: reply()
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Redact"
|
||||||
|
|
||||||
|
onTriggered: redact()
|
||||||
|
}
|
||||||
|
|
||||||
|
onClosed: destroy()
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module Spectral.Menu.Timeline
|
||||||
|
MessageDelegateContextMenu 2.0 MessageDelegateContextMenu.qml
|
||||||
|
FileDelegateContextMenu 2.0 FileDelegateContextMenu.qml
|
|
@ -0,0 +1,2 @@
|
||||||
|
module Spectral.Menu
|
||||||
|
RoomListContextMenu 2.0 RoomListContextMenu.qml
|
|
@ -1,4 +0,0 @@
|
||||||
module Spectral.Page
|
|
||||||
Login 2.0 Login.qml
|
|
||||||
Room 2.0 Room.qml
|
|
||||||
Setting 2.0 Setting.qml
|
|
|
@ -4,6 +4,9 @@ import QtQuick.Controls.Material 2.12
|
||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
import Spectral.Component 2.0
|
||||||
|
import Spectral.Dialog 2.0
|
||||||
|
import Spectral.Effect 2.0
|
||||||
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
import Spectral 0.1
|
import Spectral 0.1
|
||||||
|
|
||||||
|
@ -16,79 +19,142 @@ Drawer {
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 32
|
anchors.margins: 24
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
spacing: 16
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
Layout.preferredWidth: 96
|
Layout.preferredWidth: 72
|
||||||
Layout.preferredHeight: 96
|
Layout.preferredHeight: 72
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
hint: room ? room.displayName : "No name"
|
hint: room ? room.displayName : "No name"
|
||||||
source: room ? room.avatarMediaId : null
|
source: room ? room.avatarMediaId : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
font.pixelSize: 18
|
||||||
|
font.bold: true
|
||||||
wrapMode: Label.Wrap
|
wrapMode: Label.Wrap
|
||||||
horizontalAlignment: Text.AlignHCenter
|
text: room ? room.displayName : "No Name"
|
||||||
text: room && room.id ? room.id : ""
|
color: MPalette.foreground
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
wrapMode: Label.Wrap
|
wrapMode: Label.Wrap
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
text: room && room.canonicalAlias ? room.canonicalAlias : "No Canonical Alias"
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
wrapMode: Label.Wrap
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
text: room ? room.totalMemberCount + " Members" : "No Member Count"
|
text: room ? room.totalMemberCount + " Members" : "No Member Count"
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuSeparator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
padding: 0
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.preferredWidth: 32
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
icon: "\ue88f"
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
wrapMode: Label.Wrap
|
||||||
|
text: room && room.canonicalAlias ? room.canonicalAlias : "No Canonical Alias"
|
||||||
|
color: MPalette.accent
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
wrapMode: Label.Wrap
|
||||||
|
text: "Main Alias"
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
wrapMode: Label.Wrap
|
||||||
|
text: room && room.topic ? room.topic : "No Topic"
|
||||||
|
color: MPalette.foreground
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
wrapMode: Label.Wrap
|
||||||
|
text: "Topic"
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: RippleEffect {
|
||||||
|
onPrimaryClicked: roomSettingDialog.createObject(ApplicationWindow.overlay, {"room": room}).open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuSeparator {
|
||||||
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
AutoTextField {
|
spacing: 8
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
Layout.preferredWidth: 32
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
|
||||||
|
icon: "\ue7ff"
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
id: roomNameField
|
wrapMode: Label.Wrap
|
||||||
text: room && room.name ? room.name : ""
|
text: room ? room.totalMemberCount + " Members" : "No Member Count"
|
||||||
|
color: MPalette.lighter
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemDelegate {
|
ToolButton {
|
||||||
Layout.preferredWidth: height
|
Layout.preferredWidth: 32
|
||||||
Layout.preferredHeight: parent.height
|
Layout.preferredHeight: 32
|
||||||
|
|
||||||
contentItem: MaterialIcon { icon: "\ue5ca" }
|
contentItem: MaterialIcon {
|
||||||
|
icon: "\ue145"
|
||||||
onClicked: room.setName(roomNameField.text)
|
color: MPalette.lighter
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
onClicked: inviteUserDialog.createObject(ApplicationWindow.overlay, {"room": room}).open()
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
AutoTextField {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
id: roomTopicField
|
|
||||||
|
|
||||||
text: room && room.topic ? room.topic : ""
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.preferredWidth: height
|
|
||||||
Layout.preferredHeight: parent.height
|
|
||||||
|
|
||||||
contentItem: MaterialIcon { icon: "\ue5ca" }
|
|
||||||
|
|
||||||
onClicked: room.setTopic(roomTopicField.text)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +172,7 @@ Drawer {
|
||||||
room: roomDrawer.room
|
room: roomDrawer.room
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: SwipeDelegate {
|
delegate: Item {
|
||||||
width: userListView.width
|
width: userListView.width
|
||||||
height: 48
|
height: 48
|
||||||
|
|
||||||
|
@ -127,62 +193,36 @@ Drawer {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
text: name
|
text: name
|
||||||
|
color: MPalette.foreground
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
swipe.right: Rectangle {
|
RippleEffect {
|
||||||
width: height
|
|
||||||
height: parent.height
|
|
||||||
anchors.right: parent.right
|
|
||||||
color: Material.accent
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
icon: "\ue879"
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeDelegate.onClicked: {
|
onPrimaryClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": room, "user": user}).open()
|
||||||
room.kickMember(userId)
|
|
||||||
swipe.close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: swipe.open(SwipeDelegate.Right)
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {}
|
ScrollBar.vertical: ScrollBar {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Button {
|
Component {
|
||||||
Layout.fillWidth: true
|
id: roomSettingDialog
|
||||||
|
|
||||||
text: "Invite User"
|
RoomSettingsDialog {}
|
||||||
flat: true
|
}
|
||||||
highlighted: true
|
|
||||||
|
|
||||||
onClicked: inviteUserDialog.open()
|
Component {
|
||||||
|
id: userDetailDialog
|
||||||
|
|
||||||
Dialog {
|
UserDetailDialog {}
|
||||||
x: (window.width - width) / 2
|
}
|
||||||
y: (window.height - height) / 2
|
|
||||||
width: 360
|
|
||||||
|
|
||||||
|
Component {
|
||||||
id: inviteUserDialog
|
id: inviteUserDialog
|
||||||
|
|
||||||
parent: ApplicationWindow.overlay
|
InviteUserDialog {}
|
||||||
|
|
||||||
title: "Input User ID"
|
|
||||||
modal: true
|
|
||||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
|
||||||
|
|
||||||
contentItem: AutoTextField {
|
|
||||||
id: inviteUserDialogTextField
|
|
||||||
placeholderText: "@bot:matrix.org"
|
|
||||||
}
|
|
||||||
|
|
||||||
onAccepted: room.inviteToRoom(inviteUserDialogTextField.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Controls.Material 2.12
|
import QtQuick.Controls.Material 2.12
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
import Spectral.Component 2.0
|
||||||
|
import Spectral.Dialog 2.0
|
||||||
|
import Spectral.Menu 2.0
|
||||||
import Spectral.Effect 2.0
|
import Spectral.Effect 2.0
|
||||||
|
|
||||||
import Spectral 0.1
|
import Spectral 0.1
|
||||||
|
@ -13,12 +15,11 @@ import Spectral.Setting 0.1
|
||||||
import SortFilterProxyModel 0.2
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
property var controller: null
|
property var connection: null
|
||||||
readonly property var user: controller.connection ? controller.connection.localUser : null
|
readonly property var user: connection ? connection.localUser : null
|
||||||
|
|
||||||
property int filter: 0
|
property int filter: 0
|
||||||
property var enteredRoom: null
|
property var enteredRoom: null
|
||||||
property alias errorControl: errorControl
|
|
||||||
|
|
||||||
signal enterRoom(var room)
|
signal enterRoom(var room)
|
||||||
signal leaveRoom(var room)
|
signal leaveRoom(var room)
|
||||||
|
@ -28,7 +29,7 @@ Item {
|
||||||
RoomListModel {
|
RoomListModel {
|
||||||
id: roomListModel
|
id: roomListModel
|
||||||
|
|
||||||
connection: controller.connection
|
connection: root.connection
|
||||||
|
|
||||||
onNewMessage: if (!window.active && MSettings.showNotification) spectralController.postNotification(roomId, eventId, roomName, senderName, text, icon)
|
onNewMessage: if (!window.active && MSettings.showNotification) spectralController.postNotification(roomId, eventId, roomName, senderName, text, icon)
|
||||||
}
|
}
|
||||||
|
@ -44,8 +45,8 @@ Item {
|
||||||
switch (category) {
|
switch (category) {
|
||||||
case 1: return "Invited"
|
case 1: return "Invited"
|
||||||
case 2: return "Favorites"
|
case 2: return "Favorites"
|
||||||
case 3: return "Rooms"
|
case 3: return "People"
|
||||||
case 4: return "People"
|
case 4: return "Rooms"
|
||||||
case 5: return "Low Priority"
|
case 5: return "Low Priority"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,6 +61,9 @@ Item {
|
||||||
]
|
]
|
||||||
|
|
||||||
filters: [
|
filters: [
|
||||||
|
ExpressionFilter {
|
||||||
|
expression: joinState != "upgraded"
|
||||||
|
},
|
||||||
RegExpFilter {
|
RegExpFilter {
|
||||||
roleName: "name"
|
roleName: "name"
|
||||||
pattern: searchField.text
|
pattern: searchField.text
|
||||||
|
@ -71,483 +75,15 @@ Item {
|
||||||
},
|
},
|
||||||
ExpressionFilter {
|
ExpressionFilter {
|
||||||
enabled: filter === 2
|
enabled: filter === 2
|
||||||
expression: category === 1 || category === 2 || category === 4
|
expression: category === 1 || category === 2 || category === 3
|
||||||
},
|
},
|
||||||
ExpressionFilter {
|
ExpressionFilter {
|
||||||
enabled: filter === 3
|
enabled: filter === 3
|
||||||
expression: category === 3 || category === 5
|
expression: category === 4 || category === 5
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Drawer {
|
|
||||||
width: Math.max(root.width, 400)
|
|
||||||
height: root.height
|
|
||||||
|
|
||||||
id: drawer
|
|
||||||
|
|
||||||
edge: Qt.LeftEdge
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: mainPage
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
readonly property string title: "Main"
|
|
||||||
|
|
||||||
id: mainColumn
|
|
||||||
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Control {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 330
|
|
||||||
|
|
||||||
padding: 24
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
Avatar {
|
|
||||||
Layout.preferredWidth: 200
|
|
||||||
Layout.preferredHeight: 200
|
|
||||||
Layout.margins: 12
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
source: root.user ? root.user.avatarMediaId : null
|
|
||||||
hint: root.user ? root.user.displayName : "?"
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
text: root.user ? root.user.displayName : "No Name"
|
|
||||||
color: "white"
|
|
||||||
font.pixelSize: 22
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
text: root.user ? root.user.id : "@example:matrix.org"
|
|
||||||
color: "white"
|
|
||||||
opacity: 0.7
|
|
||||||
font.pixelSize: 13
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle { color: Material.primary }
|
|
||||||
|
|
||||||
RippleEffect {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
onClicked: stackView.push(userPage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
width: mainColumn.width
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: AccountListModel {
|
|
||||||
controller: spectralController
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: ItemDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: user.displayName
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
controller.connection = connection
|
|
||||||
drawer.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: "Add Account"
|
|
||||||
|
|
||||||
onClicked: loginDialog.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 1
|
|
||||||
|
|
||||||
color: MSettings.darkTheme ? "#424242" : "#e7ebeb"
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: "Settings"
|
|
||||||
|
|
||||||
onClicked: stackView.push(settingsPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: "Logout"
|
|
||||||
|
|
||||||
onClicked: controller.logout(controller.connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: "Exit"
|
|
||||||
|
|
||||||
onClicked: Qt.quit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: userPage
|
|
||||||
|
|
||||||
ScrollView {
|
|
||||||
readonly property string title: "User Info"
|
|
||||||
|
|
||||||
id: main
|
|
||||||
|
|
||||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
width: main.width
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
padding: 24
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
text: "Matrix ID"
|
|
||||||
font.pixelSize: 16
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
text: root.user.id
|
|
||||||
color: "#5B7480"
|
|
||||||
font.pixelSize: 13
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
padding: 24
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
text: "Name"
|
|
||||||
font.pixelSize: 16
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
text: root.user.name
|
|
||||||
color: "#5B7480"
|
|
||||||
font.pixelSize: 13
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
padding: 24
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
text: "Avatar"
|
|
||||||
font.pixelSize: 16
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
text: root.user.avatarMediaId
|
|
||||||
color: "#5B7480"
|
|
||||||
font.pixelSize: 13
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
padding: 24
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
text: "Server"
|
|
||||||
font.pixelSize: 16
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
text: root.controller.connection.accessToken
|
|
||||||
color: "#5B7480"
|
|
||||||
font.pixelSize: 13
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
padding: 24
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
text: "Device"
|
|
||||||
font.pixelSize: 16
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
text: root.controller.connection.deviceId
|
|
||||||
color: "#5B7480"
|
|
||||||
font.pixelSize: 13
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
padding: 24
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
text: "Token"
|
|
||||||
font.pixelSize: 16
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
text: root.controller.connection.accessToken
|
|
||||||
color: "#5B7480"
|
|
||||||
font.pixelSize: 13
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: settingsPage
|
|
||||||
|
|
||||||
ScrollView {
|
|
||||||
readonly property string title: "Settings"
|
|
||||||
|
|
||||||
id: main
|
|
||||||
|
|
||||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
|
||||||
|
|
||||||
padding: 32
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
width: main.width - 64
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Switch {
|
|
||||||
text: "Dark theme"
|
|
||||||
checked: MSettings.darkTheme
|
|
||||||
|
|
||||||
onCheckedChanged: MSettings.darkTheme = checked
|
|
||||||
}
|
|
||||||
|
|
||||||
Switch {
|
|
||||||
text: "Show notifications"
|
|
||||||
checked: MSettings.showNotification
|
|
||||||
|
|
||||||
onCheckedChanged: MSettings.showNotification = checked
|
|
||||||
}
|
|
||||||
|
|
||||||
Switch {
|
|
||||||
text: "Use press and hold instead of right click"
|
|
||||||
checked: MSettings.pressAndHold
|
|
||||||
|
|
||||||
onCheckedChanged: MSettings.pressAndHold = checked
|
|
||||||
}
|
|
||||||
|
|
||||||
Switch {
|
|
||||||
text: "Show tray icon"
|
|
||||||
checked: MSettings.showTray
|
|
||||||
|
|
||||||
onCheckedChanged: MSettings.showTray = checked
|
|
||||||
}
|
|
||||||
|
|
||||||
Switch {
|
|
||||||
text: "Enable timeline background"
|
|
||||||
checked: MSettings.enableTimelineBackground
|
|
||||||
|
|
||||||
onCheckedChanged: MSettings.enableTimelineBackground = checked
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "DPI"
|
|
||||||
}
|
|
||||||
|
|
||||||
Slider {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
value: controller.dpi()
|
|
||||||
from: 100
|
|
||||||
to: 300
|
|
||||||
stepSize: 25
|
|
||||||
snapMode: Slider.SnapAlways
|
|
||||||
|
|
||||||
ToolTip.visible: pressed
|
|
||||||
ToolTip.text: value
|
|
||||||
|
|
||||||
onMoved: controller.setDpi(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 64
|
|
||||||
|
|
||||||
visible: stackView.depth > 1
|
|
||||||
|
|
||||||
color: Material.primary
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 4
|
|
||||||
|
|
||||||
ToolButton {
|
|
||||||
Layout.preferredWidth: height
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
contentItem: MaterialIcon {
|
|
||||||
icon: "\ue5c4"
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: stackView.pop()
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: stackView.currentItem.title
|
|
||||||
color: "white"
|
|
||||||
font.pixelSize: 18
|
|
||||||
elide: Label.ElideRight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StackView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
id: stackView
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
initialItem: mainPage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
@ -564,7 +100,7 @@ Item {
|
||||||
rightPadding: 18
|
rightPadding: 18
|
||||||
|
|
||||||
contentItem: RowLayout {
|
contentItem: RowLayout {
|
||||||
ItemDelegate {
|
ToolButton {
|
||||||
Layout.preferredWidth: height
|
Layout.preferredWidth: height
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
@ -614,7 +150,7 @@ Item {
|
||||||
onClicked: filterMenu.popup()
|
onClicked: filterMenu.popup()
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemDelegate {
|
ToolButton {
|
||||||
Layout.preferredWidth: height
|
Layout.preferredWidth: height
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
@ -627,40 +163,14 @@ Item {
|
||||||
|
|
||||||
AutoTextField {
|
AutoTextField {
|
||||||
readonly property bool active: text
|
readonly property bool active: text
|
||||||
readonly property bool isRoom: text.match(/#.*:.*\..*/g) || text.match(/!.*:.*\..*/g)
|
|
||||||
readonly property bool isUser: text.match(/@.*:.*\..*/g)
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
id: searchField
|
id: searchField
|
||||||
|
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
placeholderText: "Search..."
|
placeholderText: "Search..."
|
||||||
color: MPalette.lighter
|
color: MPalette.lighter
|
||||||
|
|
||||||
background: Item {}
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.preferredWidth: height
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
visible: searchField.isRoom || searchField.isUser
|
|
||||||
|
|
||||||
contentItem: MaterialIcon { icon: "\ue145" }
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (searchField.isRoom) {
|
|
||||||
controller.joinRoom(controller.connection, searchField.text)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (searchField.isUser) {
|
|
||||||
controller.createDirectChat(controller.connection, searchField.text)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
|
@ -673,9 +183,12 @@ Item {
|
||||||
source: root.user ? root.user.avatarMediaId : null
|
source: root.user ? root.user.avatarMediaId : null
|
||||||
hint: root.user ? root.user.displayName : "?"
|
hint: root.user ? root.user.displayName : "?"
|
||||||
|
|
||||||
MouseArea {
|
RippleEffect {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked: drawer.open()
|
|
||||||
|
circular: true
|
||||||
|
|
||||||
|
onClicked: accountDetailDialog.createObject(ApplicationWindow.overlay).open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -692,52 +205,6 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Control {
|
|
||||||
property string error: ""
|
|
||||||
property string detail: ""
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
id: errorControl
|
|
||||||
|
|
||||||
visible: false
|
|
||||||
|
|
||||||
topPadding: 16
|
|
||||||
bottomPadding: 16
|
|
||||||
leftPadding: 24
|
|
||||||
rightPadding: 24
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: errorControl.error
|
|
||||||
font.pixelSize: 16
|
|
||||||
color: "white"
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: errorControl.detail
|
|
||||||
font.pixelSize: 14
|
|
||||||
color: "white"
|
|
||||||
opacity: 0.6
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: "#273338"
|
|
||||||
}
|
|
||||||
|
|
||||||
RippleEffect {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
onClicked: errorControl.visible = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoListView {
|
AutoListView {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
@ -766,7 +233,7 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: unreadCount > 0 ? 4 : 0
|
width: unreadCount >= 0 ? 4 : 0
|
||||||
height: parent.height
|
height: parent.height
|
||||||
|
|
||||||
color: Material.accent
|
color: Material.accent
|
||||||
|
@ -854,11 +321,9 @@ Item {
|
||||||
RippleEffect {
|
RippleEffect {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
onSecondaryClicked: roomContextMenu.popup()
|
|
||||||
onPrimaryClicked: {
|
onPrimaryClicked: {
|
||||||
if (category === RoomType.Invited) {
|
if (category === RoomType.Invited) {
|
||||||
inviteDialog.currentRoom = currentRoom
|
acceptInvitationDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom}).open()
|
||||||
inviteDialog.open()
|
|
||||||
} else {
|
} else {
|
||||||
if (enteredRoom) {
|
if (enteredRoom) {
|
||||||
enteredRoom.displayed = false
|
enteredRoom.displayed = false
|
||||||
|
@ -869,36 +334,13 @@ Item {
|
||||||
enteredRoom = currentRoom
|
enteredRoom = currentRoom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onSecondaryClicked: roomListContextMenu.createObject(ApplicationWindow.overlay, {"room": currentRoom}).popup()
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu {
|
Component {
|
||||||
id: roomContextMenu
|
id: roomListContextMenu
|
||||||
|
|
||||||
MenuItem {
|
RoomListContextMenu {}
|
||||||
text: "Favourite"
|
|
||||||
checkable: true
|
|
||||||
checked: category === RoomType.Favorite
|
|
||||||
|
|
||||||
onTriggered: category === RoomType.Favorite ? currentRoom.removeTag("m.favourite") : currentRoom.addTag("m.favourite", 1.0)
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Deprioritize"
|
|
||||||
checkable: true
|
|
||||||
checked: category === RoomType.Deprioritized
|
|
||||||
|
|
||||||
onTriggered: category === RoomType.Deprioritized ? currentRoom.removeTag("m.lowpriority") : currentRoom.addTag("m.lowpriority", 1.0)
|
|
||||||
}
|
|
||||||
MenuSeparator {}
|
|
||||||
MenuItem {
|
|
||||||
text: "Mark as Read"
|
|
||||||
|
|
||||||
onTriggered: currentRoom.markAllMessagesAsRead()
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Leave Room"
|
|
||||||
|
|
||||||
onTriggered: currentRoom.forget()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -917,42 +359,9 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialog {
|
Component {
|
||||||
property var currentRoom
|
id: acceptInvitationDialog
|
||||||
|
|
||||||
id: inviteDialog
|
AcceptInvitationDialog {}
|
||||||
parent: ApplicationWindow.overlay
|
|
||||||
|
|
||||||
x: (window.width - width) / 2
|
|
||||||
y: (window.height - height) / 2
|
|
||||||
width: 360
|
|
||||||
|
|
||||||
title: "Action Required"
|
|
||||||
modal: true
|
|
||||||
|
|
||||||
contentItem: Label { text: "Accept this invitation?" }
|
|
||||||
|
|
||||||
footer: DialogButtonBox {
|
|
||||||
Button {
|
|
||||||
text: "Accept"
|
|
||||||
flat: true
|
|
||||||
|
|
||||||
onClicked: currentRoom.acceptInvitation()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "Reject"
|
|
||||||
flat: true
|
|
||||||
|
|
||||||
onClicked: currentRoom.forget()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "Cancel"
|
|
||||||
flat: true
|
|
||||||
|
|
||||||
onClicked: inviteDialog.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,27 +23,42 @@ Item {
|
||||||
room: currentRoom
|
room: currentRoom
|
||||||
}
|
}
|
||||||
|
|
||||||
RoomDrawer {
|
Column {
|
||||||
width: Math.min(root.width * 0.7, 480)
|
anchors.centerIn: parent
|
||||||
height: root.height
|
|
||||||
|
|
||||||
id: roomDrawer
|
spacing: 16
|
||||||
|
|
||||||
room: currentRoom
|
visible: !currentRoom
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
width: 240
|
||||||
|
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
|
source: "qrc:/assets/img/matrix.svg"
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
anchors.centerIn: parent
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
visible: !currentRoom
|
|
||||||
text: "Please choose a room."
|
text: "Welcome to Matrix, a new era of instant messaging."
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
text: "To start chatting, select a room from the room list."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
visible: currentRoom && MSettings.enableTimelineBackground
|
visible: currentRoom && MSettings.timelineBackground
|
||||||
|
|
||||||
source: MSettings.timelineBackground || MSettings.darkTheme ? "qrc:/assets/img/roompanel-dark.svg" : "qrc:/assets/img/roompanel.svg"
|
source: MSettings.timelineBackground
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +79,7 @@ Item {
|
||||||
topic: currentRoom ? (currentRoom.topic).replace(/(\r\n\t|\n|\r\t)/gm,"") : ""
|
topic: currentRoom ? (currentRoom.topic).replace(/(\r\n\t|\n|\r\t)/gm,"") : ""
|
||||||
atTop: messageListView.atYBeginning
|
atTop: messageListView.atYBeginning
|
||||||
|
|
||||||
onClicked: roomDrawer.open()
|
onClicked: roomDrawer.visible ? roomDrawer.close() : roomDrawer.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
@ -92,15 +107,16 @@ Item {
|
||||||
highlightMoveDuration: 500
|
highlightMoveDuration: 500
|
||||||
|
|
||||||
boundsBehavior: Flickable.DragOverBounds
|
boundsBehavior: Flickable.DragOverBounds
|
||||||
|
|
||||||
model: SortFilterProxyModel {
|
model: SortFilterProxyModel {
|
||||||
id: sortedMessageEventModel
|
id: sortedMessageEventModel
|
||||||
|
|
||||||
sourceModel: messageEventModel
|
sourceModel: messageEventModel
|
||||||
|
|
||||||
filters: ExpressionFilter {
|
filters: [
|
||||||
|
ExpressionFilter {
|
||||||
expression: marks !== 0x10 && eventType !== "other"
|
expression: marks !== 0x10 && eventType !== "other"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
|
||||||
onModelReset: {
|
onModelReset: {
|
||||||
if (currentRoom) {
|
if (currentRoom) {
|
||||||
|
@ -182,8 +198,7 @@ Item {
|
||||||
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
|
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDelegate {
|
MessageDelegate {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,8 +215,7 @@ Item {
|
||||||
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
|
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageDelegate {
|
MessageDelegate {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,24 +258,21 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RoundButton {
|
Button {
|
||||||
width: 64
|
|
||||||
height: 64
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
id: goBottomFab
|
|
||||||
|
|
||||||
visible: currentRoom && currentRoom.hasUnreadMessages
|
visible: currentRoom && currentRoom.hasUnreadMessages
|
||||||
|
|
||||||
contentItem: MaterialIcon {
|
topPadding: 8
|
||||||
anchors.fill: parent
|
bottomPadding: 8
|
||||||
|
leftPadding: 24
|
||||||
|
rightPadding: 24
|
||||||
|
|
||||||
icon: "\ue316"
|
Material.foreground: MPalette.foreground
|
||||||
color: "white"
|
Material.background: MPalette.banner
|
||||||
}
|
|
||||||
|
|
||||||
Material.background: Material.accent
|
text: "Go to read marker"
|
||||||
|
|
||||||
onClicked: goToEvent(currentRoom.readMarkerEventId)
|
onClicked: goToEvent(currentRoom.readMarkerEventId)
|
||||||
}
|
}
|
||||||
|
@ -287,85 +298,6 @@ Item {
|
||||||
|
|
||||||
onClicked: messageListView.positionViewAtBeginning()
|
onClicked: messageListView.positionViewAtBeginning()
|
||||||
}
|
}
|
||||||
|
|
||||||
Popup {
|
|
||||||
property string sourceText
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: 480
|
|
||||||
|
|
||||||
id: sourceDialog
|
|
||||||
|
|
||||||
parent: ApplicationWindow.overlay
|
|
||||||
|
|
||||||
padding: 16
|
|
||||||
|
|
||||||
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
|
|
||||||
|
|
||||||
contentItem: ScrollView {
|
|
||||||
clip: true
|
|
||||||
TextArea {
|
|
||||||
readOnly: true
|
|
||||||
selectByMouse: true
|
|
||||||
|
|
||||||
text: sourceDialog.sourceText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Popup {
|
|
||||||
property alias listModel: readMarkerListView.model
|
|
||||||
|
|
||||||
x: (window.width - width) / 2
|
|
||||||
y: (window.height - height) / 2
|
|
||||||
width: 320
|
|
||||||
|
|
||||||
id: readMarkerDialog
|
|
||||||
|
|
||||||
parent: ApplicationWindow.overlay
|
|
||||||
|
|
||||||
modal: true
|
|
||||||
padding: 16
|
|
||||||
|
|
||||||
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
|
|
||||||
|
|
||||||
contentItem: AutoListView {
|
|
||||||
implicitHeight: Math.min(window.height - 64,
|
|
||||||
readMarkerListView.contentHeight)
|
|
||||||
|
|
||||||
id: readMarkerListView
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
boundsBehavior: Flickable.DragOverBounds
|
|
||||||
|
|
||||||
delegate: ItemDelegate {
|
|
||||||
width: parent.width
|
|
||||||
height: 48
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 8
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Avatar {
|
|
||||||
Layout.preferredWidth: height
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
source: modelData.avatar
|
|
||||||
hint: modelData.displayName
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: modelData.displayName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Control {
|
Control {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
module Spectral.Panel
|
module Spectral.Panel
|
||||||
RoomPanel 2.0 RoomPanel.qml
|
RoomPanel 2.0 RoomPanel.qml
|
||||||
RoomListPanel 2.0 RoomListPanel.qml
|
RoomListPanel 2.0 RoomListPanel.qml
|
||||||
|
RoomDrawer 2.0 RoomDrawer.qml
|
||||||
|
|
|
@ -5,11 +5,11 @@ import Qt.labs.settings 1.0
|
||||||
Settings {
|
Settings {
|
||||||
property bool showNotification: true
|
property bool showNotification: true
|
||||||
|
|
||||||
property bool pressAndHold
|
|
||||||
property bool showTray: true
|
property bool showTray: true
|
||||||
|
|
||||||
property bool darkTheme
|
property bool darkTheme
|
||||||
|
|
||||||
property bool enableTimelineBackground: true
|
|
||||||
property string timelineBackground
|
property string timelineBackground
|
||||||
|
|
||||||
|
property string fontFamily: "Roboto,Noto Sans,Noto Color Emoji"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit b467b0816f5f6816778f90b55a9d0b5437310fd5
|
Subproject commit 52a81dfa8a5415be369d819837f445479b833cde
|
150
qml/main.qml
|
@ -7,19 +7,21 @@ import Qt.labs.platform 1.0 as Platform
|
||||||
|
|
||||||
import Spectral.Panel 2.0
|
import Spectral.Panel 2.0
|
||||||
import Spectral.Component 2.0
|
import Spectral.Component 2.0
|
||||||
import Spectral.Page 2.0
|
import Spectral.Dialog 2.0
|
||||||
import Spectral.Effect 2.0
|
import Spectral.Effect 2.0
|
||||||
|
|
||||||
import Spectral 0.1
|
import Spectral 0.1
|
||||||
import Spectral.Setting 0.1
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
|
readonly property bool inPortrait: window.width < window.height
|
||||||
|
|
||||||
Material.theme: MPalette.theme
|
Material.theme: MPalette.theme
|
||||||
Material.background: MPalette.background
|
Material.background: MPalette.background
|
||||||
|
|
||||||
width: 960
|
width: 960
|
||||||
height: 640
|
height: 640
|
||||||
minimumWidth: 720
|
minimumWidth: 480
|
||||||
minimumHeight: 360
|
minimumHeight: 360
|
||||||
|
|
||||||
id: window
|
id: window
|
||||||
|
@ -27,6 +29,8 @@ ApplicationWindow {
|
||||||
visible: true
|
visible: true
|
||||||
title: qsTr("Spectral")
|
title: qsTr("Spectral")
|
||||||
|
|
||||||
|
font.family: MSettings.fontFamily
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: MSettings.darkTheme ? "#303030" : "#FFFFFF"
|
color: MSettings.darkTheme ? "#303030" : "#FFFFFF"
|
||||||
}
|
}
|
||||||
|
@ -37,16 +41,14 @@ ApplicationWindow {
|
||||||
|
|
||||||
menu: Platform.Menu {
|
menu: Platform.Menu {
|
||||||
Platform.MenuItem {
|
Platform.MenuItem {
|
||||||
text: qsTr("Hide Window")
|
text: qsTr("Toggle Window")
|
||||||
onTriggered: hideWindow()
|
onTriggered: window.visible ? hideWindow() : showWindow()
|
||||||
}
|
}
|
||||||
Platform.MenuItem {
|
Platform.MenuItem {
|
||||||
text: qsTr("Quit")
|
text: qsTr("Quit")
|
||||||
onTriggered: Qt.quit()
|
onTriggered: Qt.quit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onActivated: showWindow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller {
|
Controller {
|
||||||
|
@ -59,130 +61,86 @@ ApplicationWindow {
|
||||||
roomForm.goToEvent(eventId)
|
roomForm.goToEvent(eventId)
|
||||||
showWindow()
|
showWindow()
|
||||||
}
|
}
|
||||||
onErrorOccured: {
|
onErrorOccured: errorControl.show(error + ": " + detail, 3000)
|
||||||
roomListForm.errorControl.error = error
|
|
||||||
roomListForm.errorControl.detail = detail
|
|
||||||
roomListForm.errorControl.visible = true
|
|
||||||
}
|
|
||||||
onSyncDone: roomListForm.errorControl.visible = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: StandardKey.Quit
|
sequence: "Ctrl+Q"
|
||||||
|
context: Qt.ApplicationShortcut
|
||||||
onActivated: Qt.quit()
|
onActivated: Qt.quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialog {
|
ToolTip {
|
||||||
property bool busy: false
|
id: errorControl
|
||||||
|
|
||||||
width: 360
|
|
||||||
x: (window.width - width) / 2
|
|
||||||
y: (window.height - height) / 2
|
|
||||||
|
|
||||||
id: loginDialog
|
|
||||||
|
|
||||||
parent: ApplicationWindow.overlay
|
parent: ApplicationWindow.overlay
|
||||||
|
|
||||||
title: "Login"
|
font.pixelSize: 14
|
||||||
|
|
||||||
contentItem: Column {
|
|
||||||
AutoTextField {
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
id: serverField
|
|
||||||
|
|
||||||
placeholderText: "Server Address"
|
|
||||||
text: "https://matrix.org"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoTextField {
|
Component {
|
||||||
width: parent.width
|
id: accountDetailDialog
|
||||||
|
|
||||||
id: usernameField
|
AccountDetailDialog {}
|
||||||
|
|
||||||
placeholderText: "Username"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoTextField {
|
Component {
|
||||||
width: parent.width
|
id: loginDialog
|
||||||
|
|
||||||
id: passwordField
|
LoginDialog {}
|
||||||
|
|
||||||
placeholderText: "Password"
|
|
||||||
echoMode: TextInput.Password
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
footer: DialogButtonBox {
|
Component {
|
||||||
Button {
|
id: joinRoomDialog
|
||||||
text: "OK"
|
|
||||||
flat: true
|
|
||||||
enabled: !loginDialog.busy
|
|
||||||
|
|
||||||
onClicked: loginDialog.doLogin()
|
JoinRoomDialog {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Component {
|
||||||
text: "Cancel"
|
id: createRoomDialog
|
||||||
flat: true
|
|
||||||
enabled: !loginDialog.busy
|
|
||||||
|
|
||||||
onClicked: loginDialog.close()
|
CreateRoomDialog {}
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip {
|
Component {
|
||||||
id: loginButtonTooltip
|
id: fontFamilyDialog
|
||||||
|
|
||||||
}
|
FontFamilyDialog {}
|
||||||
}
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
Component {
|
||||||
if (visible) spectralController.onErrorOccured.connect(showError)
|
id: chatBackgroundDialog
|
||||||
else spectralController.onErrorOccured.disconnect(showError)
|
|
||||||
|
OpenFileDialog {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showError(error, detail) {
|
Drawer {
|
||||||
loginDialog.busy = false
|
width: Math.min((inPortrait ? 0.67 : 0.3) * window.width, 360)
|
||||||
loginButtonTooltip.text = error + ": " + detail
|
height: window.height
|
||||||
loginButtonTooltip.open()
|
modal: inPortrait
|
||||||
}
|
interactive: inPortrait
|
||||||
|
position: inPortrait ? 0 : 1
|
||||||
|
visible: !inPortrait
|
||||||
|
|
||||||
function doLogin() {
|
id: roomListDrawer
|
||||||
if (!(serverField.text.startsWith("http") && serverField.text.includes("://"))) {
|
|
||||||
loginButtonTooltip.text = "Server address should start with http(s)://"
|
|
||||||
loginButtonTooltip.open()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loginDialog.busy = true
|
|
||||||
spectralController.loginWithCredentials(serverField.text, usernameField.text, passwordField.text)
|
|
||||||
|
|
||||||
spectralController.connectionAdded.connect(function(conn) {
|
|
||||||
busy = false
|
|
||||||
loginDialog.close()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SplitView {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
RoomListPanel {
|
RoomListPanel {
|
||||||
width: window.width * 0.35
|
anchors.fill: parent
|
||||||
Layout.minimumWidth: 180
|
|
||||||
|
|
||||||
id: roomListForm
|
id: roomListForm
|
||||||
|
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
controller: spectralController
|
connection: spectralController.connection
|
||||||
|
|
||||||
onLeaveRoom: roomForm.saveReadMarker(room)
|
onLeaveRoom: roomForm.saveReadMarker(room)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RoomPanel {
|
RoomPanel {
|
||||||
Layout.fillWidth: true
|
anchors.fill: parent
|
||||||
Layout.minimumWidth: 480
|
anchors.leftMargin: !inPortrait ? roomListDrawer.width : undefined
|
||||||
|
anchors.rightMargin: !inPortrait && roomDrawer.visible ? roomDrawer.width : undefined
|
||||||
|
|
||||||
id: roomForm
|
id: roomForm
|
||||||
|
|
||||||
|
@ -190,6 +148,18 @@ ApplicationWindow {
|
||||||
|
|
||||||
currentRoom: roomListForm.enteredRoom
|
currentRoom: roomListForm.enteredRoom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RoomDrawer {
|
||||||
|
width: Math.min((inPortrait ? 0.67 : 0.3) * window.width, 360)
|
||||||
|
height: window.height
|
||||||
|
modal: inPortrait
|
||||||
|
interactive: inPortrait
|
||||||
|
|
||||||
|
edge: Qt.RightEdge
|
||||||
|
|
||||||
|
id: roomDrawer
|
||||||
|
|
||||||
|
room: roomListForm.enteredRoom
|
||||||
}
|
}
|
||||||
|
|
||||||
Binding {
|
Binding {
|
||||||
|
@ -210,7 +180,7 @@ ApplicationWindow {
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
spectralController.initiated.connect(function() {
|
spectralController.initiated.connect(function() {
|
||||||
if (spectralController.accountCount == 0) loginDialog.open()
|
if (spectralController.accountCount == 0) loginDialog.createObject(window).open()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,4 +10,3 @@ Theme=Light
|
||||||
Variant=Dense
|
Variant=Dense
|
||||||
Primary=#344955
|
Primary=#344955
|
||||||
Accent=#673AB7
|
Accent=#673AB7
|
||||||
Font/Family="Roboto,Noto Sans,Noto Color Emoji"
|
|
||||||
|
|
23
res.qrc
|
@ -13,7 +13,6 @@
|
||||||
<file>imports/Spectral/Component/qmldir</file>
|
<file>imports/Spectral/Component/qmldir</file>
|
||||||
<file>imports/Spectral/Effect/ElevationEffect.qml</file>
|
<file>imports/Spectral/Effect/ElevationEffect.qml</file>
|
||||||
<file>imports/Spectral/Effect/qmldir</file>
|
<file>imports/Spectral/Effect/qmldir</file>
|
||||||
<file>imports/Spectral/Page/qmldir</file>
|
|
||||||
<file>assets/font/material.ttf</file>
|
<file>assets/font/material.ttf</file>
|
||||||
<file>assets/img/icon.icns</file>
|
<file>assets/img/icon.icns</file>
|
||||||
<file>assets/img/icon.ico</file>
|
<file>assets/img/icon.ico</file>
|
||||||
|
@ -31,17 +30,31 @@
|
||||||
<file>imports/Spectral/Component/AutoListView.qml</file>
|
<file>imports/Spectral/Component/AutoListView.qml</file>
|
||||||
<file>imports/Spectral/Component/AutoTextField.qml</file>
|
<file>imports/Spectral/Component/AutoTextField.qml</file>
|
||||||
<file>imports/Spectral/Panel/RoomPanelInput.qml</file>
|
<file>imports/Spectral/Panel/RoomPanelInput.qml</file>
|
||||||
<file>imports/Spectral/Component/SplitView.qml</file>
|
|
||||||
<file>imports/Spectral/Font/CommonFont.qml</file>
|
|
||||||
<file>imports/Spectral/Component/Timeline/SectionDelegate.qml</file>
|
<file>imports/Spectral/Component/Timeline/SectionDelegate.qml</file>
|
||||||
<file>assets/img/roompanel.svg</file>
|
|
||||||
<file>assets/img/matrix.svg</file>
|
<file>assets/img/matrix.svg</file>
|
||||||
<file>imports/Spectral/Effect/RippleEffect.qml</file>
|
<file>imports/Spectral/Effect/RippleEffect.qml</file>
|
||||||
<file>imports/Spectral/Effect/CircleMask.qml</file>
|
<file>imports/Spectral/Effect/CircleMask.qml</file>
|
||||||
<file>assets/img/roompanel-dark.svg</file>
|
|
||||||
<file>imports/Spectral/Component/Timeline/ImageDelegate.qml</file>
|
<file>imports/Spectral/Component/Timeline/ImageDelegate.qml</file>
|
||||||
<file>imports/Spectral/Component/Avatar.qml</file>
|
<file>imports/Spectral/Component/Avatar.qml</file>
|
||||||
<file>imports/Spectral/Setting/Palette.qml</file>
|
<file>imports/Spectral/Setting/Palette.qml</file>
|
||||||
<file>imports/Spectral/Component/Timeline/FileDelegate.qml</file>
|
<file>imports/Spectral/Component/Timeline/FileDelegate.qml</file>
|
||||||
|
<file>imports/Spectral/Component/FullScreenImage.qml</file>
|
||||||
|
<file>imports/Spectral/Dialog/qmldir</file>
|
||||||
|
<file>imports/Spectral/Dialog/RoomSettingsDialog.qml</file>
|
||||||
|
<file>imports/Spectral/Dialog/UserDetailDialog.qml</file>
|
||||||
|
<file>imports/Spectral/Dialog/MessageSourceDialog.qml</file>
|
||||||
|
<file>imports/Spectral/Dialog/LoginDialog.qml</file>
|
||||||
|
<file>imports/Spectral/Dialog/CreateRoomDialog.qml</file>
|
||||||
|
<file>imports/Spectral/Dialog/JoinRoomDialog.qml</file>
|
||||||
|
<file>imports/Spectral/Dialog/InviteUserDialog.qml</file>
|
||||||
|
<file>imports/Spectral/Dialog/AcceptInvitationDialog.qml</file>
|
||||||
|
<file>imports/Spectral/Menu/qmldir</file>
|
||||||
|
<file>imports/Spectral/Menu/RoomListContextMenu.qml</file>
|
||||||
|
<file>imports/Spectral/Menu/Timeline/qmldir</file>
|
||||||
|
<file>imports/Spectral/Menu/Timeline/MessageDelegateContextMenu.qml</file>
|
||||||
|
<file>imports/Spectral/Menu/Timeline/FileDelegateContextMenu.qml</file>
|
||||||
|
<file>imports/Spectral/Dialog/FontFamilyDialog.qml</file>
|
||||||
|
<file>imports/Spectral/Dialog/AccountDetailDialog.qml</file>
|
||||||
|
<file>imports/Spectral/Dialog/OpenFileDialog.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 204 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 231 KiB |
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 114 KiB |
10
spectral.pro
|
@ -20,9 +20,6 @@ isEmpty(USE_SYSTEM_SORTFILTERPROXYMODEL) {
|
||||||
isEmpty(USE_SYSTEM_QMATRIXCLIENT) {
|
isEmpty(USE_SYSTEM_QMATRIXCLIENT) {
|
||||||
USE_SYSTEM_QMATRIXCLIENT = false
|
USE_SYSTEM_QMATRIXCLIENT = false
|
||||||
}
|
}
|
||||||
isEmpty(BUNDLE_FONT) {
|
|
||||||
BUNDLE_FONT = false
|
|
||||||
}
|
|
||||||
|
|
||||||
$$USE_SYSTEM_QMATRIXCLIENT {
|
$$USE_SYSTEM_QMATRIXCLIENT {
|
||||||
PKGCONFIG += QMatrixClient
|
PKGCONFIG += QMatrixClient
|
||||||
|
@ -70,13 +67,6 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
||||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||||
|
|
||||||
RESOURCES += res.qrc
|
RESOURCES += res.qrc
|
||||||
$$BUNDLE_FONT {
|
|
||||||
message("Bundling fonts.")
|
|
||||||
DEFINES += BUNDLE_FONT
|
|
||||||
RESOURCES += font.qrc
|
|
||||||
} else {
|
|
||||||
message("Using fonts from operating system.")
|
|
||||||
}
|
|
||||||
|
|
||||||
# Additional import path used to resolve QML modules in Qt Creator's code model
|
# Additional import path used to resolve QML modules in Qt Creator's code model
|
||||||
QML_IMPORT_PATH += imports/
|
QML_IMPORT_PATH += imports/
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "events/eventcontent.h"
|
#include "events/eventcontent.h"
|
||||||
#include "events/roommessageevent.h"
|
#include "events/roommessageevent.h"
|
||||||
|
|
||||||
|
#include "csapi/account-data.h"
|
||||||
#include "csapi/joining.h"
|
#include "csapi/joining.h"
|
||||||
#include "csapi/logout.h"
|
#include "csapi/logout.h"
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QSysInfo>
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
#include <QtCore/QDir>
|
#include <QtCore/QDir>
|
||||||
#include <QtCore/QElapsedTimer>
|
#include <QtCore/QElapsedTimer>
|
||||||
|
@ -57,19 +59,25 @@ inline QString accessTokenFileName(const AccountSettings& account) {
|
||||||
'/' + fileName;
|
'/' + fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::loginWithCredentials(QString serverAddr, QString user,
|
void Controller::loginWithCredentials(QString serverAddr,
|
||||||
|
QString user,
|
||||||
QString pass) {
|
QString pass) {
|
||||||
if (!user.isEmpty() && !pass.isEmpty()) {
|
if (!user.isEmpty() && !pass.isEmpty()) {
|
||||||
|
QString deviceName = "Spectral " + QSysInfo::machineHostName() + " " +
|
||||||
|
QSysInfo::productType() + " " +
|
||||||
|
QSysInfo::productVersion() + " " +
|
||||||
|
QSysInfo::currentCpuArchitecture();
|
||||||
|
|
||||||
Connection* conn = new Connection(this);
|
Connection* conn = new Connection(this);
|
||||||
conn->setHomeserver(QUrl(serverAddr));
|
conn->setHomeserver(QUrl(serverAddr));
|
||||||
conn->connectToServer(user, pass, "");
|
conn->connectToServer(user, pass, deviceName, "");
|
||||||
connect(conn, &Connection::connected, [=] {
|
connect(conn, &Connection::connected, [=] {
|
||||||
AccountSettings account(conn->userId());
|
AccountSettings account(conn->userId());
|
||||||
account.setKeepLoggedIn(true);
|
account.setKeepLoggedIn(true);
|
||||||
account.clearAccessToken(); // Drop the legacy - just in case
|
account.clearAccessToken(); // Drop the legacy - just in case
|
||||||
account.setHomeserver(conn->homeserver());
|
account.setHomeserver(conn->homeserver());
|
||||||
account.setDeviceId(conn->deviceId());
|
account.setDeviceId(conn->deviceId());
|
||||||
account.setDeviceName("Spectral");
|
account.setDeviceName(deviceName);
|
||||||
if (!saveAccessToken(account, conn->accessToken()))
|
if (!saveAccessToken(account, conn->accessToken()))
|
||||||
qWarning() << "Couldn't save access token";
|
qWarning() << "Couldn't save access token";
|
||||||
account.sync();
|
account.sync();
|
||||||
|
@ -100,7 +108,8 @@ void Controller::logout(Connection* conn) {
|
||||||
conn->stopSync();
|
conn->stopSync();
|
||||||
emit conn->stateChanged();
|
emit conn->stateChanged();
|
||||||
emit conn->loggedOut();
|
emit conn->loggedOut();
|
||||||
if (!m_connections.isEmpty()) setConnection(m_connections[0]);
|
if (!m_connections.isEmpty())
|
||||||
|
setConnection(m_connections[0]);
|
||||||
});
|
});
|
||||||
connect(job, &LogoutJob::failure, this, [=] {
|
connect(job, &LogoutJob::failure, this, [=] {
|
||||||
emit errorOccured("Server-side Logout Failed", job->errorString());
|
emit errorOccured("Server-side Logout Failed", job->errorString());
|
||||||
|
@ -160,14 +169,16 @@ void Controller::invokeLogin() {
|
||||||
c->connectWithToken(account.userId(), accessToken, account.deviceId());
|
c->connectWithToken(account.userId(), accessToken, account.deviceId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!m_connections.isEmpty()) setConnection(m_connections[0]);
|
if (!m_connections.isEmpty())
|
||||||
|
setConnection(m_connections[0]);
|
||||||
emit initiated();
|
emit initiated();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray Controller::loadAccessToken(const AccountSettings& account) {
|
QByteArray Controller::loadAccessToken(const AccountSettings& account) {
|
||||||
QFile accountTokenFile{accessTokenFileName(account)};
|
QFile accountTokenFile{accessTokenFileName(account)};
|
||||||
if (accountTokenFile.open(QFile::ReadOnly)) {
|
if (accountTokenFile.open(QFile::ReadOnly)) {
|
||||||
if (accountTokenFile.size() < 1024) return accountTokenFile.readAll();
|
if (accountTokenFile.size() < 1024)
|
||||||
|
return accountTokenFile.readAll();
|
||||||
|
|
||||||
qWarning() << "File" << accountTokenFile.fileName() << "is"
|
qWarning() << "File" << accountTokenFile.fileName() << "is"
|
||||||
<< accountTokenFile.size()
|
<< accountTokenFile.size()
|
||||||
|
@ -203,7 +214,8 @@ void Controller::joinRoom(Connection* c, const QString& alias) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::createRoom(Connection* c, const QString& name,
|
void Controller::createRoom(Connection* c,
|
||||||
|
const QString& name,
|
||||||
const QString& topic) {
|
const QString& topic) {
|
||||||
CreateRoomJob* createRoomJob =
|
CreateRoomJob* createRoomJob =
|
||||||
c->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
|
c->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
|
||||||
|
@ -231,10 +243,12 @@ void Controller::playAudio(QUrl localFile) {
|
||||||
connect(player, &QMediaPlayer::stateChanged, [=] { player->deleteLater(); });
|
connect(player, &QMediaPlayer::stateChanged, [=] { player->deleteLater(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::postNotification(const QString& roomId, const QString& eventId,
|
void Controller::postNotification(const QString& roomId,
|
||||||
|
const QString& eventId,
|
||||||
const QString& roomName,
|
const QString& roomName,
|
||||||
const QString& senderName,
|
const QString& senderName,
|
||||||
const QString& text, const QImage& icon) {
|
const QString& text,
|
||||||
|
const QImage& icon) {
|
||||||
notificationsManager.postNotification(roomId, eventId, roomName, senderName,
|
notificationsManager.postNotification(roomId, eventId, roomName, senderName,
|
||||||
text, icon);
|
text, icon);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "connection.h"
|
#include "connection.h"
|
||||||
#include "notifications/manager.h"
|
#include "notifications/manager.h"
|
||||||
|
#include "room.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "user.h"
|
#include "user.h"
|
||||||
|
|
||||||
|
@ -53,13 +54,14 @@ class Controller : public QObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
Connection* connection() {
|
Connection* connection() {
|
||||||
if (m_connection.isNull()) return nullptr;
|
if (m_connection.isNull())
|
||||||
|
return nullptr;
|
||||||
return m_connection;
|
return m_connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setConnection(Connection* conn) {
|
void setConnection(Connection* conn) {
|
||||||
if (!conn) return;
|
if (conn == m_connection)
|
||||||
if (conn == m_connection) return;
|
return;
|
||||||
m_connection = conn;
|
m_connection = conn;
|
||||||
emit connectionChanged();
|
emit connectionChanged();
|
||||||
}
|
}
|
||||||
|
@ -99,9 +101,12 @@ class Controller : public QObject {
|
||||||
void createDirectChat(Connection* c, const QString& userID);
|
void createDirectChat(Connection* c, const QString& userID);
|
||||||
void copyToClipboard(const QString& text);
|
void copyToClipboard(const QString& text);
|
||||||
void playAudio(QUrl localFile);
|
void playAudio(QUrl localFile);
|
||||||
void postNotification(const QString& roomId, const QString& eventId,
|
void postNotification(const QString& roomId,
|
||||||
const QString& roomName, const QString& senderName,
|
const QString& eventId,
|
||||||
const QString& text, const QImage& icon);
|
const QString& roomName,
|
||||||
|
const QString& senderName,
|
||||||
|
const QString& text,
|
||||||
|
const QImage& icon);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONTROLLER_H
|
#endif // CONTROLLER_H
|
||||||
|
|
|
@ -1,15 +1,25 @@
|
||||||
#include "imageprovider.h"
|
#include "imageprovider.h"
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QStandardPaths>
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
#include <QtCore/QThread>
|
#include <QtCore/QThread>
|
||||||
|
|
||||||
using QMatrixClient::BaseJob;
|
using QMatrixClient::BaseJob;
|
||||||
|
|
||||||
ThumbnailResponse::ThumbnailResponse(QMatrixClient::Connection* c,
|
ThumbnailResponse::ThumbnailResponse(QMatrixClient::Connection* c,
|
||||||
QString id, const QSize& size)
|
QString id,
|
||||||
|
const QSize& size)
|
||||||
: c(c),
|
: c(c),
|
||||||
mediaId(std::move(id)),
|
mediaId(std::move(id)),
|
||||||
requestedSize(size),
|
requestedSize(size),
|
||||||
|
localFile(QStringLiteral("%1/image_provider/%2-%3x%4.png")
|
||||||
|
.arg(QStandardPaths::writableLocation(
|
||||||
|
QStandardPaths::CacheLocation),
|
||||||
|
mediaId,
|
||||||
|
QString::number(requestedSize.width()),
|
||||||
|
QString::number(requestedSize.height()))),
|
||||||
errorStr("Image request hasn't started") {
|
errorStr("Image request hasn't started") {
|
||||||
if (requestedSize.isEmpty()) {
|
if (requestedSize.isEmpty()) {
|
||||||
errorStr.clear();
|
errorStr.clear();
|
||||||
|
@ -18,11 +28,19 @@ ThumbnailResponse::ThumbnailResponse(QMatrixClient::Connection* c,
|
||||||
}
|
}
|
||||||
if (mediaId.count('/') != 1) {
|
if (mediaId.count('/') != 1) {
|
||||||
errorStr =
|
errorStr =
|
||||||
tr("Media id '%1' doesn't follow server/mediaId pattern")
|
tr("Media id '%1' doesn't follow server/mediaId pattern").arg(mediaId);
|
||||||
.arg(mediaId);
|
|
||||||
emit finished();
|
emit finished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QImage cachedImage;
|
||||||
|
if (cachedImage.load(localFile)) {
|
||||||
|
image = cachedImage;
|
||||||
|
errorStr.clear();
|
||||||
|
emit finished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Execute a request on the main thread asynchronously
|
// Execute a request on the main thread asynchronously
|
||||||
moveToThread(c->thread());
|
moveToThread(c->thread());
|
||||||
QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest,
|
QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest,
|
||||||
|
@ -45,6 +63,14 @@ void ThumbnailResponse::prepareResult() {
|
||||||
QWriteLocker _(&lock);
|
QWriteLocker _(&lock);
|
||||||
if (job->error() == BaseJob::Success) {
|
if (job->error() == BaseJob::Success) {
|
||||||
image = job->thumbnail();
|
image = job->thumbnail();
|
||||||
|
|
||||||
|
QString localPath = QFileInfo(localFile).absolutePath();
|
||||||
|
QDir dir;
|
||||||
|
if (!dir.exists(localPath))
|
||||||
|
dir.mkpath(localPath);
|
||||||
|
|
||||||
|
image.save(localFile);
|
||||||
|
|
||||||
errorStr.clear();
|
errorStr.clear();
|
||||||
} else if (job->error() == BaseJob::Abandoned) {
|
} else if (job->error() == BaseJob::Abandoned) {
|
||||||
errorStr = tr("Image request has been cancelled");
|
errorStr = tr("Image request has been cancelled");
|
||||||
|
@ -83,7 +109,7 @@ void ThumbnailResponse::cancel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QQuickImageResponse* ImageProvider::requestImageResponse(
|
QQuickImageResponse* ImageProvider::requestImageResponse(
|
||||||
const QString& id, const QSize& requestedSize) {
|
const QString& id,
|
||||||
qDebug() << "ImageProvider: requesting " << id << "of size" << requestedSize;
|
const QSize& requestedSize) {
|
||||||
return new ThumbnailResponse(m_connection.load(), id, requestedSize);
|
return new ThumbnailResponse(m_connection.load(), id, requestedSize);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
#include <connection.h>
|
#include <connection.h>
|
||||||
#include <jobs/mediathumbnailjob.h>
|
#include <jobs/mediathumbnailjob.h>
|
||||||
|
|
||||||
#include <QtCore/QReadWriteLock>
|
|
||||||
#include <QtCore/QAtomicPointer>
|
#include <QtCore/QAtomicPointer>
|
||||||
|
#include <QtCore/QReadWriteLock>
|
||||||
|
|
||||||
namespace QMatrixClient {
|
namespace QMatrixClient {
|
||||||
class Connection;
|
class Connection;
|
||||||
|
@ -17,11 +17,12 @@ class Connection;
|
||||||
class ThumbnailResponse : public QQuickImageResponse {
|
class ThumbnailResponse : public QQuickImageResponse {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ThumbnailResponse(QMatrixClient::Connection* c, QString mediaId,
|
ThumbnailResponse(QMatrixClient::Connection* c,
|
||||||
|
QString mediaId,
|
||||||
const QSize& requestedSize);
|
const QSize& requestedSize);
|
||||||
~ThumbnailResponse() override = default;
|
~ThumbnailResponse() override = default;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void startRequest();
|
void startRequest();
|
||||||
void prepareResult();
|
void prepareResult();
|
||||||
void doCancel();
|
void doCancel();
|
||||||
|
@ -30,6 +31,7 @@ private slots:
|
||||||
QMatrixClient::Connection* c;
|
QMatrixClient::Connection* c;
|
||||||
const QString mediaId;
|
const QString mediaId;
|
||||||
const QSize requestedSize;
|
const QSize requestedSize;
|
||||||
|
const QString localFile;
|
||||||
QMatrixClient::MediaThumbnailJob* job = nullptr;
|
QMatrixClient::MediaThumbnailJob* job = nullptr;
|
||||||
|
|
||||||
QImage image;
|
QImage image;
|
||||||
|
@ -49,7 +51,8 @@ class ImageProvider : public QObject, public QQuickAsyncImageProvider {
|
||||||
explicit ImageProvider() = default;
|
explicit ImageProvider() = default;
|
||||||
|
|
||||||
QQuickImageResponse* requestImageResponse(
|
QQuickImageResponse* requestImageResponse(
|
||||||
const QString& id, const QSize& requestedSize) override;
|
const QString& id,
|
||||||
|
const QSize& requestedSize) override;
|
||||||
|
|
||||||
QMatrixClient::Connection* connection() { return m_connection; }
|
QMatrixClient::Connection* connection() { return m_connection; }
|
||||||
void setConnection(QMatrixClient::Connection* connection) {
|
void setConnection(QMatrixClient::Connection* connection) {
|
||||||
|
|
24
src/main.cpp
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
using namespace QMatrixClient;
|
using namespace QMatrixClient;
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN) || defined(Q_OS_FREEBSD)
|
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN) || defined(Q_OS_FREEBSD)
|
||||||
if (qgetenv("QT_SCALE_FACTOR").size() == 0) {
|
if (qgetenv("QT_SCALE_FACTOR").size() == 0) {
|
||||||
QSettings settings("ENCOM", "Spectral");
|
QSettings settings("ENCOM", "Spectral");
|
||||||
|
@ -36,6 +36,8 @@ int main(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
|
||||||
|
|
||||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||||
|
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
@ -57,26 +59,24 @@ int main(int argc, char *argv[]) {
|
||||||
"RoomMessageEvent", "ENUM");
|
"RoomMessageEvent", "ENUM");
|
||||||
qmlRegisterUncreatableType<RoomType>("Spectral", 0, 1, "RoomType", "ENUM");
|
qmlRegisterUncreatableType<RoomType>("Spectral", 0, 1, "RoomType", "ENUM");
|
||||||
|
|
||||||
qRegisterMetaType<User *>("User*");
|
qRegisterMetaType<User*>("User*");
|
||||||
qRegisterMetaType<Room *>("Room*");
|
qRegisterMetaType<User*>("const User*");
|
||||||
|
qRegisterMetaType<Room*>("Room*");
|
||||||
|
qRegisterMetaType<Connection*>("Connection*");
|
||||||
qRegisterMetaType<MessageEventType>("MessageEventType");
|
qRegisterMetaType<MessageEventType>("MessageEventType");
|
||||||
qRegisterMetaType<SpectralRoom *>("SpectralRoom*");
|
qRegisterMetaType<SpectralRoom*>("SpectralRoom*");
|
||||||
qRegisterMetaType<SpectralUser *>("SpectralUser*");
|
qRegisterMetaType<SpectralUser*>("SpectralUser*");
|
||||||
|
|
||||||
#if defined(BUNDLE_FONT)
|
|
||||||
QFontDatabase::addApplicationFont(":/assets/font/roboto.ttf");
|
|
||||||
QFontDatabase::addApplicationFont(":/assets/font/twemoji.ttf");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
|
|
||||||
engine.addImportPath("qrc:/imports");
|
engine.addImportPath("qrc:/imports");
|
||||||
ImageProvider *m_provider = new ImageProvider();
|
ImageProvider* m_provider = new ImageProvider();
|
||||||
engine.rootContext()->setContextProperty("imageProvider", m_provider);
|
engine.rootContext()->setContextProperty("imageProvider", m_provider);
|
||||||
engine.addImageProvider(QLatin1String("mxc"), m_provider);
|
engine.addImageProvider(QLatin1String("mxc"), m_provider);
|
||||||
|
|
||||||
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
|
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
|
||||||
if (engine.rootObjects().isEmpty()) return -1;
|
if (engine.rootObjects().isEmpty())
|
||||||
|
return -1;
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const {
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageEventModel::MessageEventModel(QObject *parent)
|
MessageEventModel::MessageEventModel(QObject* parent)
|
||||||
: QAbstractListModel(parent), m_currentRoom(nullptr) {
|
: QAbstractListModel(parent), m_currentRoom(nullptr) {
|
||||||
using namespace QMatrixClient;
|
using namespace QMatrixClient;
|
||||||
qmlRegisterType<FileTransferInfo>();
|
qmlRegisterType<FileTransferInfo>();
|
||||||
|
@ -52,8 +52,9 @@ MessageEventModel::MessageEventModel(QObject *parent)
|
||||||
|
|
||||||
MessageEventModel::~MessageEventModel() {}
|
MessageEventModel::~MessageEventModel() {}
|
||||||
|
|
||||||
void MessageEventModel::setRoom(SpectralRoom *room) {
|
void MessageEventModel::setRoom(SpectralRoom* room) {
|
||||||
if (room == m_currentRoom) return;
|
if (room == m_currentRoom)
|
||||||
|
return;
|
||||||
|
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
if (m_currentRoom) {
|
if (m_currentRoom) {
|
||||||
|
@ -96,8 +97,9 @@ void MessageEventModel::setRoom(SpectralRoom *room) {
|
||||||
connect(m_currentRoom, &Room::pendingEventAdded, this,
|
connect(m_currentRoom, &Room::pendingEventAdded, this,
|
||||||
&MessageEventModel::endInsertRows);
|
&MessageEventModel::endInsertRows);
|
||||||
connect(m_currentRoom, &Room::pendingEventAboutToMerge, this,
|
connect(m_currentRoom, &Room::pendingEventAboutToMerge, this,
|
||||||
[this](RoomEvent *, int i) {
|
[this](RoomEvent*, int i) {
|
||||||
if (i == 0) return; // No need to move anything, just refresh
|
if (i == 0)
|
||||||
|
return; // No need to move anything, just refresh
|
||||||
|
|
||||||
movingEvent = true;
|
movingEvent = true;
|
||||||
// Reverse i because row 0 is bottommost in the model
|
// Reverse i because row 0 is bottommost in the model
|
||||||
|
@ -131,7 +133,7 @@ void MessageEventModel::setRoom(SpectralRoom *room) {
|
||||||
refreshEventRoles(lastReadEventId, {ReadMarkerRole});
|
refreshEventRoles(lastReadEventId, {ReadMarkerRole});
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::replacedEvent, this,
|
connect(m_currentRoom, &Room::replacedEvent, this,
|
||||||
[this](const RoomEvent *newEvent) {
|
[this](const RoomEvent* newEvent) {
|
||||||
refreshLastUserEvents(refreshEvent(newEvent->id()) -
|
refreshLastUserEvents(refreshEvent(newEvent->id()) -
|
||||||
timelineBaseIndex());
|
timelineBaseIndex());
|
||||||
});
|
});
|
||||||
|
@ -144,10 +146,15 @@ void MessageEventModel::setRoom(SpectralRoom *room) {
|
||||||
connect(m_currentRoom, &Room::fileTransferCancelled, this,
|
connect(m_currentRoom, &Room::fileTransferCancelled, this,
|
||||||
&MessageEventModel::refreshEvent);
|
&MessageEventModel::refreshEvent);
|
||||||
connect(m_currentRoom, &Room::readMarkerForUserMoved, this,
|
connect(m_currentRoom, &Room::readMarkerForUserMoved, this,
|
||||||
[=](User *, QString fromEventId, QString toEventId) {
|
[=](User*, QString fromEventId, QString toEventId) {
|
||||||
refreshEventRoles(fromEventId, {UserMarkerRole});
|
refreshEventRoles(fromEventId, {UserMarkerRole});
|
||||||
refreshEventRoles(toEventId, {UserMarkerRole});
|
refreshEventRoles(toEventId, {UserMarkerRole});
|
||||||
});
|
});
|
||||||
|
connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged,
|
||||||
|
this, [=] {
|
||||||
|
beginResetModel();
|
||||||
|
endResetModel();
|
||||||
|
});
|
||||||
qDebug() << "Connected to room" << room->id() << "as"
|
qDebug() << "Connected to room" << room->id() << "as"
|
||||||
<< room->localUser()->id();
|
<< room->localUser()->id();
|
||||||
} else
|
} else
|
||||||
|
@ -155,23 +162,25 @@ void MessageEventModel::setRoom(SpectralRoom *room) {
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
int MessageEventModel::refreshEvent(const QString &eventId) {
|
int MessageEventModel::refreshEvent(const QString& eventId) {
|
||||||
return refreshEventRoles(eventId);
|
return refreshEventRoles(eventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageEventModel::refreshRow(int row) { refreshEventRoles(row); }
|
void MessageEventModel::refreshRow(int row) {
|
||||||
|
refreshEventRoles(row);
|
||||||
|
}
|
||||||
|
|
||||||
int MessageEventModel::timelineBaseIndex() const {
|
int MessageEventModel::timelineBaseIndex() const {
|
||||||
return m_currentRoom ? int(m_currentRoom->pendingEvents().size()) : 0;
|
return m_currentRoom ? int(m_currentRoom->pendingEvents().size()) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageEventModel::refreshEventRoles(int row, const QVector<int> &roles) {
|
void MessageEventModel::refreshEventRoles(int row, const QVector<int>& roles) {
|
||||||
const auto idx = index(row);
|
const auto idx = index(row);
|
||||||
emit dataChanged(idx, idx, roles);
|
emit dataChanged(idx, idx, roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MessageEventModel::refreshEventRoles(const QString &eventId,
|
int MessageEventModel::refreshEventRoles(const QString& eventId,
|
||||||
const QVector<int> &roles) {
|
const QVector<int>& roles) {
|
||||||
const auto it = m_currentRoom->findInTimeline(eventId);
|
const auto it = m_currentRoom->findInTimeline(eventId);
|
||||||
if (it == m_currentRoom->timelineEdge()) {
|
if (it == m_currentRoom->timelineEdge()) {
|
||||||
qWarning() << "Trying to refresh inexistent event:" << eventId;
|
qWarning() << "Trying to refresh inexistent event:" << eventId;
|
||||||
|
@ -183,15 +192,16 @@ int MessageEventModel::refreshEventRoles(const QString &eventId,
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool hasValidTimestamp(const QMatrixClient::TimelineItem &ti) {
|
inline bool hasValidTimestamp(const QMatrixClient::TimelineItem& ti) {
|
||||||
return ti->timestamp().isValid();
|
return ti->timestamp().isValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
QDateTime MessageEventModel::makeMessageTimestamp(
|
QDateTime MessageEventModel::makeMessageTimestamp(
|
||||||
const QMatrixClient::Room::rev_iter_t &baseIt) const {
|
const QMatrixClient::Room::rev_iter_t& baseIt) const {
|
||||||
const auto &timeline = m_currentRoom->messageEvents();
|
const auto& timeline = m_currentRoom->messageEvents();
|
||||||
auto ts = baseIt->event()->timestamp();
|
auto ts = baseIt->event()->timestamp();
|
||||||
if (ts.isValid()) return ts;
|
if (ts.isValid())
|
||||||
|
return ts;
|
||||||
|
|
||||||
// The event is most likely redacted or just invalid.
|
// The event is most likely redacted or just invalid.
|
||||||
// Look for the nearest date around and slap zero time to it.
|
// Look for the nearest date around and slap zero time to it.
|
||||||
|
@ -210,11 +220,14 @@ QDateTime MessageEventModel::makeMessageTimestamp(
|
||||||
|
|
||||||
QString MessageEventModel::renderDate(QDateTime timestamp) const {
|
QString MessageEventModel::renderDate(QDateTime timestamp) const {
|
||||||
auto date = timestamp.toLocalTime().date();
|
auto date = timestamp.toLocalTime().date();
|
||||||
if (date == QDate::currentDate()) return tr("Today");
|
if (date == QDate::currentDate())
|
||||||
if (date == QDate::currentDate().addDays(-1)) return tr("Yesterday");
|
return tr("Today");
|
||||||
|
if (date == QDate::currentDate().addDays(-1))
|
||||||
|
return tr("Yesterday");
|
||||||
if (date == QDate::currentDate().addDays(-2))
|
if (date == QDate::currentDate().addDays(-2))
|
||||||
return tr("The day before yesterday");
|
return tr("The day before yesterday");
|
||||||
if (date > QDate::currentDate().addDays(-7)) return date.toString("dddd");
|
if (date > QDate::currentDate().addDays(-7))
|
||||||
|
return date.toString("dddd");
|
||||||
return date.toString(Qt::DefaultLocaleShortDate);
|
return date.toString(Qt::DefaultLocaleShortDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,8 +235,8 @@ void MessageEventModel::refreshLastUserEvents(int baseTimelineRow) {
|
||||||
if (!m_currentRoom || m_currentRoom->timelineSize() <= baseTimelineRow)
|
if (!m_currentRoom || m_currentRoom->timelineSize() <= baseTimelineRow)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const auto &timelineBottom = m_currentRoom->messageEvents().rbegin();
|
const auto& timelineBottom = m_currentRoom->messageEvents().rbegin();
|
||||||
const auto &lastSender = (*(timelineBottom + baseTimelineRow))->senderId();
|
const auto& lastSender = (*(timelineBottom + baseTimelineRow))->senderId();
|
||||||
const auto limit = timelineBottom + std::min(baseTimelineRow + 10,
|
const auto limit = timelineBottom + std::min(baseTimelineRow + 10,
|
||||||
m_currentRoom->timelineSize());
|
m_currentRoom->timelineSize());
|
||||||
for (auto it = timelineBottom + std::max(baseTimelineRow - 10, 0);
|
for (auto it = timelineBottom + std::max(baseTimelineRow - 10, 0);
|
||||||
|
@ -235,12 +248,13 @@ void MessageEventModel::refreshLastUserEvents(int baseTimelineRow) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int MessageEventModel::rowCount(const QModelIndex &parent) const {
|
int MessageEventModel::rowCount(const QModelIndex& parent) const {
|
||||||
if (!m_currentRoom || parent.isValid()) return 0;
|
if (!m_currentRoom || parent.isValid())
|
||||||
|
return 0;
|
||||||
return m_currentRoom->timelineSize();
|
return m_currentRoom->timelineSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
|
QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
|
||||||
const auto row = idx.row();
|
const auto row = idx.row();
|
||||||
|
|
||||||
if (!m_currentRoom || row < 0 ||
|
if (!m_currentRoom || row < 0 ||
|
||||||
|
@ -253,14 +267,14 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
|
||||||
std::max(0, row - timelineBaseIndex());
|
std::max(0, row - timelineBaseIndex());
|
||||||
const auto pendingIt = m_currentRoom->pendingEvents().crbegin() +
|
const auto pendingIt = m_currentRoom->pendingEvents().crbegin() +
|
||||||
std::min(row, timelineBaseIndex());
|
std::min(row, timelineBaseIndex());
|
||||||
const auto &evt = isPending ? **pendingIt : **timelineIt;
|
const auto& evt = isPending ? **pendingIt : **timelineIt;
|
||||||
|
|
||||||
if (role == Qt::DisplayRole) {
|
if (role == Qt::DisplayRole) {
|
||||||
return utils::removeReply(utils::eventToString(evt, m_currentRoom, Qt::RichText));
|
return utils::removeReply(m_currentRoom->eventToString(evt, Qt::RichText));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == MessageRole) {
|
if (role == MessageRole) {
|
||||||
return utils::removeReply(utils::eventToString(evt, m_currentRoom));
|
return utils::removeReply(m_currentRoom->eventToString(evt));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == Qt::ToolTipRole) {
|
if (role == Qt::ToolTipRole) {
|
||||||
|
@ -282,7 +296,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
|
||||||
return e->hasFileContent() ? "file" : "message";
|
return e->hasFileContent() ? "file" : "message";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (evt.isStateEvent()) return "state";
|
if (evt.isStateEvent())
|
||||||
|
return "state";
|
||||||
|
|
||||||
return "other";
|
return "other";
|
||||||
}
|
}
|
||||||
|
@ -298,7 +313,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
|
||||||
|
|
||||||
if (role == ContentTypeRole) {
|
if (role == ContentTypeRole) {
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||||
const auto &contentType = e->mimeType().name();
|
const auto& contentType = e->mimeType().name();
|
||||||
return contentType == "text/plain" ? QStringLiteral("text/html")
|
return contentType == "text/plain" ? QStringLiteral("text/html")
|
||||||
: contentType;
|
: contentType;
|
||||||
}
|
}
|
||||||
|
@ -323,19 +338,27 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == HighlightRole) return m_currentRoom->isEventHighlighted(&evt);
|
if (role == HighlightRole)
|
||||||
|
return m_currentRoom->isEventHighlighted(&evt);
|
||||||
|
|
||||||
if (role == ReadMarkerRole)
|
if (role == ReadMarkerRole)
|
||||||
return evt.id() == lastReadEventId && row > timelineBaseIndex();
|
return evt.id() == lastReadEventId && row > timelineBaseIndex();
|
||||||
|
|
||||||
if (role == SpecialMarksRole) {
|
if (role == SpecialMarksRole) {
|
||||||
if (isPending) return pendingIt->deliveryStatus();
|
if (isPending)
|
||||||
|
return pendingIt->deliveryStatus();
|
||||||
|
|
||||||
if (is<RedactionEvent>(evt)) return EventStatus::Hidden;
|
if (is<RedactionEvent>(evt))
|
||||||
if (evt.isRedacted()) return EventStatus::Hidden;
|
return EventStatus::Hidden;
|
||||||
|
if (evt.isRedacted())
|
||||||
|
return EventStatus::Hidden;
|
||||||
|
|
||||||
if (evt.isStateEvent() &&
|
if (evt.isStateEvent() &&
|
||||||
static_cast<const StateEventBase &>(evt).repeatsState())
|
static_cast<const StateEventBase&>(evt).repeatsState())
|
||||||
|
return EventStatus::Hidden;
|
||||||
|
|
||||||
|
if (m_currentRoom->connection()->isIgnored(
|
||||||
|
m_currentRoom->user(evt.senderId())))
|
||||||
return EventStatus::Hidden;
|
return EventStatus::Hidden;
|
||||||
|
|
||||||
return EventStatus::Normal;
|
return EventStatus::Normal;
|
||||||
|
@ -351,7 +374,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == AnnotationRole)
|
if (role == AnnotationRole)
|
||||||
if (isPending) return pendingIt->annotation();
|
if (isPending)
|
||||||
|
return pendingIt->annotation();
|
||||||
|
|
||||||
if (role == TimeRole || role == SectionRole) {
|
if (role == TimeRole || role == SectionRole) {
|
||||||
auto ts =
|
auto ts =
|
||||||
|
@ -361,8 +385,9 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
|
||||||
|
|
||||||
if (role == UserMarkerRole) {
|
if (role == UserMarkerRole) {
|
||||||
QVariantList variantList;
|
QVariantList variantList;
|
||||||
for (User *user : m_currentRoom->usersAtEventId(evt.id())) {
|
for (User* user : m_currentRoom->usersAtEventId(evt.id())) {
|
||||||
if (user == m_currentRoom->localUser()) continue;
|
if (user == m_currentRoom->localUser())
|
||||||
|
continue;
|
||||||
variantList.append(QVariant::fromValue(user));
|
variantList.append(QVariant::fromValue(user));
|
||||||
}
|
}
|
||||||
return variantList;
|
return variantList;
|
||||||
|
@ -370,22 +395,24 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
|
||||||
|
|
||||||
if (role == ReplyEventIdRole || role == ReplyDisplayRole ||
|
if (role == ReplyEventIdRole || role == ReplyDisplayRole ||
|
||||||
role == ReplyAuthorRole) {
|
role == ReplyAuthorRole) {
|
||||||
const QString &replyEventId = evt.contentJson()["m.relates_to"]
|
const QString& replyEventId = evt.contentJson()["m.relates_to"]
|
||||||
.toObject()["m.in_reply_to"]
|
.toObject()["m.in_reply_to"]
|
||||||
.toObject()["event_id"]
|
.toObject()["event_id"]
|
||||||
.toString();
|
.toString();
|
||||||
if (replyEventId.isEmpty()) return {};
|
if (replyEventId.isEmpty())
|
||||||
|
return {};
|
||||||
const auto replyIt = m_currentRoom->findInTimeline(replyEventId);
|
const auto replyIt = m_currentRoom->findInTimeline(replyEventId);
|
||||||
if (replyIt == m_currentRoom->timelineEdge()) return {};
|
if (replyIt == m_currentRoom->timelineEdge())
|
||||||
|
return {};
|
||||||
const auto& replyEvt = **replyIt;
|
const auto& replyEvt = **replyIt;
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case ReplyEventIdRole:
|
case ReplyEventIdRole:
|
||||||
return replyEventId;
|
return replyEventId;
|
||||||
case ReplyDisplayRole:
|
case ReplyDisplayRole:
|
||||||
return utils::removeReply(utils::eventToString(replyEvt, m_currentRoom, Qt::RichText));
|
return utils::removeReply(
|
||||||
|
m_currentRoom->eventToString(replyEvt, Qt::RichText));
|
||||||
case ReplyAuthorRole:
|
case ReplyAuthorRole:
|
||||||
return QVariant::fromValue(
|
return QVariant::fromValue(m_currentRoom->user(replyEvt.senderId()));
|
||||||
m_currentRoom->user(replyEvt.senderId()));
|
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -394,7 +421,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
|
||||||
role == AboveAuthorRole || role == AboveTimeRole)
|
role == AboveAuthorRole || role == AboveTimeRole)
|
||||||
for (auto r = row + 1; r < rowCount(); ++r) {
|
for (auto r = row + 1; r < rowCount(); ++r) {
|
||||||
auto i = index(r);
|
auto i = index(r);
|
||||||
if (data(i, SpecialMarksRole) != EventStatus::Hidden) switch (role) {
|
if (data(i, SpecialMarksRole) != EventStatus::Hidden)
|
||||||
|
switch (role) {
|
||||||
case AboveEventTypeRole:
|
case AboveEventTypeRole:
|
||||||
return data(i, EventTypeRole);
|
return data(i, EventTypeRole);
|
||||||
case AboveSectionRole:
|
case AboveSectionRole:
|
||||||
|
@ -409,7 +437,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int MessageEventModel::eventIDToIndex(const QString &eventID) {
|
int MessageEventModel::eventIDToIndex(const QString& eventID) {
|
||||||
const auto it = m_currentRoom->findInTimeline(eventID);
|
const auto it = m_currentRoom->findInTimeline(eventID);
|
||||||
if (it == m_currentRoom->timelineEdge()) {
|
if (it == m_currentRoom->timelineEdge()) {
|
||||||
qWarning() << "Trying to find inexistent event:" << eventID;
|
qWarning() << "Trying to find inexistent event:" << eventID;
|
||||||
|
|
|
@ -16,8 +16,10 @@ RoomListModel::RoomListModel(QObject* parent) : QAbstractListModel(parent) {}
|
||||||
RoomListModel::~RoomListModel() {}
|
RoomListModel::~RoomListModel() {}
|
||||||
|
|
||||||
void RoomListModel::setConnection(Connection* connection) {
|
void RoomListModel::setConnection(Connection* connection) {
|
||||||
if (connection == m_connection) return;
|
if (connection == m_connection)
|
||||||
if (m_connection) m_connection->disconnect(this);
|
return;
|
||||||
|
if (m_connection)
|
||||||
|
m_connection->disconnect(this);
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
qDebug() << "Removing current connection...";
|
qDebug() << "Removing current connection...";
|
||||||
m_connection = nullptr;
|
m_connection = nullptr;
|
||||||
|
@ -29,7 +31,8 @@ void RoomListModel::setConnection(Connection* connection) {
|
||||||
|
|
||||||
m_connection = connection;
|
m_connection = connection;
|
||||||
|
|
||||||
for (SpectralRoom* room : m_rooms) room->disconnect(this);
|
for (SpectralRoom* room : m_rooms)
|
||||||
|
room->disconnect(this);
|
||||||
|
|
||||||
connect(connection, &Connection::connected, this,
|
connect(connection, &Connection::connected, this,
|
||||||
&RoomListModel::doResetModel);
|
&RoomListModel::doResetModel);
|
||||||
|
@ -40,6 +43,12 @@ void RoomListModel::setConnection(Connection* connection) {
|
||||||
connect(connection, &Connection::leftRoom, this, &RoomListModel::updateRoom);
|
connect(connection, &Connection::leftRoom, this, &RoomListModel::updateRoom);
|
||||||
connect(connection, &Connection::aboutToDeleteRoom, this,
|
connect(connection, &Connection::aboutToDeleteRoom, this,
|
||||||
&RoomListModel::deleteRoom);
|
&RoomListModel::deleteRoom);
|
||||||
|
connect(connection, &Connection::directChatsListChanged, this,
|
||||||
|
[=](Connection::DirectChatsMap additions,
|
||||||
|
Connection::DirectChatsMap removals) {
|
||||||
|
for (QString roomID : additions.values() + removals.values())
|
||||||
|
refresh(static_cast<SpectralRoom*>(connection->room(roomID)));
|
||||||
|
});
|
||||||
|
|
||||||
doResetModel();
|
doResetModel();
|
||||||
}
|
}
|
||||||
|
@ -47,11 +56,14 @@ void RoomListModel::setConnection(Connection* connection) {
|
||||||
void RoomListModel::doResetModel() {
|
void RoomListModel::doResetModel() {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_rooms.clear();
|
m_rooms.clear();
|
||||||
for (auto r : m_connection->roomMap()) doAddRoom(r);
|
for (auto r : m_connection->roomMap())
|
||||||
|
doAddRoom(r);
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
SpectralRoom* RoomListModel::roomAt(int row) { return m_rooms.at(row); }
|
SpectralRoom* RoomListModel::roomAt(int row) {
|
||||||
|
return m_rooms.at(row);
|
||||||
|
}
|
||||||
|
|
||||||
void RoomListModel::doAddRoom(Room* r) {
|
void RoomListModel::doAddRoom(Room* r) {
|
||||||
if (auto* room = static_cast<SpectralRoom*>(r)) {
|
if (auto* room = static_cast<SpectralRoom*>(r)) {
|
||||||
|
@ -77,15 +89,18 @@ void RoomListModel::connectRoomSignals(SpectralRoom* room) {
|
||||||
connect(room, &Room::addedMessages, this,
|
connect(room, &Room::addedMessages, this,
|
||||||
[=] { refresh(room, {LastEventRole}); });
|
[=] { refresh(room, {LastEventRole}); });
|
||||||
connect(room, &Room::notificationCountChanged, this, [=] {
|
connect(room, &Room::notificationCountChanged, this, [=] {
|
||||||
if (room->notificationCount() == 0) return;
|
if (room->notificationCount() == 0)
|
||||||
if (room->timelineSize() == 0) return;
|
return;
|
||||||
|
if (room->timelineSize() == 0)
|
||||||
|
return;
|
||||||
const RoomEvent* lastEvent = room->messageEvents().rbegin()->get();
|
const RoomEvent* lastEvent = room->messageEvents().rbegin()->get();
|
||||||
if (lastEvent->isStateEvent()) return;
|
if (lastEvent->isStateEvent())
|
||||||
|
return;
|
||||||
User* sender = room->user(lastEvent->senderId());
|
User* sender = room->user(lastEvent->senderId());
|
||||||
if (sender == room->localUser()) return;
|
if (sender == room->localUser())
|
||||||
emit newMessage(
|
return;
|
||||||
room->id(), lastEvent->id(), room->displayName(),
|
emit newMessage(room->id(), lastEvent->id(), room->displayName(),
|
||||||
sender->displayname(), utils::eventToString(*lastEvent),
|
sender->displayname(), room->eventToString(*lastEvent),
|
||||||
room->avatar(128));
|
room->avatar(128));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -129,7 +144,8 @@ void RoomListModel::updateRoom(Room* room, Room* prev) {
|
||||||
void RoomListModel::deleteRoom(Room* room) {
|
void RoomListModel::deleteRoom(Room* room) {
|
||||||
qDebug() << "Deleting room" << room->id();
|
qDebug() << "Deleting room" << room->id();
|
||||||
const auto it = std::find(m_rooms.begin(), m_rooms.end(), room);
|
const auto it = std::find(m_rooms.begin(), m_rooms.end(), room);
|
||||||
if (it == m_rooms.end()) return; // Already deleted, nothing to do
|
if (it == m_rooms.end())
|
||||||
|
return; // Already deleted, nothing to do
|
||||||
qDebug() << "Erasing room" << room->id();
|
qDebug() << "Erasing room" << room->id();
|
||||||
const int row = it - m_rooms.begin();
|
const int row = it - m_rooms.begin();
|
||||||
beginRemoveRows(QModelIndex(), row, row);
|
beginRemoveRows(QModelIndex(), row, row);
|
||||||
|
@ -138,34 +154,54 @@ void RoomListModel::deleteRoom(Room* room) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int RoomListModel::rowCount(const QModelIndex& parent) const {
|
int RoomListModel::rowCount(const QModelIndex& parent) const {
|
||||||
if (parent.isValid()) return 0;
|
if (parent.isValid())
|
||||||
|
return 0;
|
||||||
return m_rooms.count();
|
return m_rooms.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant RoomListModel::data(const QModelIndex& index, int role) const {
|
QVariant RoomListModel::data(const QModelIndex& index, int role) const {
|
||||||
if (!index.isValid()) return QVariant();
|
if (!index.isValid())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
if (index.row() >= m_rooms.count()) {
|
if (index.row() >= m_rooms.count()) {
|
||||||
qDebug() << "UserListModel: something wrong here...";
|
qDebug() << "UserListModel: something wrong here...";
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
SpectralRoom* room = m_rooms.at(index.row());
|
SpectralRoom* room = m_rooms.at(index.row());
|
||||||
if (role == NameRole) return room->displayName();
|
if (role == NameRole)
|
||||||
if (role == AvatarRole) return room->avatarMediaId();
|
return room->displayName();
|
||||||
if (role == TopicRole) return room->topic();
|
if (role == AvatarRole)
|
||||||
|
return room->avatarMediaId();
|
||||||
|
if (role == TopicRole)
|
||||||
|
return room->topic();
|
||||||
if (role == CategoryRole) {
|
if (role == CategoryRole) {
|
||||||
if (room->joinState() == JoinState::Invite) return RoomType::Invited;
|
if (room->joinState() == JoinState::Invite)
|
||||||
if (room->isFavourite()) return RoomType::Favorite;
|
return RoomType::Invited;
|
||||||
if (room->isDirectChat()) return RoomType::Direct;
|
if (room->isFavourite())
|
||||||
if (room->isLowPriority()) return RoomType::Deprioritized;
|
return RoomType::Favorite;
|
||||||
|
if (room->isDirectChat())
|
||||||
|
return RoomType::Direct;
|
||||||
|
if (room->isLowPriority())
|
||||||
|
return RoomType::Deprioritized;
|
||||||
return RoomType::Normal;
|
return RoomType::Normal;
|
||||||
}
|
}
|
||||||
if (role == UnreadCountRole) return room->unreadCount();
|
if (role == UnreadCountRole)
|
||||||
if (role == NotificationCountRole) return room->notificationCount();
|
return room->unreadCount();
|
||||||
if (role == HighlightCountRole) return room->highlightCount();
|
if (role == NotificationCountRole)
|
||||||
if (role == LastEventRole) return room->lastEvent();
|
return room->notificationCount();
|
||||||
if (role == LastActiveTimeRole) return room->lastActiveTime();
|
if (role == HighlightCountRole)
|
||||||
if (role == CurrentRoomRole) return QVariant::fromValue(room);
|
return room->highlightCount();
|
||||||
|
if (role == LastEventRole)
|
||||||
|
return room->lastEvent();
|
||||||
|
if (role == LastActiveTimeRole)
|
||||||
|
return room->lastActiveTime();
|
||||||
|
if (role == JoinStateRole) {
|
||||||
|
if (!room->successorId().isEmpty())
|
||||||
|
return QStringLiteral("upgraded");
|
||||||
|
return toCString(room->joinState());
|
||||||
|
}
|
||||||
|
if (role == CurrentRoomRole)
|
||||||
|
return QVariant::fromValue(room);
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,6 +236,7 @@ QHash<int, QByteArray> RoomListModel::roleNames() const {
|
||||||
roles[HighlightCountRole] = "highlightCount";
|
roles[HighlightCountRole] = "highlightCount";
|
||||||
roles[LastEventRole] = "lastEvent";
|
roles[LastEventRole] = "lastEvent";
|
||||||
roles[LastActiveTimeRole] = "lastActiveTime";
|
roles[LastActiveTimeRole] = "lastActiveTime";
|
||||||
|
roles[JoinStateRole] = "joinState";
|
||||||
roles[CurrentRoomRole] = "currentRoom";
|
roles[CurrentRoomRole] = "currentRoom";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ class RoomType : public QObject {
|
||||||
enum Types {
|
enum Types {
|
||||||
Invited = 1,
|
Invited = 1,
|
||||||
Favorite,
|
Favorite,
|
||||||
Normal,
|
|
||||||
Direct,
|
Direct,
|
||||||
|
Normal,
|
||||||
Deprioritized,
|
Deprioritized,
|
||||||
};
|
};
|
||||||
REGISTER_ENUM(Types)
|
REGISTER_ENUM(Types)
|
||||||
|
@ -39,6 +39,7 @@ class RoomListModel : public QAbstractListModel {
|
||||||
HighlightCountRole,
|
HighlightCountRole,
|
||||||
LastEventRole,
|
LastEventRole,
|
||||||
LastActiveTimeRole,
|
LastActiveTimeRole,
|
||||||
|
JoinStateRole,
|
||||||
CurrentRoomRole,
|
CurrentRoomRole,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -75,9 +76,12 @@ class RoomListModel : public QAbstractListModel {
|
||||||
signals:
|
signals:
|
||||||
void connectionChanged();
|
void connectionChanged();
|
||||||
void roomAdded(SpectralRoom* room);
|
void roomAdded(SpectralRoom* room);
|
||||||
void newMessage(const QString& roomId, const QString& eventId,
|
void newMessage(const QString& roomId,
|
||||||
const QString& roomName, const QString& senderName,
|
const QString& eventId,
|
||||||
const QString& text, const QImage& icon);
|
const QString& roomName,
|
||||||
|
const QString& senderName,
|
||||||
|
const QString& text,
|
||||||
|
const QImage& icon);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ROOMLISTMODEL_H
|
#endif // ROOMLISTMODEL_H
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "csapi/content-repo.h"
|
#include "csapi/content-repo.h"
|
||||||
#include "csapi/leaving.h"
|
#include "csapi/leaving.h"
|
||||||
#include "csapi/typing.h"
|
#include "csapi/typing.h"
|
||||||
|
#include "events/accountdataevents.h"
|
||||||
#include "events/typingevent.h"
|
#include "events/typingevent.h"
|
||||||
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
@ -18,7 +19,8 @@
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
SpectralRoom::SpectralRoom(Connection* connection, QString roomId,
|
SpectralRoom::SpectralRoom(Connection* connection,
|
||||||
|
QString roomId,
|
||||||
JoinState joinState)
|
JoinState joinState)
|
||||||
: Room(connection, std::move(roomId), joinState) {
|
: Room(connection, std::move(roomId), joinState) {
|
||||||
connect(this, &SpectralRoom::notificationCountChanged, this,
|
connect(this, &SpectralRoom::notificationCountChanged, this,
|
||||||
|
@ -70,21 +72,21 @@ void SpectralRoom::chooseAndUploadFile() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpectralRoom::saveFileAs(QString eventId) {
|
void SpectralRoom::acceptInvitation() {
|
||||||
auto fileName = QFileDialog::getSaveFileName(Q_NULLPTR, tr("Save File as"),
|
connection()->joinRoom(id());
|
||||||
fileNameToDownload(eventId));
|
|
||||||
if (!fileName.isEmpty()) downloadFile(eventId, QUrl::fromLocalFile(fileName));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpectralRoom::acceptInvitation() { connection()->joinRoom(id()); }
|
void SpectralRoom::forget() {
|
||||||
|
connection()->forgetRoom(id());
|
||||||
void SpectralRoom::forget() { connection()->forgetRoom(id()); }
|
}
|
||||||
|
|
||||||
bool SpectralRoom::hasUsersTyping() {
|
bool SpectralRoom::hasUsersTyping() {
|
||||||
QList<User*> users = usersTyping();
|
QList<User*> users = usersTyping();
|
||||||
if (users.isEmpty()) return false;
|
if (users.isEmpty())
|
||||||
|
return false;
|
||||||
int count = users.length();
|
int count = users.length();
|
||||||
if (users.contains(localUser())) count--;
|
if (users.contains(localUser()))
|
||||||
|
count--;
|
||||||
return count != 0;
|
return count != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,10 +106,11 @@ void SpectralRoom::sendTypingNotification(bool isTyping) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SpectralRoom::lastEvent() {
|
QString SpectralRoom::lastEvent() {
|
||||||
if (timelineSize() == 0) return "";
|
if (timelineSize() == 0)
|
||||||
|
return "";
|
||||||
const RoomEvent* lastEvent = messageEvents().rbegin()->get();
|
const RoomEvent* lastEvent = messageEvents().rbegin()->get();
|
||||||
return user(lastEvent->senderId())->displayname() + ": " +
|
return user(lastEvent->senderId())->displayname() + ": " +
|
||||||
utils::removeReply(utils::eventToString(*lastEvent, this));
|
utils::removeReply(eventToString(*lastEvent));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SpectralRoom::isEventHighlighted(const RoomEvent* e) const {
|
bool SpectralRoom::isEventHighlighted(const RoomEvent* e) const {
|
||||||
|
@ -116,7 +119,8 @@ bool SpectralRoom::isEventHighlighted(const RoomEvent* e) const {
|
||||||
|
|
||||||
void SpectralRoom::checkForHighlights(const QMatrixClient::TimelineItem& ti) {
|
void SpectralRoom::checkForHighlights(const QMatrixClient::TimelineItem& ti) {
|
||||||
auto localUserId = localUser()->id();
|
auto localUserId = localUser()->id();
|
||||||
if (ti->senderId() == localUserId) return;
|
if (ti->senderId() == localUserId)
|
||||||
|
return;
|
||||||
if (auto* e = ti.viewAs<RoomMessageEvent>()) {
|
if (auto* e = ti.viewAs<RoomMessageEvent>()) {
|
||||||
const auto& text = e->plainBody();
|
const auto& text = e->plainBody();
|
||||||
if (text.contains(localUserId) ||
|
if (text.contains(localUserId) ||
|
||||||
|
@ -142,8 +146,10 @@ void SpectralRoom::countChanged() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpectralRoom::sendReply(QString userId, QString eventId,
|
void SpectralRoom::sendReply(QString userId,
|
||||||
QString replyContent, QString sendContent) {
|
QString eventId,
|
||||||
|
QString replyContent,
|
||||||
|
QString sendContent) {
|
||||||
QJsonObject json{
|
QJsonObject json{
|
||||||
{"msgtype", "m.text"},
|
{"msgtype", "m.text"},
|
||||||
{"body", "> <" + userId + "> " + replyContent + "\n\n" + sendContent},
|
{"body", "> <" + userId + "> " + replyContent + "\n\n" + sendContent},
|
||||||
|
@ -159,7 +165,8 @@ void SpectralRoom::sendReply(QString userId, QString eventId,
|
||||||
}
|
}
|
||||||
|
|
||||||
QDateTime SpectralRoom::lastActiveTime() {
|
QDateTime SpectralRoom::lastActiveTime() {
|
||||||
if (timelineSize() == 0) return QDateTime();
|
if (timelineSize() == 0)
|
||||||
|
return QDateTime();
|
||||||
return messageEvents().rbegin()->get()->timestamp();
|
return messageEvents().rbegin()->get()->timestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,15 +212,19 @@ QVariantList SpectralRoom::getUsers(const QString& prefix) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SpectralRoom::postMarkdownText(const QString& markdown) {
|
QString SpectralRoom::postMarkdownText(const QString& markdown) {
|
||||||
unsigned char *sequence = (unsigned char *) qstrdup(markdown.toUtf8().constData());
|
unsigned char* sequence =
|
||||||
qint64 length = strlen((char *) sequence);
|
(unsigned char*)qstrdup(markdown.toUtf8().constData());
|
||||||
|
qint64 length = strlen((char*)sequence);
|
||||||
|
|
||||||
hoedown_renderer* renderer = hoedown_html_renderer_new(HOEDOWN_HTML_USE_XHTML, 32);
|
hoedown_renderer* renderer =
|
||||||
hoedown_extensions extensions = (hoedown_extensions) ((HOEDOWN_EXT_BLOCK | HOEDOWN_EXT_SPAN | HOEDOWN_EXT_MATH_EXPLICIT) & ~HOEDOWN_EXT_QUOTE);
|
hoedown_html_renderer_new(HOEDOWN_HTML_USE_XHTML, 32);
|
||||||
|
hoedown_extensions extensions = (hoedown_extensions)(
|
||||||
|
(HOEDOWN_EXT_BLOCK | HOEDOWN_EXT_SPAN | HOEDOWN_EXT_MATH_EXPLICIT) &
|
||||||
|
~HOEDOWN_EXT_QUOTE);
|
||||||
hoedown_document* document = hoedown_document_new(renderer, extensions, 32);
|
hoedown_document* document = hoedown_document_new(renderer, extensions, 32);
|
||||||
hoedown_buffer* html = hoedown_buffer_new(length);
|
hoedown_buffer* html = hoedown_buffer_new(length);
|
||||||
hoedown_document_render(document, html, sequence, length);
|
hoedown_document_render(document, html, sequence, length);
|
||||||
QString result = QString::fromUtf8((char *) html->data, html->size);
|
QString result = QString::fromUtf8((char*)html->data, html->size);
|
||||||
|
|
||||||
free(sequence);
|
free(sequence);
|
||||||
hoedown_buffer_free(html);
|
hoedown_buffer_free(html);
|
||||||
|
|
|
@ -8,6 +8,13 @@
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include <events/redactionevent.h>
|
||||||
|
#include <events/roomavatarevent.h>
|
||||||
|
#include <events/roomcreateevent.h>
|
||||||
|
#include <events/roommemberevent.h>
|
||||||
|
#include <events/roommessageevent.h>
|
||||||
|
#include <events/simplestateevents.h>
|
||||||
|
|
||||||
using namespace QMatrixClient;
|
using namespace QMatrixClient;
|
||||||
|
|
||||||
class SpectralRoom : public Room {
|
class SpectralRoom : public Room {
|
||||||
|
@ -23,7 +30,8 @@ class SpectralRoom : public Room {
|
||||||
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
|
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SpectralRoom(Connection* connection, QString roomId,
|
explicit SpectralRoom(Connection* connection,
|
||||||
|
QString roomId,
|
||||||
JoinState joinState = {});
|
JoinState joinState = {});
|
||||||
|
|
||||||
const QString& cachedInput() const { return m_cachedInput; }
|
const QString& cachedInput() const { return m_cachedInput; }
|
||||||
|
@ -76,6 +84,148 @@ class SpectralRoom : public Room {
|
||||||
|
|
||||||
Q_INVOKABLE QString postMarkdownText(const QString& markdown);
|
Q_INVOKABLE QString postMarkdownText(const QString& markdown);
|
||||||
|
|
||||||
|
template <typename BaseEventT>
|
||||||
|
QString eventToString(const BaseEventT& evt,
|
||||||
|
Qt::TextFormat format = Qt::PlainText) {
|
||||||
|
bool prettyPrint = (format == Qt::RichText);
|
||||||
|
|
||||||
|
using namespace QMatrixClient;
|
||||||
|
return visit(
|
||||||
|
evt,
|
||||||
|
[this, prettyPrint](const RoomMessageEvent& e) {
|
||||||
|
using namespace MessageEventContent;
|
||||||
|
|
||||||
|
if (prettyPrint && e.hasTextContent() &&
|
||||||
|
e.mimeType().name() != "text/plain")
|
||||||
|
return static_cast<const TextContent*>(e.content())->body;
|
||||||
|
if (e.hasFileContent()) {
|
||||||
|
auto fileCaption =
|
||||||
|
e.content()->fileInfo()->originalName.toHtmlEscaped();
|
||||||
|
if (fileCaption.isEmpty()) {
|
||||||
|
if (prettyPrint)
|
||||||
|
fileCaption = this->prettyPrint(e.plainBody());
|
||||||
|
else
|
||||||
|
fileCaption = e.plainBody();
|
||||||
|
}
|
||||||
|
return !fileCaption.isEmpty() ? fileCaption : tr("a file");
|
||||||
|
}
|
||||||
|
return prettyPrint ? this->prettyPrint(e.plainBody()) : e.plainBody();
|
||||||
|
},
|
||||||
|
[this](const RoomMemberEvent& e) {
|
||||||
|
// FIXME: Rewind to the name that was at the time of this event
|
||||||
|
auto subjectName = this->user(e.userId())->displayname();
|
||||||
|
// The below code assumes senderName output in AuthorRole
|
||||||
|
switch (e.membership()) {
|
||||||
|
case MembershipType::Invite:
|
||||||
|
if (e.repeatsState())
|
||||||
|
return tr("reinvited %1 to the room").arg(subjectName);
|
||||||
|
FALLTHROUGH;
|
||||||
|
case MembershipType::Join: {
|
||||||
|
if (e.repeatsState())
|
||||||
|
return tr("joined the room (repeated)");
|
||||||
|
if (!e.prevContent() ||
|
||||||
|
e.membership() != e.prevContent()->membership) {
|
||||||
|
return e.membership() == MembershipType::Invite
|
||||||
|
? tr("invited %1 to the room").arg(subjectName)
|
||||||
|
: tr("joined the room");
|
||||||
|
}
|
||||||
|
QString text{};
|
||||||
|
if (e.isRename()) {
|
||||||
|
if (e.displayName().isEmpty())
|
||||||
|
text = tr("cleared the display name");
|
||||||
|
else
|
||||||
|
text = tr("changed the display name to %1")
|
||||||
|
.arg(e.displayName().toHtmlEscaped());
|
||||||
|
}
|
||||||
|
if (e.isAvatarUpdate()) {
|
||||||
|
if (!text.isEmpty())
|
||||||
|
text += " and ";
|
||||||
|
if (e.avatarUrl().isEmpty())
|
||||||
|
text += tr("cleared the avatar");
|
||||||
|
else
|
||||||
|
text += tr("updated the avatar");
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
case MembershipType::Leave:
|
||||||
|
if (e.prevContent() &&
|
||||||
|
e.prevContent()->membership == MembershipType::Invite) {
|
||||||
|
return (e.senderId() != e.userId())
|
||||||
|
? tr("withdrew %1's invitation").arg(subjectName)
|
||||||
|
: tr("rejected the invitation");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.prevContent() &&
|
||||||
|
e.prevContent()->membership == MembershipType::Ban) {
|
||||||
|
return (e.senderId() != e.userId())
|
||||||
|
? tr("unbanned %1").arg(subjectName)
|
||||||
|
: tr("self-unbanned");
|
||||||
|
}
|
||||||
|
return (e.senderId() != e.userId())
|
||||||
|
? tr("has put %1 out of the room: %2")
|
||||||
|
.arg(subjectName, e.contentJson()["reason"_ls]
|
||||||
|
.toString()
|
||||||
|
.toHtmlEscaped())
|
||||||
|
: tr("left the room");
|
||||||
|
case MembershipType::Ban:
|
||||||
|
return (e.senderId() != e.userId())
|
||||||
|
? tr("banned %1 from the room: %2")
|
||||||
|
.arg(subjectName, e.contentJson()["reason"_ls]
|
||||||
|
.toString()
|
||||||
|
.toHtmlEscaped())
|
||||||
|
: tr("self-banned from the room");
|
||||||
|
case MembershipType::Knock:
|
||||||
|
return tr("knocked");
|
||||||
|
default:;
|
||||||
|
}
|
||||||
|
return tr("made something unknown");
|
||||||
|
},
|
||||||
|
[](const RoomAliasesEvent& e) {
|
||||||
|
return tr("has set room aliases on server %1 to: %2")
|
||||||
|
.arg(e.stateKey(), QLocale().createSeparatedList(e.aliases()));
|
||||||
|
},
|
||||||
|
[](const RoomCanonicalAliasEvent& e) {
|
||||||
|
return (e.alias().isEmpty())
|
||||||
|
? tr("cleared the room main alias")
|
||||||
|
: tr("set the room main alias to: %1").arg(e.alias());
|
||||||
|
},
|
||||||
|
[](const RoomNameEvent& e) {
|
||||||
|
return (e.name().isEmpty()) ? tr("cleared the room name")
|
||||||
|
: tr("set the room name to: %1")
|
||||||
|
.arg(e.name().toHtmlEscaped());
|
||||||
|
},
|
||||||
|
[this, prettyPrint](const RoomTopicEvent& e) {
|
||||||
|
return (e.topic().isEmpty())
|
||||||
|
? tr("cleared the topic")
|
||||||
|
: tr("set the topic to: %1")
|
||||||
|
.arg(prettyPrint ? this->prettyPrint(e.topic())
|
||||||
|
: e.topic());
|
||||||
|
},
|
||||||
|
[](const RoomAvatarEvent&) { return tr("changed the room avatar"); },
|
||||||
|
[](const EncryptionEvent&) {
|
||||||
|
return tr("activated End-to-End Encryption");
|
||||||
|
},
|
||||||
|
[](const RoomCreateEvent& e) {
|
||||||
|
return (e.isUpgrade() ? tr("upgraded the room to version %1")
|
||||||
|
: tr("created the room, version %1"))
|
||||||
|
.arg(e.version().isEmpty() ? "1" : e.version().toHtmlEscaped());
|
||||||
|
},
|
||||||
|
[](const StateEventBase& e) {
|
||||||
|
// A small hack for state events from TWIM bot
|
||||||
|
return e.stateKey() == "twim"
|
||||||
|
? tr("updated the database",
|
||||||
|
"TWIM bot updated the database")
|
||||||
|
: e.stateKey().isEmpty()
|
||||||
|
? tr("updated %1 state", "%1 - Matrix event type")
|
||||||
|
.arg(e.matrixType())
|
||||||
|
: tr("updated %1 state for %2",
|
||||||
|
"%1 - Matrix event type, %2 - state key")
|
||||||
|
.arg(e.matrixType(),
|
||||||
|
e.stateKey().toHtmlEscaped());
|
||||||
|
},
|
||||||
|
tr("Unknown event"));
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_cachedInput;
|
QString m_cachedInput;
|
||||||
QSet<const QMatrixClient::RoomEvent*> highlights;
|
QSet<const QMatrixClient::RoomEvent*> highlights;
|
||||||
|
@ -101,11 +251,12 @@ class SpectralRoom : public Room {
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void chooseAndUploadFile();
|
void chooseAndUploadFile();
|
||||||
void saveFileAs(QString eventId);
|
|
||||||
void acceptInvitation();
|
void acceptInvitation();
|
||||||
void forget();
|
void forget();
|
||||||
void sendTypingNotification(bool isTyping);
|
void sendTypingNotification(bool isTyping);
|
||||||
void sendReply(QString userId, QString eventId, QString replyContent,
|
void sendReply(QString userId,
|
||||||
|
QString eventId,
|
||||||
|
QString replyContent,
|
||||||
QString sendContent);
|
QString sendContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,16 @@ UserListModel::UserListModel(QObject* parent)
|
||||||
: QAbstractListModel(parent), m_currentRoom(nullptr) {}
|
: QAbstractListModel(parent), m_currentRoom(nullptr) {}
|
||||||
|
|
||||||
void UserListModel::setRoom(QMatrixClient::Room* room) {
|
void UserListModel::setRoom(QMatrixClient::Room* room) {
|
||||||
if (m_currentRoom == room) return;
|
if (m_currentRoom == room)
|
||||||
|
return;
|
||||||
|
|
||||||
using namespace QMatrixClient;
|
using namespace QMatrixClient;
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
if (m_currentRoom) {
|
if (m_currentRoom) {
|
||||||
m_currentRoom->disconnect(this);
|
m_currentRoom->disconnect(this);
|
||||||
// m_currentRoom->connection()->disconnect(this);
|
// m_currentRoom->connection()->disconnect(this);
|
||||||
for (User* user : m_users) user->disconnect(this);
|
for (User* user : m_users)
|
||||||
|
user->disconnect(this);
|
||||||
m_users.clear();
|
m_users.clear();
|
||||||
}
|
}
|
||||||
m_currentRoom = room;
|
m_currentRoom = room;
|
||||||
|
@ -49,12 +51,14 @@ void UserListModel::setRoom(QMatrixClient::Room* room) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QMatrixClient::User* UserListModel::userAt(QModelIndex index) {
|
QMatrixClient::User* UserListModel::userAt(QModelIndex index) {
|
||||||
if (index.row() < 0 || index.row() >= m_users.size()) return nullptr;
|
if (index.row() < 0 || index.row() >= m_users.size())
|
||||||
|
return nullptr;
|
||||||
return m_users.at(index.row());
|
return m_users.at(index.row());
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant UserListModel::data(const QModelIndex& index, int role) const {
|
QVariant UserListModel::data(const QModelIndex& index, int role) const {
|
||||||
if (!index.isValid()) return QVariant();
|
if (!index.isValid())
|
||||||
|
return QVariant();
|
||||||
|
|
||||||
if (index.row() >= m_users.count()) {
|
if (index.row() >= m_users.count()) {
|
||||||
qDebug()
|
qDebug()
|
||||||
|
@ -71,12 +75,16 @@ QVariant UserListModel::data(const QModelIndex& index, int role) const {
|
||||||
if (role == AvatarRole) {
|
if (role == AvatarRole) {
|
||||||
return user->avatarMediaId();
|
return user->avatarMediaId();
|
||||||
}
|
}
|
||||||
|
if (role == ObjectRole) {
|
||||||
|
return QVariant::fromValue(user);
|
||||||
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
int UserListModel::rowCount(const QModelIndex& parent) const {
|
int UserListModel::rowCount(const QModelIndex& parent) const {
|
||||||
if (parent.isValid()) return 0;
|
if (parent.isValid())
|
||||||
|
return 0;
|
||||||
|
|
||||||
return m_users.count();
|
return m_users.count();
|
||||||
}
|
}
|
||||||
|
@ -111,7 +119,8 @@ void UserListModel::refresh(QMatrixClient::User* user, QVector<int> roles) {
|
||||||
|
|
||||||
void UserListModel::avatarChanged(QMatrixClient::User* user,
|
void UserListModel::avatarChanged(QMatrixClient::User* user,
|
||||||
const QMatrixClient::Room* context) {
|
const QMatrixClient::Room* context) {
|
||||||
if (context == m_currentRoom) refresh(user, {AvatarRole});
|
if (context == m_currentRoom)
|
||||||
|
refresh(user, {AvatarRole});
|
||||||
}
|
}
|
||||||
|
|
||||||
int UserListModel::findUserPos(User* user) const {
|
int UserListModel::findUserPos(User* user) const {
|
||||||
|
@ -127,5 +136,6 @@ QHash<int, QByteArray> UserListModel::roleNames() const {
|
||||||
roles[NameRole] = "name";
|
roles[NameRole] = "name";
|
||||||
roles[UserIDRole] = "userId";
|
roles[UserIDRole] = "userId";
|
||||||
roles[AvatarRole] = "avatar";
|
roles[AvatarRole] = "avatar";
|
||||||
|
roles[ObjectRole] = "user";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,12 @@ class UserListModel : public QAbstractListModel {
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
QMatrixClient::Room* room READ room WRITE setRoom NOTIFY roomChanged)
|
QMatrixClient::Room* room READ room WRITE setRoom NOTIFY roomChanged)
|
||||||
public:
|
public:
|
||||||
enum EventRoles { NameRole = Qt::UserRole + 1, UserIDRole, AvatarRole };
|
enum EventRoles {
|
||||||
|
NameRole = Qt::UserRole + 1,
|
||||||
|
UserIDRole,
|
||||||
|
AvatarRole,
|
||||||
|
ObjectRole
|
||||||
|
};
|
||||||
|
|
||||||
using User = QMatrixClient::User;
|
using User = QMatrixClient::User;
|
||||||
|
|
||||||
|
|
114
src/utils.h
|
@ -26,120 +26,6 @@ static const QRegularExpression userPillRegExp{
|
||||||
|
|
||||||
QString removeReply(const QString& text);
|
QString removeReply(const QString& text);
|
||||||
QString cleanHTML(const QString& text, QMatrixClient::Room* room);
|
QString cleanHTML(const QString& text, QMatrixClient::Room* room);
|
||||||
|
|
||||||
template <typename BaseEventT>
|
|
||||||
QString eventToString(const BaseEventT& evt,
|
|
||||||
QMatrixClient::Room* room = nullptr,
|
|
||||||
Qt::TextFormat format = Qt::PlainText) {
|
|
||||||
bool prettyPrint = (format == Qt::RichText);
|
|
||||||
|
|
||||||
using namespace QMatrixClient;
|
|
||||||
return visit(
|
|
||||||
evt,
|
|
||||||
[room, prettyPrint](const RoomMessageEvent& e) {
|
|
||||||
using namespace MessageEventContent;
|
|
||||||
|
|
||||||
if (prettyPrint && e.hasTextContent() &&
|
|
||||||
e.mimeType().name() != "text/plain") {
|
|
||||||
return cleanHTML(static_cast<const TextContent*>(e.content())->body,
|
|
||||||
room);
|
|
||||||
}
|
|
||||||
if (e.hasFileContent()) {
|
|
||||||
auto fileCaption = e.content()->fileInfo()->originalName;
|
|
||||||
if (fileCaption.isEmpty())
|
|
||||||
fileCaption = prettyPrint && room ? room->prettyPrint(e.plainBody())
|
|
||||||
: e.plainBody();
|
|
||||||
if (fileCaption.isEmpty()) return QObject::tr("a file");
|
|
||||||
}
|
|
||||||
return prettyPrint && room ? room->prettyPrint(e.plainBody())
|
|
||||||
: e.plainBody();
|
|
||||||
},
|
|
||||||
[room](const RoomMemberEvent& e) {
|
|
||||||
// FIXME: Rewind to the name that was at the time of this event
|
|
||||||
QString subjectName =
|
|
||||||
room ? room->roomMembername(e.userId()) : e.userId();
|
|
||||||
// The below code assumes senderName output in AuthorRole
|
|
||||||
switch (e.membership()) {
|
|
||||||
case MembershipType::Invite:
|
|
||||||
if (e.repeatsState())
|
|
||||||
return QObject::tr("reinvited %1 to the room").arg(subjectName);
|
|
||||||
FALLTHROUGH;
|
|
||||||
case MembershipType::Join: {
|
|
||||||
if (e.repeatsState())
|
|
||||||
return QObject::tr("joined the room (repeated)");
|
|
||||||
if (!e.prevContent() ||
|
|
||||||
e.membership() != e.prevContent()->membership) {
|
|
||||||
return e.membership() == MembershipType::Invite
|
|
||||||
? QObject::tr("invited %1 to the room")
|
|
||||||
.arg(subjectName)
|
|
||||||
: QObject::tr("joined the room");
|
|
||||||
}
|
|
||||||
QString text{};
|
|
||||||
if (e.isRename()) {
|
|
||||||
if (e.displayName().isEmpty())
|
|
||||||
text = QObject::tr("cleared their display name");
|
|
||||||
else
|
|
||||||
text = QObject::tr("changed their display name to %1")
|
|
||||||
.arg(e.displayName());
|
|
||||||
}
|
|
||||||
if (e.isAvatarUpdate()) {
|
|
||||||
if (!text.isEmpty()) text += " and ";
|
|
||||||
if (e.avatarUrl().isEmpty())
|
|
||||||
text += QObject::tr("cleared the avatar");
|
|
||||||
else
|
|
||||||
text += QObject::tr("updated the avatar");
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
case MembershipType::Leave:
|
|
||||||
if (e.prevContent() &&
|
|
||||||
e.prevContent()->membership == MembershipType::Ban) {
|
|
||||||
return (e.senderId() != e.userId())
|
|
||||||
? QObject::tr("unbanned %1").arg(subjectName)
|
|
||||||
: QObject::tr("self-unbanned");
|
|
||||||
}
|
|
||||||
return (e.senderId() != e.userId())
|
|
||||||
? QObject::tr("has kicked %1 from the room")
|
|
||||||
.arg(subjectName)
|
|
||||||
: QObject::tr("left the room");
|
|
||||||
case MembershipType::Ban:
|
|
||||||
return (e.senderId() != e.userId())
|
|
||||||
? QObject::tr("banned %1 from the room ")
|
|
||||||
.arg(subjectName)
|
|
||||||
: QObject::tr(" self-banned from the room ");
|
|
||||||
case MembershipType::Knock:
|
|
||||||
return QObject::tr("knocked");
|
|
||||||
default:;
|
|
||||||
}
|
|
||||||
return QObject::tr("made something unknown");
|
|
||||||
},
|
|
||||||
[](const RoomAliasesEvent& e) {
|
|
||||||
return QObject::tr("set aliases to: %1").arg(e.aliases().join(","));
|
|
||||||
},
|
|
||||||
[](const RoomCanonicalAliasEvent& e) {
|
|
||||||
return (e.alias().isEmpty())
|
|
||||||
? QObject::tr("cleared the room main alias")
|
|
||||||
: QObject::tr("set the room main alias to: %1")
|
|
||||||
.arg(e.alias());
|
|
||||||
},
|
|
||||||
[](const RoomNameEvent& e) {
|
|
||||||
return (e.name().isEmpty())
|
|
||||||
? QObject::tr("cleared the room name")
|
|
||||||
: QObject::tr("set the room name to: %1").arg(e.name());
|
|
||||||
},
|
|
||||||
[](const RoomTopicEvent& e) {
|
|
||||||
return (e.topic().isEmpty())
|
|
||||||
? QObject::tr("cleared the topic")
|
|
||||||
: QObject::tr("set the topic to: %1").arg(e.topic());
|
|
||||||
},
|
|
||||||
[](const RoomAvatarEvent&) {
|
|
||||||
return QObject::tr("changed the room avatar");
|
|
||||||
},
|
|
||||||
[](const EncryptionEvent&) {
|
|
||||||
return QObject::tr("activated End-to-End Encryption");
|
|
||||||
},
|
|
||||||
QObject::tr("Unknown Event"));
|
|
||||||
};
|
|
||||||
} // namespace utils
|
} // namespace utils
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|