Compare commits
188 Commits
v2.7.0-bet
...
v2.6.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d745535f7 | ||
|
|
3184aeed43 | ||
|
|
3faf010b55 | ||
|
|
163c80e203 | ||
|
|
73462e97aa | ||
|
|
6f4144a464 | ||
|
|
9820464545 | ||
|
|
3ecd7823f9 | ||
|
|
9b504eaddd | ||
|
|
838ca6cba0 | ||
|
|
7e5e40d5c4 | ||
|
|
abd8d1fda1 | ||
|
|
0a3491f332 | ||
|
|
15d9ca2b00 | ||
|
|
7682749415 | ||
|
|
a18fc5b5c9 | ||
|
|
96783a9b80 | ||
|
|
258c2cee2e | ||
|
|
65ff3c0de1 | ||
|
|
6879c9f1c9 | ||
|
|
fade8465e4 | ||
|
|
76b5c6b6d4 | ||
|
|
bdd0cc4dc3 | ||
|
|
876b1e239e | ||
|
|
da2007c7f6 | ||
|
|
7a33cb97cd | ||
|
|
7a18a58fae | ||
|
|
fcc9d02bcc | ||
|
|
98238669fe | ||
|
|
a80de38517 | ||
|
|
4cb75d91f9 | ||
|
|
55e4dceeb7 | ||
|
|
0006b35abf | ||
|
|
b153391cbf | ||
|
|
c0659a3124 | ||
|
|
de8a7aa680 | ||
|
|
03b11693a3 | ||
|
|
23f2d79f70 | ||
|
|
f6db365391 | ||
|
|
95f74ceb1f | ||
|
|
91be4961d7 | ||
|
|
b3eb16bfd3 | ||
|
|
52d91ce198 | ||
|
|
c49efcc137 | ||
|
|
c14556153f | ||
|
|
5e1a2a423f | ||
|
|
3ac1ba079a | ||
|
|
b7a9cd1d45 | ||
|
|
c7b0ce036d | ||
|
|
f5afa07b0a | ||
|
|
1b550b976f | ||
|
|
68f0b9e0aa | ||
|
|
8945ef2652 | ||
|
|
0cc74d21a2 | ||
|
|
f4c79c8f68 | ||
|
|
b6ef8edb12 | ||
|
|
06ffd3e841 | ||
|
|
a3596b80e9 | ||
|
|
8ac1d92161 | ||
|
|
d55f1d5c06 | ||
|
|
854c637c73 | ||
|
|
92ad0c4a43 | ||
|
|
fb8facb787 | ||
|
|
30832cc427 | ||
|
|
ab7f29c0b2 | ||
|
|
6500442217 | ||
|
|
0e6a9fd0c1 | ||
|
|
ed7c489722 | ||
|
|
d4b9b5129a | ||
|
|
4a79e24db2 | ||
|
|
215d185fbe | ||
|
|
6f273bf7dd | ||
|
|
f2bc25c9ca | ||
|
|
ca6de6128f | ||
|
|
a1e5e6ca40 | ||
|
|
cf8bb1c5bc | ||
|
|
ebe1cef357 | ||
|
|
922d14f016 | ||
|
|
7a0c6a2f8f | ||
|
|
f35a2c0b2c | ||
|
|
c6b22089e3 | ||
|
|
cf0082f92f | ||
|
|
f4e129d4e2 | ||
|
|
ec6eaf2121 | ||
|
|
1dc1f91620 | ||
|
|
5adbc01ef1 | ||
|
|
c4a04bfd05 | ||
|
|
03711429f4 | ||
|
|
e3cb040c3c | ||
|
|
83a21179fa | ||
|
|
829da85aa5 | ||
|
|
3b1ac89312 | ||
|
|
57df29e7c9 | ||
|
|
d774067004 | ||
|
|
728154386c | ||
|
|
bc31182c63 | ||
|
|
a6bb84080a | ||
|
|
576ba7c011 | ||
|
|
b6893aad16 | ||
|
|
59d1624ce5 | ||
|
|
a0faf1f54d | ||
|
|
caa7c845c2 | ||
|
|
e43a80d0be | ||
|
|
f060a92563 | ||
|
|
483696261d | ||
|
|
610e35ec64 | ||
|
|
5fa5526ea2 | ||
|
|
42d9d99a92 | ||
|
|
c3ff9ca917 | ||
|
|
875f123d5b | ||
|
|
a5f053afe4 | ||
|
|
63a6992f97 | ||
|
|
54740378f0 | ||
|
|
1dc443bc06 | ||
|
|
e541109d7c | ||
|
|
993f124120 | ||
|
|
0a373ea708 | ||
|
|
0fae01495e | ||
|
|
97867384b1 | ||
|
|
c54f6e83ed | ||
|
|
a9a731dfc0 | ||
|
|
d0f469bd90 | ||
|
|
374375ce3f | ||
|
|
89ef03412e | ||
|
|
07d3fe3a79 | ||
|
|
24107040cc | ||
|
|
9d9fc6d0bf | ||
|
|
51f5991f1e | ||
|
|
a8b93516cc | ||
|
|
a85c228e59 | ||
|
|
04a75eaca2 | ||
|
|
2f46601396 | ||
|
|
18fc6a9e0e | ||
|
|
34675e03a8 | ||
|
|
4b5cf94a29 | ||
|
|
1729e1a94c | ||
|
|
60859714ae | ||
|
|
b5fcfd918b | ||
|
|
44176be964 | ||
|
|
3935866052 | ||
|
|
1e9c45222c | ||
|
|
adc3b1a25c | ||
|
|
9ae0417cad | ||
|
|
03453d6800 | ||
|
|
1ac9c4ea8d | ||
|
|
986bb49a88 | ||
|
|
8f39c4140e | ||
|
|
9c7903868f | ||
|
|
8ee1adf058 | ||
|
|
27fb1fcd53 | ||
|
|
29cc5c1e7f | ||
|
|
29bb76019f | ||
|
|
bf6d57f327 | ||
|
|
eb5ec05ef8 | ||
|
|
c723028eae | ||
|
|
5d024fdf33 | ||
|
|
ed99cb297b | ||
|
|
a0e794a7f1 | ||
|
|
0761342840 | ||
|
|
4da9123b67 | ||
|
|
c7158e2c7c | ||
|
|
4adc45483a | ||
|
|
ae0ff6b3e3 | ||
|
|
dc6d2e6a6d | ||
|
|
5127f50d1e | ||
|
|
a26f2a7359 | ||
|
|
42f1f445a9 | ||
|
|
51304485c3 | ||
|
|
63cc6edddd | ||
|
|
58abebe9ac | ||
|
|
1182ae9e26 | ||
|
|
3407174c2f | ||
|
|
913894eaa5 | ||
|
|
db91552578 | ||
|
|
286e45bafe | ||
|
|
aa1bb470e6 | ||
|
|
3be9adde4b | ||
|
|
41d97abd08 | ||
|
|
6bc232c9b4 | ||
|
|
d4a0be92ae | ||
|
|
75bf41fba1 | ||
|
|
a2bfd5039c | ||
|
|
a9ee7472b9 | ||
|
|
211d6cb162 | ||
|
|
501c353291 | ||
|
|
aeba2e4de6 | ||
|
|
2f9f84c1f2 | ||
|
|
33646b1775 |
37
.drone.yml
@@ -22,11 +22,11 @@ steps:
|
|||||||
source /opt/qt57/bin/qt57-env.sh &&
|
source /opt/qt57/bin/qt57-env.sh &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
su -c 'ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -59,11 +59,11 @@ steps:
|
|||||||
source /opt/qt58/bin/qt58-env.sh &&
|
source /opt/qt58/bin/qt58-env.sh &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
su -c 'ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -96,11 +96,11 @@ steps:
|
|||||||
source /opt/qt59/bin/qt59-env.sh &&
|
source /opt/qt59/bin/qt59-env.sh &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
su -c 'ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -137,11 +137,11 @@ steps:
|
|||||||
source /opt/qt510/bin/qt510-env.sh &&
|
source /opt/qt510/bin/qt510-env.sh &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
su -c 'ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -178,11 +178,11 @@ steps:
|
|||||||
source /opt/qt511/bin/qt511-env.sh &&
|
source /opt/qt511/bin/qt511-env.sh &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
su -c 'ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -219,11 +219,11 @@ steps:
|
|||||||
source /opt/qt511/bin/qt511-env.sh &&
|
source /opt/qt511/bin/qt511-env.sh &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
su -c 'ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -268,11 +268,11 @@ steps:
|
|||||||
export PKG_CONFIG_PATH=\$QT_BASE_DIR/lib/pkgconfig:\$PKG_CONFIG_PATH &&
|
export PKG_CONFIG_PATH=\$QT_BASE_DIR/lib/pkgconfig:\$PKG_CONFIG_PATH &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
su -c 'ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -317,11 +317,11 @@ steps:
|
|||||||
export PKG_CONFIG_PATH=\$QT_BASE_DIR/lib/pkgconfig:\$PKG_CONFIG_PATH &&
|
export PKG_CONFIG_PATH=\$QT_BASE_DIR/lib/pkgconfig:\$PKG_CONFIG_PATH &&
|
||||||
mkdir build &&
|
mkdir build &&
|
||||||
cd build &&
|
cd build &&
|
||||||
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 -DSANITIZE_ADDRESS=ON ../ &&
|
cmake -D NO_SHIBBOLETH=1 -DCMAKE_BUILD_TYPE=Debug -DUNIT_TESTING=1 ../ &&
|
||||||
make &&
|
make &&
|
||||||
useradd -m -s /bin/bash test &&
|
useradd -m -s /bin/bash test &&
|
||||||
chown -R test:test . &&
|
chown -R test:test . &&
|
||||||
su -c 'ASAN_OPTIONS=detect_leaks=0 ctest --output-on-failure' test"
|
su -c 'ctest --output-on-failure' test"
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
@@ -361,10 +361,9 @@ steps:
|
|||||||
from_secret: DEBIAN_SECRET_IV
|
from_secret: DEBIAN_SECRET_IV
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- stable-2.6
|
||||||
event:
|
event:
|
||||||
- pull_request
|
- tag
|
||||||
- push
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
name: Documentation
|
name: Documentation
|
||||||
|
|||||||
3
.github/FUNDING.yml
vendored
@@ -1,3 +0,0 @@
|
|||||||
|
|
||||||
# You can add one username per supported platform and one custom link
|
|
||||||
custom: https://www.bountysource.com/teams/nextcloud/issues?tracker_ids=74294474
|
|
||||||
67
.github/stale.yml
vendored
@@ -1,67 +0,0 @@
|
|||||||
|
|
||||||
# Number of days of inactivity before an issue becomes stale
|
|
||||||
daysUntilStale: 28
|
|
||||||
# Number of days of inactivity before a stale issue is closed
|
|
||||||
daysUntilClose: 14
|
|
||||||
# Issues with these labels will never be considered stale
|
|
||||||
exemptLabels:
|
|
||||||
- 1. to develop
|
|
||||||
- 2. to review
|
|
||||||
- 3. to release
|
|
||||||
- 4. to test
|
|
||||||
- accessibility
|
|
||||||
- backport-request
|
|
||||||
- bug
|
|
||||||
- design
|
|
||||||
- enhancement
|
|
||||||
- epic
|
|
||||||
- discussion
|
|
||||||
- documentation
|
|
||||||
- overview
|
|
||||||
- good first issue
|
|
||||||
- feature-request
|
|
||||||
- feature: :arrows_counterclockwise: sync engine
|
|
||||||
- feature: :busts_in_silhouette: sharing
|
|
||||||
- feature: :cloud: system tray
|
|
||||||
- feature: :gear: settings
|
|
||||||
- feature: :inbox_tray: install and update
|
|
||||||
- feature: :key: authentication
|
|
||||||
- feature: :lock: end to end encryption
|
|
||||||
- feature: :memo: versions
|
|
||||||
- feature: :minidisc: external storage
|
|
||||||
- feature: :minidisc: virtual drive
|
|
||||||
- feature: :new: versions
|
|
||||||
- feature: :tongue: language l10n and translations
|
|
||||||
- feature: :wheelchair: accessibility
|
|
||||||
- feature: :white_square_button: nextcloudcmd
|
|
||||||
- feature: :zap: activity and :bell: notification
|
|
||||||
- good first issue
|
|
||||||
- help wanted
|
|
||||||
- high
|
|
||||||
- integration
|
|
||||||
- low
|
|
||||||
- medium
|
|
||||||
- needs info
|
|
||||||
- os: :apple: macOS
|
|
||||||
- os: :door: Windows
|
|
||||||
- os: :penguin: Linux
|
|
||||||
- os: :smiling_imp: FreeBSD
|
|
||||||
- overview
|
|
||||||
- package: appimage
|
|
||||||
- package: debian
|
|
||||||
- package: snap
|
|
||||||
- papercut
|
|
||||||
- regression
|
|
||||||
- security
|
|
||||||
- server
|
|
||||||
- spec
|
|
||||||
- technical debt
|
|
||||||
# Label to use when marking an issue as stale
|
|
||||||
staleLabel: stale
|
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
|
||||||
markComment: >
|
|
||||||
This request did not receive an update in the last 4 weeks.
|
|
||||||
Please take a look again and update the issue with new details,
|
|
||||||
otherwise the issue will be automatically closed in 2 weeks. Thank you!
|
|
||||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
|
||||||
closeComment: false
|
|
||||||
@@ -198,7 +198,7 @@ X-GNOME-Autostart-Delay=3
|
|||||||
|
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[cs_CZ]=@APPLICATION_ICON_NAME@
|
Icon[cs_CZ]=@NAZEV_IKONY_APLIKACE@
|
||||||
Name[cs_CZ]=@APPLICATION_NAME@ synchronizační klient pro desktop
|
Name[cs_CZ]=@APPLICATION_NAME@ synchronizační klient pro desktop
|
||||||
Comment[cs_CZ]=@APPLICATION_NAME@ synchronizační klient pro desktop
|
Comment[cs_CZ]=@APPLICATION_NAME@ synchronizační klient pro desktop
|
||||||
GenericName[cs_CZ]=Synchronizace složek
|
GenericName[cs_CZ]=Synchronizace složek
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ X-GNOME-Autostart-Delay=3
|
|||||||
|
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[de_DE]=@APPLICATION_ICON_NAME@
|
Icon[de]=@APPLICATION_ICON_NAME@
|
||||||
Name[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
|
Name[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
|
||||||
Comment[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
|
Comment[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisation
|
||||||
GenericName[de_DE]=Synchronisationsordner
|
GenericName[de]=Synchronisationsordner
|
||||||
|
|||||||
@@ -1,204 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Categories=Utility;X-SuSE-SyncUtility;
|
|
||||||
Type=Application
|
|
||||||
Exec=@APPLICATION_EXECUTABLE@
|
|
||||||
Name=@APPLICATION_NAME@ desktop sync client
|
|
||||||
Comment=@APPLICATION_NAME@ desktop synchronization client
|
|
||||||
GenericName=Folder Sync
|
|
||||||
Icon=@APPLICATION_ICON_NAME@
|
|
||||||
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
|
||||||
X-GNOME-Autostart-Delay=3
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
Icon[es_AR]=@APPLICATION_ICON_NAME@
|
|
||||||
Name[es_AR]=@APPLICATION_NAME@ cliente de sincronización de escritorio
|
|
||||||
Comment[es_AR]=@APPLICATION_NAME@ cliente de sincronización de escritorio
|
|
||||||
GenericName[es_AR]=Sincronización de carpetas
|
|
||||||
@@ -199,6 +199,4 @@ X-GNOME-Autostart-Delay=3
|
|||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Icon[sv]=@APPLICATION_ICON_NAME@
|
Icon[sv]=@APPLICATION_ICON_NAME@
|
||||||
Name[sv]=@APPLICATION_NAME@ desktopssynkklient
|
|
||||||
Comment[sv]=@APPLICATION_NAME@ desktopssynkroniseringsklient
|
|
||||||
GenericName[sv]=Mappsynkronisering
|
GenericName[sv]=Mappsynkronisering
|
||||||
|
|||||||
@@ -219,12 +219,6 @@ if (APPLE)
|
|||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
option(SANITIZE_ADDRESS "Enable address sanitizer in tests" OFF)
|
|
||||||
if (SANITIZE_ADDRESS)
|
|
||||||
include(SanitizerFlags)
|
|
||||||
enable_sanitizer()
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# Handle Translations, pick all client_* files from trans directory.
|
# Handle Translations, pick all client_* files from trans directory.
|
||||||
file( GLOB TRANS_FILES ${CMAKE_SOURCE_DIR}/translations/client_*.ts)
|
file( GLOB TRANS_FILES ${CMAKE_SOURCE_DIR}/translations/client_*.ts)
|
||||||
set(TRANSLATIONS ${TRANS_FILES})
|
set(TRANSLATIONS ${TRANS_FILES})
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
Will be tracked going forward here:
|
|
||||||
https://github.com/nextcloud/desktop/releases
|
|
||||||
|
|
||||||
2.5 Series ChangeLog
|
2.5 Series ChangeLog
|
||||||
====================
|
====================
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,11 @@ set( APPLICATION_UPDATE_URL "https://updates.nextcloud.org/client/" CACHE STRING
|
|||||||
set( APPLICATION_HELP_URL "" CACHE STRING "URL for the help menu" )
|
set( APPLICATION_HELP_URL "" CACHE STRING "URL for the help menu" )
|
||||||
set( APPLICATION_ICON_NAME "Nextcloud" )
|
set( APPLICATION_ICON_NAME "Nextcloud" )
|
||||||
set( APPLICATION_SERVER_URL "" CACHE STRING "URL for the server to use. If entered the server can only connect to this instance" )
|
set( APPLICATION_SERVER_URL "" CACHE STRING "URL for the server to use. If entered the server can only connect to this instance" )
|
||||||
set( APPLICATION_REV_DOMAIN "com.nextcloud.desktopclient" )
|
|
||||||
|
|
||||||
set( LINUX_PACKAGE_SHORTNAME "nextcloud" )
|
set( LINUX_PACKAGE_SHORTNAME "nextcloud" )
|
||||||
set( LINUX_APPLICATION_ID "${APPLICATION_REV_DOMAIN}.${LINUX_PACKAGE_SHORTNAME}")
|
|
||||||
|
|
||||||
set( THEME_CLASS "NextcloudTheme" )
|
set( THEME_CLASS "NextcloudTheme" )
|
||||||
|
set( APPLICATION_REV_DOMAIN "com.nextcloud.desktopclient" )
|
||||||
set( WIN_SETUP_BITMAP_PATH "${CMAKE_SOURCE_DIR}/admin/win/nsi" )
|
set( WIN_SETUP_BITMAP_PATH "${CMAKE_SOURCE_DIR}/admin/win/nsi" )
|
||||||
|
|
||||||
set( MAC_INSTALLER_BACKGROUND_FILE "${CMAKE_SOURCE_DIR}/admin/osx/installer-background.png" CACHE STRING "The MacOSX installer background image")
|
set( MAC_INSTALLER_BACKGROUND_FILE "${CMAKE_SOURCE_DIR}/admin/osx/installer-background.png" CACHE STRING "The MacOSX installer background image")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
set( MIRALL_VERSION_MAJOR 2 )
|
set( MIRALL_VERSION_MAJOR 2 )
|
||||||
set( MIRALL_VERSION_MINOR 7 )
|
set( MIRALL_VERSION_MINOR 6 )
|
||||||
set( MIRALL_VERSION_PATCH 0 )
|
set( MIRALL_VERSION_PATCH 2 )
|
||||||
set( MIRALL_VERSION_YEAR 2020 )
|
set( MIRALL_VERSION_YEAR 2019 )
|
||||||
set( MIRALL_SOVERSION 0 )
|
set( MIRALL_SOVERSION 0 )
|
||||||
|
|
||||||
if ( NOT DEFINED MIRALL_VERSION_SUFFIX )
|
if ( NOT DEFINED MIRALL_VERSION_SUFFIX )
|
||||||
|
|||||||
@@ -12,20 +12,17 @@ export PATH=$QT_BASE_DIR/bin:$PATH
|
|||||||
export LD_LIBRARY_PATH=$QT_BASE_DIR/lib/x86_64-linux-gnu:$QT_BASE_DIR/lib:$LD_LIBRARY_PATH
|
export LD_LIBRARY_PATH=$QT_BASE_DIR/lib/x86_64-linux-gnu:$QT_BASE_DIR/lib:$LD_LIBRARY_PATH
|
||||||
export PKG_CONFIG_PATH=$QT_BASE_DIR/lib/pkgconfig:$PKG_CONFIG_PATH
|
export PKG_CONFIG_PATH=$QT_BASE_DIR/lib/pkgconfig:$PKG_CONFIG_PATH
|
||||||
|
|
||||||
#Set APPID for .desktop file processing
|
|
||||||
export LINUX_APPLICATION_ID=com.nextcloud.desktopclient.nextcloud
|
|
||||||
|
|
||||||
#set defaults
|
#set defaults
|
||||||
export SUFFIX=${DRONE_PULL_REQUEST:=master}
|
export SUFFIX=${DRONE_PULL_REQUEST:=master}
|
||||||
if [ $SUFFIX != "master" ]; then
|
if [ $SUFFIX != "master" ]; then
|
||||||
SUFFIX="PR-$SUFFIX"
|
SUFFIX="PR-$SUFFIX"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#QtKeyChain master
|
#QtKeyChain 0.9.1
|
||||||
cd /build
|
cd /build
|
||||||
git clone https://github.com/frankosterfeld/qtkeychain.git
|
git clone https://github.com/frankosterfeld/qtkeychain.git
|
||||||
cd qtkeychain
|
cd qtkeychain
|
||||||
git checkout master
|
git checkout v0.9.1
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake -D CMAKE_INSTALL_PREFIX=/usr ../
|
cmake -D CMAKE_INSTALL_PREFIX=/usr ../
|
||||||
@@ -65,7 +62,7 @@ rm -rf ./usr/share/caja-python/
|
|||||||
rm -rf ./usr/share/nautilus-python/
|
rm -rf ./usr/share/nautilus-python/
|
||||||
rm -rf ./usr/share/nemo-python/
|
rm -rf ./usr/share/nemo-python/
|
||||||
|
|
||||||
# Move sync exclude to right location
|
# Move sync exlucde to right location
|
||||||
mv ./etc/Nextcloud/sync-exclude.lst ./usr/bin/
|
mv ./etc/Nextcloud/sync-exclude.lst ./usr/bin/
|
||||||
rm -rf ./etc
|
rm -rf ./etc
|
||||||
|
|
||||||
@@ -91,7 +88,7 @@ chmod a+x linuxdeployqt*.AppImage
|
|||||||
rm ./linuxdeployqt-continuous-x86_64.AppImage
|
rm ./linuxdeployqt-continuous-x86_64.AppImage
|
||||||
unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
|
unset QTDIR; unset QT_PLUGIN_PATH ; unset LD_LIBRARY_PATH
|
||||||
export LD_LIBRARY_PATH=/app/usr/lib/
|
export LD_LIBRARY_PATH=/app/usr/lib/
|
||||||
./squashfs-root/AppRun ${DESKTOP_FILE} -bundle-non-qt-libs -qmldir=$DRONE_WORKSPACE/src/gui
|
./squashfs-root/AppRun ${DESKTOP_FILE} -bundle-non-qt-libs
|
||||||
|
|
||||||
# Set origin
|
# Set origin
|
||||||
./squashfs-root/usr/bin/patchelf --set-rpath '$ORIGIN/' /app/usr/lib/libnextcloudsync.so.0
|
./squashfs-root/usr/bin/patchelf --set-rpath '$ORIGIN/' /app/usr/lib/libnextcloudsync.so.0
|
||||||
|
|||||||
19
client.qrc
@@ -1,8 +1,16 @@
|
|||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="/client">
|
<qresource prefix="/client">
|
||||||
|
<file>resources/dialog-close.png</file>
|
||||||
|
<file>resources/dialog-ok.png</file>
|
||||||
|
<file>resources/dialog-cancel.png</file>
|
||||||
|
<file>resources/folder-sync.png</file>
|
||||||
|
<file>resources/folder-sync@2x.png</file>
|
||||||
|
<file>resources/task-ongoing.png</file>
|
||||||
|
<file>resources/view-refresh.png</file>
|
||||||
|
<file>resources/warning.png</file>
|
||||||
|
<file>resources/warning@2x.png</file>
|
||||||
<file>resources/settings.png</file>
|
<file>resources/settings.png</file>
|
||||||
<file>resources/settings@2x.png</file>
|
<file>resources/settings@2x.png</file>
|
||||||
<file>resources/activity.svg</file>
|
|
||||||
<file>resources/activity.png</file>
|
<file>resources/activity.png</file>
|
||||||
<file>resources/activity@2x.png</file>
|
<file>resources/activity@2x.png</file>
|
||||||
<file>resources/network.png</file>
|
<file>resources/network.png</file>
|
||||||
@@ -12,13 +20,13 @@
|
|||||||
<file>resources/lock-https.png</file>
|
<file>resources/lock-https.png</file>
|
||||||
<file>resources/lock-https@2x.png</file>
|
<file>resources/lock-https@2x.png</file>
|
||||||
<file>resources/account.png</file>
|
<file>resources/account.png</file>
|
||||||
<file>resources/account.svg</file>
|
|
||||||
<file>resources/more.svg</file>
|
<file>resources/more.svg</file>
|
||||||
<file>resources/delete.png</file>
|
<file>resources/delete.png</file>
|
||||||
<file>resources/close.svg</file>
|
<file>resources/close.svg</file>
|
||||||
<file>resources/bell.svg</file>
|
<file>resources/bell.svg</file>
|
||||||
<file>resources/link.svg</file>
|
<file>resources/link.svg</file>
|
||||||
<file>resources/files.svg</file>
|
<file>resources/files.svg</file>
|
||||||
|
<file>resources/folder-grey.png</file>
|
||||||
<file>resources/state-error.svg</file>
|
<file>resources/state-error.svg</file>
|
||||||
<file>resources/state-warning.svg</file>
|
<file>resources/state-warning.svg</file>
|
||||||
<file>resources/folder.svg</file>
|
<file>resources/folder.svg</file>
|
||||||
@@ -30,14 +38,7 @@
|
|||||||
<file>resources/copy.svg</file>
|
<file>resources/copy.svg</file>
|
||||||
<file>resources/state-sync.svg</file>
|
<file>resources/state-sync.svg</file>
|
||||||
<file>resources/add.png</file>
|
<file>resources/add.png</file>
|
||||||
<file>resources/add-color.svg</file>
|
|
||||||
<file>resources/state-info.svg</file>
|
<file>resources/state-info.svg</file>
|
||||||
<file>resources/change.svg</file>
|
|
||||||
<file>resources/delete-color.svg</file>
|
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="/"/>
|
<qresource prefix="/"/>
|
||||||
<qresource prefix="/qml">
|
|
||||||
<file>src/gui/tray/Window.qml</file>
|
|
||||||
<file>src/gui/tray/UserLine.qml</file>
|
|
||||||
</qresource>
|
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>@MIRALL_VERSION_STRING@</string>
|
<string>@MIRALL_VERSION_STRING@</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>(C) 2014-2020 @APPLICATION_VENDOR@</string>
|
<string>(C) 2014-2019 @APPLICATION_VENDOR@</string>
|
||||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>SUShowReleaseNotes</key>
|
<key>SUShowReleaseNotes</key>
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
# Enable address sanitizer (gcc/clang only)
|
|
||||||
macro(ENABLE_SANITIZER)
|
|
||||||
|
|
||||||
if (NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
|
||||||
message(FATAL_ERROR "Sanitizer supported only for gcc/clang")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(SANITIZER_FLAGS "-fsanitize=address -fsanitize=leak -g")
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SANITIZER_FLAGS}")
|
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SANITIZER_FLAGS}")
|
|
||||||
|
|
||||||
set(LINKER_FLAGS "-fsanitize=address,undefined -fuse-ld=gold")
|
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LINKER_FLAGS}")
|
|
||||||
|
|
||||||
endmacro()
|
|
||||||
|
|
||||||
@@ -8,12 +8,12 @@
|
|||||||
#cmakedefine CRASHREPORTER_EXECUTABLE "@CRASHREPORTER_EXECUTABLE@"
|
#cmakedefine CRASHREPORTER_EXECUTABLE "@CRASHREPORTER_EXECUTABLE@"
|
||||||
#define SOCKETAPI_TEAM_IDENTIFIER_PREFIX "@SOCKETAPI_TEAM_IDENTIFIER_PREFIX@"
|
#define SOCKETAPI_TEAM_IDENTIFIER_PREFIX "@SOCKETAPI_TEAM_IDENTIFIER_PREFIX@"
|
||||||
|
|
||||||
|
#cmakedefine APPLICATION_DOMAIN @APPLICATION_DOMAIN@
|
||||||
#cmakedefine THEME_CLASS @THEME_CLASS@
|
#cmakedefine THEME_CLASS @THEME_CLASS@
|
||||||
#cmakedefine THEME_INCLUDE @THEME_INCLUDE@
|
#cmakedefine THEME_INCLUDE @THEME_INCLUDE@
|
||||||
|
|
||||||
#cmakedefine APPLICATION_NAME "@APPLICATION_NAME@"
|
#cmakedefine APPLICATION_NAME "@APPLICATION_NAME@"
|
||||||
#cmakedefine APPLICATION_VENDOR "@APPLICATION_VENDOR@"
|
#cmakedefine APPLICATION_VENDOR "@APPLICATION_VENDOR@"
|
||||||
#cmakedefine APPLICATION_DOMAIN "@APPLICATION_DOMAIN@"
|
|
||||||
#cmakedefine APPLICATION_REV_DOMAIN "@APPLICATION_REV_DOMAIN@"
|
#cmakedefine APPLICATION_REV_DOMAIN "@APPLICATION_REV_DOMAIN@"
|
||||||
#cmakedefine APPLICATION_SHORTNAME "@APPLICATION_SHORTNAME@"
|
#cmakedefine APPLICATION_SHORTNAME "@APPLICATION_SHORTNAME@"
|
||||||
#cmakedefine APPLICATION_EXECUTABLE "@APPLICATION_EXECUTABLE@"
|
#cmakedefine APPLICATION_EXECUTABLE "@APPLICATION_EXECUTABLE@"
|
||||||
@@ -21,7 +21,6 @@
|
|||||||
#cmakedefine APPLICATION_HELP_URL "@APPLICATION_HELP_URL@"
|
#cmakedefine APPLICATION_HELP_URL "@APPLICATION_HELP_URL@"
|
||||||
#cmakedefine APPLICATION_ICON_NAME "@APPLICATION_ICON_NAME@"
|
#cmakedefine APPLICATION_ICON_NAME "@APPLICATION_ICON_NAME@"
|
||||||
#cmakedefine APPLICATION_SERVER_URL "@APPLICATION_SERVER_URL@"
|
#cmakedefine APPLICATION_SERVER_URL "@APPLICATION_SERVER_URL@"
|
||||||
#cmakedefine LINUX_APPLICATION_ID "@LINUX_APPLICATION_ID@"
|
|
||||||
#cmakedefine APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR "@APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR@"
|
#cmakedefine APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR "@APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR@"
|
||||||
#cmakedefine APPLICATION_WIZARD_HEADER_TITLE_COLOR "@APPLICATION_WIZARD_HEADER_TITLE_COLOR@"
|
#cmakedefine APPLICATION_WIZARD_HEADER_TITLE_COLOR "@APPLICATION_WIZARD_HEADER_TITLE_COLOR@"
|
||||||
#cmakedefine APPLICATION_WIZARD_USE_CUSTOM_LOGO "@APPLICATION_WIZARD_USE_CUSTOM_LOGO@"
|
#cmakedefine APPLICATION_WIZARD_USE_CUSTOM_LOGO "@APPLICATION_WIZARD_USE_CUSTOM_LOGO@"
|
||||||
|
|||||||
@@ -13,19 +13,11 @@ desktop client.
|
|||||||
|
|
||||||
These instructions are updated to work with version |version| of the Nextcloud Client.
|
These instructions are updated to work with version |version| of the Nextcloud Client.
|
||||||
|
|
||||||
You have two possibilities to clone the repo.
|
Getting Source Code
|
||||||
|
-------------------
|
||||||
|
|
||||||
First option is As [remote URL](https://help.github.com/en/articles/which-remote-url-should-i-use) you can choose between cloning with HTTPS URL's, which is recommended or cloning with SSH URL's.
|
The :ref:`generic-build-instructions` pull the latest code directly from
|
||||||
|
GitHub, and work on Linux, macOS, and Windows.
|
||||||
[https://github.com/nextcloud/desktop.git](https://github.com/nextcloud/desktop.git)
|
|
||||||
|
|
||||||
When you don't have SSH key added to your GitHub account, than use HTTPS.
|
|
||||||
|
|
||||||
When you no part of the nextcloud organisation, clone with HTTPS:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ git clone git@github.com:nextcloud/desktop.git
|
|
||||||
```
|
|
||||||
|
|
||||||
macOS
|
macOS
|
||||||
-----
|
-----
|
||||||
@@ -55,7 +47,7 @@ To set up your build environment for development using HomeBrew_:
|
|||||||
|
|
||||||
5. Install a Qt5 version with qtwebkit support::
|
5. Install a Qt5 version with qtwebkit support::
|
||||||
|
|
||||||
brew install qt5
|
brew install qt5 --with-qtwebkit
|
||||||
|
|
||||||
6. Install any missing dependencies::
|
6. Install any missing dependencies::
|
||||||
|
|
||||||
@@ -75,23 +67,10 @@ To set up your build environment for development using HomeBrew_:
|
|||||||
|
|
||||||
10. Install the Packages_ package creation tool.
|
10. Install the Packages_ package creation tool.
|
||||||
|
|
||||||
11. Enable git submodules:
|
11. In the build directory, run ``admin/osx/create_mac.sh <build_dir> <install_dir>``.
|
||||||
```
|
If you have a developer signing certificate, you can specify
|
||||||
$ cd desktop
|
its Common Name as a third parameter (use quotes) to have the package
|
||||||
$ git submodule init
|
signed automatically.
|
||||||
$ git submodule update
|
|
||||||
```
|
|
||||||
|
|
||||||
12. Generate the build files:
|
|
||||||
```
|
|
||||||
$ cd build
|
|
||||||
$ cmake .. -DCMAKE_INSTALL_PREFIX=~/nextcloud-desktop-client -DCMAKE_BUILD_TYPE=Debug -DNO_SHIBBOLETH=1
|
|
||||||
```
|
|
||||||
|
|
||||||
13. Compile and install:
|
|
||||||
```
|
|
||||||
$ make install
|
|
||||||
```
|
|
||||||
|
|
||||||
.. note:: Contrary to earlier versions, Nextcloud 1.7 and later are packaged
|
.. note:: Contrary to earlier versions, Nextcloud 1.7 and later are packaged
|
||||||
as a ``pkg`` installer. Do not call "make package" at any time when
|
as a ``pkg`` installer. Do not call "make package" at any time when
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ download page.
|
|||||||
System Requirements
|
System Requirements
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
- Windows 8.1+
|
- Windows 7+
|
||||||
- macOS 10.7+ (**64-bit only**)
|
- macOS 10.7+ (**64-bit only**)
|
||||||
- CentOS 6 & 7 (64-bit only)
|
- CentOS 6 & 7 (64-bit only)
|
||||||
- Debian 8.0 & 9.0
|
- Debian 8.0 & 9.0
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" viewbox="0 0 16 16"><path fill="#00d400" d="M9.02 13.98h-2v-5h-5v-2h5v-5h2v5l5-.028V8.98h-5z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 179 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" version="1.1" height="16"><path d="m8 2c-2.142 0-4.125 1.145-5.196 3l1.948 1.125c0.671-1.162 1.906-1.875 3.2476-1.875 1.1906 0 2.297 0.56157 3 1.5l-1.5 1.5h4.5v-4.5l-1.406 1.406c-1.129-1.348-2.802-2.1563-4.594-2.1563z"/><path d="m2 8.75v4.5l1.408-1.41c1.116 1.334 2.817 2.145 4.592 2.16 2.16 0.01827 4.116-1.132 5.196-3.002l-1.948-1.125c-0.677 1.171-1.9005 1.886-3.248 1.875-1.18-0.01-2.3047-0.572-3-1.5l1.5-1.5z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 493 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" viewBox="0 0 16 16"><path d="m3.0503 4.4645 3.5355 3.5355-3.5355 3.536 1.4142 1.414 3.5355-3.5358 3.536 3.5358 1.414-1.414-3.5358-3.536 3.5358-3.5355-1.414-1.4142-3.536 3.5355-3.5355-3.5355-1.4142 1.4142z" fill="#d40000"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 306 B |
BIN
resources/dialog-cancel.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
resources/dialog-close.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
resources/dialog-ok.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
resources/folder-grey.png
Normal file
|
After Width: | Height: | Size: 668 B |
BIN
resources/folder-sync.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
resources/folder-sync@2x.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
resources/task-ongoing.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
resources/view-refresh.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
resources/warning.png
Normal file
|
After Width: | Height: | Size: 596 B |
BIN
resources/warning@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
@@ -499,7 +499,7 @@ restart_sync:
|
|||||||
}
|
}
|
||||||
|
|
||||||
Cmd cmd;
|
Cmd cmd;
|
||||||
QString dbPath = options.source_dir + SyncJournalDb::makeDbName(credentialFreeUrl, folder, user);
|
QString dbPath = options.source_dir + SyncJournalDb::makeDbName(options.source_dir, credentialFreeUrl, folder, user);
|
||||||
SyncJournalDb db(dbPath);
|
SyncJournalDb db(dbPath);
|
||||||
|
|
||||||
if (!selectiveSyncList.empty()) {
|
if (!selectiveSyncList.empty()) {
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
#include "common/syncjournaldb.h"
|
#include "common/syncjournaldb.h"
|
||||||
@@ -103,15 +102,11 @@ SyncJournalDb::SyncJournalDb(const QString &dbFilePath, QObject *parent)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SyncJournalDb::makeDbName(const QUrl &remoteUrl,
|
QString SyncJournalDb::makeDbName(const QString &localPath,
|
||||||
|
const QUrl &remoteUrl,
|
||||||
const QString &remotePath,
|
const QString &remotePath,
|
||||||
const QString &user)
|
const QString &user)
|
||||||
{
|
{
|
||||||
const QString dbPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
|
||||||
if (!QDir(dbPath).exists()) {
|
|
||||||
QDir().mkdir(dbPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString journalPath = QLatin1String("._sync_");
|
QString journalPath = QLatin1String("._sync_");
|
||||||
|
|
||||||
QString key = QString::fromUtf8("%1@%2:%3").arg(user, remoteUrl.toString(), remotePath);
|
QString key = QString::fromUtf8("%1@%2:%3").arg(user, remoteUrl.toString(), remotePath);
|
||||||
@@ -120,16 +115,17 @@ QString SyncJournalDb::makeDbName(const QUrl &remoteUrl,
|
|||||||
journalPath.append(ba.left(6).toHex());
|
journalPath.append(ba.left(6).toHex());
|
||||||
journalPath.append(".db");
|
journalPath.append(".db");
|
||||||
|
|
||||||
journalPath = dbPath + QLatin1Char('/') + journalPath;
|
|
||||||
|
|
||||||
// If the journal doesn't exist and we can't create a file
|
// If the journal doesn't exist and we can't create a file
|
||||||
// at that location, try again with a journal name that doesn't
|
// at that location, try again with a journal name that doesn't
|
||||||
// have the ._ prefix.
|
// have the ._ prefix.
|
||||||
//
|
//
|
||||||
|
// The disadvantage of that filename is that it will only be ignored
|
||||||
|
// by client versions >2.3.2.
|
||||||
|
//
|
||||||
// See #5633: "._*" is often forbidden on samba shared folders.
|
// See #5633: "._*" is often forbidden on samba shared folders.
|
||||||
|
|
||||||
// If it exists already, the path is clearly usable
|
// If it exists already, the path is clearly usable
|
||||||
QFile file(QDir(dbPath).filePath(journalPath));
|
QFile file(QDir(localPath).filePath(journalPath));
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
return journalPath;
|
return journalPath;
|
||||||
}
|
}
|
||||||
@@ -144,7 +140,7 @@ QString SyncJournalDb::makeDbName(const QUrl &remoteUrl,
|
|||||||
|
|
||||||
// Can we create it if we drop the underscore?
|
// Can we create it if we drop the underscore?
|
||||||
QString alternateJournalPath = journalPath.mid(2).prepend(".");
|
QString alternateJournalPath = journalPath.mid(2).prepend(".");
|
||||||
QFile file2(QDir(dbPath).filePath(alternateJournalPath));
|
QFile file2(QDir(localPath).filePath(alternateJournalPath));
|
||||||
if (file2.open(QIODevice::ReadWrite)) {
|
if (file2.open(QIODevice::ReadWrite)) {
|
||||||
// The alternative worked, use it
|
// The alternative worked, use it
|
||||||
qCInfo(lcDb) << "Using alternate database path" << alternateJournalPath;
|
qCInfo(lcDb) << "Using alternate database path" << alternateJournalPath;
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ public:
|
|||||||
virtual ~SyncJournalDb();
|
virtual ~SyncJournalDb();
|
||||||
|
|
||||||
/// Create a journal path for a specific configuration
|
/// Create a journal path for a specific configuration
|
||||||
static QString makeDbName(const QUrl &remoteUrl,
|
static QString makeDbName(const QString &localPath,
|
||||||
|
const QUrl &remoteUrl,
|
||||||
const QString &remotePath,
|
const QString &remotePath,
|
||||||
const QString &user);
|
const QString &user);
|
||||||
|
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ void Utility::usleep(int usec)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This can be overriden from the tests
|
// This can be overriden from the tests
|
||||||
OCSYNC_EXPORT bool fsCasePreserving_override = []() -> bool {
|
OCSYNC_EXPORT bool fsCasePreserving_override = []()-> bool {
|
||||||
QByteArray env = qgetenv("OWNCLOUD_TEST_CASE_PRESERVING");
|
QByteArray env = qgetenv("OWNCLOUD_TEST_CASE_PRESERVING");
|
||||||
if (!env.isEmpty())
|
if (!env.isEmpty())
|
||||||
return env.toInt();
|
return env.toInt();
|
||||||
@@ -362,12 +362,12 @@ QString Utility::fileNameForGuiUse(const QString &fName)
|
|||||||
QByteArray Utility::normalizeEtag(QByteArray etag)
|
QByteArray Utility::normalizeEtag(QByteArray etag)
|
||||||
{
|
{
|
||||||
/* strip "XXXX-gzip" */
|
/* strip "XXXX-gzip" */
|
||||||
if (etag.startsWith('"') && etag.endsWith("-gzip\"")) {
|
if(etag.startsWith('"') && etag.endsWith("-gzip\"")) {
|
||||||
etag.chop(6);
|
etag.chop(6);
|
||||||
etag.remove(0, 1);
|
etag.remove(0, 1);
|
||||||
}
|
}
|
||||||
/* strip trailing -gzip */
|
/* strip trailing -gzip */
|
||||||
if (etag.endsWith("-gzip")) {
|
if(etag.endsWith("-gzip")) {
|
||||||
etag.chop(5);
|
etag.chop(5);
|
||||||
}
|
}
|
||||||
/* strip normal quotes */
|
/* strip normal quotes */
|
||||||
@@ -400,7 +400,7 @@ void Utility::crash()
|
|||||||
// without compiler warnings about possible truncation
|
// without compiler warnings about possible truncation
|
||||||
uint Utility::convertSizeToUint(size_t &convertVar)
|
uint Utility::convertSizeToUint(size_t &convertVar)
|
||||||
{
|
{
|
||||||
if (convertVar > UINT_MAX) {
|
if( convertVar > UINT_MAX ) {
|
||||||
//throw std::bad_cast();
|
//throw std::bad_cast();
|
||||||
convertVar = UINT_MAX; // intentionally default to wrong value here to not crash: exception handling TBD
|
convertVar = UINT_MAX; // intentionally default to wrong value here to not crash: exception handling TBD
|
||||||
}
|
}
|
||||||
@@ -409,7 +409,7 @@ uint Utility::convertSizeToUint(size_t &convertVar)
|
|||||||
|
|
||||||
uint Utility::convertSizeToInt(size_t &convertVar)
|
uint Utility::convertSizeToInt(size_t &convertVar)
|
||||||
{
|
{
|
||||||
if (convertVar > INT_MAX) {
|
if( convertVar > INT_MAX ) {
|
||||||
//throw std::bad_cast();
|
//throw std::bad_cast();
|
||||||
convertVar = INT_MAX; // intentionally default to wrong value here to not crash: exception handling TBD
|
convertVar = INT_MAX; // intentionally default to wrong value here to not crash: exception handling TBD
|
||||||
}
|
}
|
||||||
@@ -465,7 +465,7 @@ QString Utility::timeAgoInWords(const QDateTime &dt, const QDateTime &from)
|
|||||||
|
|
||||||
if (floor(secs / 3600.0) > 0) {
|
if (floor(secs / 3600.0) > 0) {
|
||||||
int hours = floor(secs / 3600.0);
|
int hours = floor(secs / 3600.0);
|
||||||
if (hours == 1) {
|
if(hours == 1){
|
||||||
return (QObject::tr("%n hour ago", "", hours));
|
return (QObject::tr("%n hour ago", "", hours));
|
||||||
} else {
|
} else {
|
||||||
return (QObject::tr("%n hours ago", "", hours));
|
return (QObject::tr("%n hours ago", "", hours));
|
||||||
@@ -480,7 +480,7 @@ QString Utility::timeAgoInWords(const QDateTime &dt, const QDateTime &from)
|
|||||||
return QObject::tr("Less than a minute ago");
|
return QObject::tr("Less than a minute ago");
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (minutes == 1) {
|
} else if(minutes == 1){
|
||||||
return (QObject::tr("%n minute ago", "", minutes));
|
return (QObject::tr("%n minute ago", "", minutes));
|
||||||
} else {
|
} else {
|
||||||
return (QObject::tr("%n minutes ago", "", minutes));
|
return (QObject::tr("%n minutes ago", "", minutes));
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
#ifndef UTILITY_H
|
#ifndef UTILITY_H
|
||||||
#define UTILITY_H
|
#define UTILITY_H
|
||||||
|
|
||||||
|
|
||||||
#include "ocsynclib.h"
|
#include "ocsynclib.h"
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
@@ -30,7 +29,6 @@
|
|||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QUrlQuery>
|
#include <QUrlQuery>
|
||||||
#include <QtQuick/QQuickImageProvider>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
|||||||
@@ -47,18 +47,14 @@ QString getUserAutostartDir_private()
|
|||||||
|
|
||||||
bool hasLaunchOnStartup_private(const QString &appName)
|
bool hasLaunchOnStartup_private(const QString &appName)
|
||||||
{
|
{
|
||||||
QString desktopFileLocation = getUserAutostartDir_private()
|
QString desktopFileLocation = getUserAutostartDir_private() + appName + QLatin1String(".desktop");
|
||||||
+ QLatin1String(LINUX_APPLICATION_ID)
|
|
||||||
+ QLatin1String(".desktop");
|
|
||||||
return QFile::exists(desktopFileLocation);
|
return QFile::exists(desktopFileLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLaunchOnStartup_private(const QString &appName, const QString &guiName, bool enable)
|
void setLaunchOnStartup_private(const QString &appName, const QString &guiName, bool enable)
|
||||||
{
|
{
|
||||||
QString userAutoStartPath = getUserAutostartDir_private();
|
QString userAutoStartPath = getUserAutostartDir_private();
|
||||||
QString desktopFileLocation = userAutoStartPath
|
QString desktopFileLocation = userAutoStartPath + appName + QLatin1String(".desktop");
|
||||||
+ QLatin1String(LINUX_APPLICATION_ID)
|
|
||||||
+ QLatin1String(".desktop");
|
|
||||||
if (enable) {
|
if (enable) {
|
||||||
if (!QDir().exists(userAutoStartPath) && !QDir().mkpath(userAutoStartPath)) {
|
if (!QDir().exists(userAutoStartPath) && !QDir().mkpath(userAutoStartPath)) {
|
||||||
qCWarning(lcUtility) << "Could not create autostart folder" << userAutoStartPath;
|
qCWarning(lcUtility) << "Could not create autostart folder" << userAutoStartPath;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
project(gui)
|
project(gui)
|
||||||
find_package(Qt5 REQUIRED COMPONENTS Widgets Svg)
|
find_package(Qt5 REQUIRED COMPONENTS Widgets)
|
||||||
set(CMAKE_AUTOMOC TRUE)
|
set(CMAKE_AUTOMOC TRUE)
|
||||||
set(CMAKE_AUTOUIC TRUE)
|
set(CMAKE_AUTOUIC TRUE)
|
||||||
set(CMAKE_AUTORCC TRUE)
|
set(CMAKE_AUTORCC TRUE)
|
||||||
@@ -24,6 +24,7 @@ set(client_UI_SRCS
|
|||||||
ignorelisteditor.ui
|
ignorelisteditor.ui
|
||||||
ignorelisttablewidget.ui
|
ignorelisttablewidget.ui
|
||||||
networksettings.ui
|
networksettings.ui
|
||||||
|
activitywidget.ui
|
||||||
synclogdialog.ui
|
synclogdialog.ui
|
||||||
settingsdialog.ui
|
settingsdialog.ui
|
||||||
sharedialog.ui
|
sharedialog.ui
|
||||||
@@ -34,8 +35,6 @@ set(client_UI_SRCS
|
|||||||
addcertificatedialog.ui
|
addcertificatedialog.ui
|
||||||
proxyauthdialog.ui
|
proxyauthdialog.ui
|
||||||
mnemonicdialog.ui
|
mnemonicdialog.ui
|
||||||
tray/Window.qml
|
|
||||||
tray/UserLine.qml
|
|
||||||
wizard/flow2authwidget.ui
|
wizard/flow2authwidget.ui
|
||||||
wizard/owncloudadvancedsetuppage.ui
|
wizard/owncloudadvancedsetuppage.ui
|
||||||
wizard/owncloudconnectionmethoddialog.ui
|
wizard/owncloudconnectionmethoddialog.ui
|
||||||
@@ -74,6 +73,10 @@ set(client_SRCS
|
|||||||
openfilemanager.cpp
|
openfilemanager.cpp
|
||||||
owncloudgui.cpp
|
owncloudgui.cpp
|
||||||
owncloudsetupwizard.cpp
|
owncloudsetupwizard.cpp
|
||||||
|
activitydata.cpp
|
||||||
|
activitylistmodel.cpp
|
||||||
|
activitywidget.cpp
|
||||||
|
activityitemdelegate.cpp
|
||||||
selectivesyncdialog.cpp
|
selectivesyncdialog.cpp
|
||||||
settingsdialog.cpp
|
settingsdialog.cpp
|
||||||
sharedialog.cpp
|
sharedialog.cpp
|
||||||
@@ -96,15 +99,12 @@ set(client_SRCS
|
|||||||
synclogdialog.cpp
|
synclogdialog.cpp
|
||||||
tooltipupdater.cpp
|
tooltipupdater.cpp
|
||||||
notificationconfirmjob.cpp
|
notificationconfirmjob.cpp
|
||||||
|
servernotificationhandler.cpp
|
||||||
guiutility.cpp
|
guiutility.cpp
|
||||||
elidedlabel.cpp
|
elidedlabel.cpp
|
||||||
headerbanner.cpp
|
headerbanner.cpp
|
||||||
iconjob.cpp
|
iconjob.cpp
|
||||||
remotewipe.cpp
|
remotewipe.cpp
|
||||||
tray/ActivityData.cpp
|
|
||||||
tray/ActivityListModel.cpp
|
|
||||||
tray/UserModel.cpp
|
|
||||||
tray/NotificationHandler.cpp
|
|
||||||
creds/credentialsfactory.cpp
|
creds/credentialsfactory.cpp
|
||||||
creds/httpcredentialsgui.cpp
|
creds/httpcredentialsgui.cpp
|
||||||
creds/oauth.cpp
|
creds/oauth.cpp
|
||||||
@@ -298,7 +298,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(updater STATIC ${updater_SRCS})
|
add_library(updater STATIC ${updater_SRCS})
|
||||||
target_link_libraries(updater ${synclib_NAME} Qt5::Widgets Qt5::Svg Qt5::Network Qt5::Xml Qt5::WebEngineWidgets)
|
target_link_libraries(updater ${synclib_NAME} Qt5::Widgets Qt5::Network Qt5::Xml Qt5::WebEngineWidgets)
|
||||||
target_include_directories(updater PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(updater PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
|
set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
|
||||||
@@ -308,7 +308,7 @@ set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
|
|||||||
set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
|
set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES
|
||||||
INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE};${CMAKE_INSTALL_RPATH}" )
|
INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE};${CMAKE_INSTALL_RPATH}" )
|
||||||
|
|
||||||
target_link_libraries( ${APPLICATION_EXECUTABLE} Qt5::Widgets Qt5::Svg Qt5::Network Qt5::Xml)
|
target_link_libraries( ${APPLICATION_EXECUTABLE} Qt5::Widgets Qt5::Network Qt5::Xml)
|
||||||
target_link_libraries( ${APPLICATION_EXECUTABLE} ${synclib_NAME} )
|
target_link_libraries( ${APPLICATION_EXECUTABLE} ${synclib_NAME} )
|
||||||
target_link_libraries( ${APPLICATION_EXECUTABLE} updater )
|
target_link_libraries( ${APPLICATION_EXECUTABLE} updater )
|
||||||
target_link_libraries( ${APPLICATION_EXECUTABLE} ${OS_SPECIFIC_LINK_LIBRARIES} )
|
target_link_libraries( ${APPLICATION_EXECUTABLE} ${OS_SPECIFIC_LINK_LIBRARIES} )
|
||||||
@@ -388,7 +388,7 @@ endif()
|
|||||||
|
|
||||||
if(NOT BUILD_OWNCLOUD_OSX_BUNDLE AND NOT WIN32)
|
if(NOT BUILD_OWNCLOUD_OSX_BUNDLE AND NOT WIN32)
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/mirall.desktop.in
|
configure_file(${CMAKE_SOURCE_DIR}/mirall.desktop.in
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/${LINUX_APPLICATION_ID}.desktop)
|
${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_EXECUTABLE}.desktop)
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${LINUX_APPLICATION_ID}.desktop DESTINATION ${DATADIR}/applications )
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${APPLICATION_EXECUTABLE}.desktop DESTINATION ${DATADIR}/applications )
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -89,9 +89,6 @@ private:
|
|||||||
// Adds an account to the tracked list, emitting accountAdded()
|
// Adds an account to the tracked list, emitting accountAdded()
|
||||||
void addAccountState(AccountState *accountState);
|
void addAccountState(AccountState *accountState);
|
||||||
|
|
||||||
AccountManager() {}
|
|
||||||
QList<AccountStatePtr> _accounts;
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/// Saves account data, not including the credentials
|
/// Saves account data, not including the credentials
|
||||||
void saveAccount(Account *a);
|
void saveAccount(Account *a);
|
||||||
@@ -107,5 +104,9 @@ Q_SIGNALS:
|
|||||||
void accountAdded(AccountState *account);
|
void accountAdded(AccountState *account);
|
||||||
void accountRemoved(AccountState *account);
|
void accountRemoved(AccountState *account);
|
||||||
void removeAccountFolders(AccountState *account);
|
void removeAccountFolders(AccountState *account);
|
||||||
|
|
||||||
|
private:
|
||||||
|
AccountManager() {}
|
||||||
|
QList<AccountStatePtr> _accounts;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,9 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
|
|||||||
_ui->_folderList->setAttribute(Qt::WA_Hover, true);
|
_ui->_folderList->setAttribute(Qt::WA_Hover, true);
|
||||||
_ui->_folderList->installEventFilter(mouseCursorChanger);
|
_ui->_folderList->installEventFilter(mouseCursorChanger);
|
||||||
|
|
||||||
|
createAccountToolbox();
|
||||||
|
connect(AccountManager::instance(), &AccountManager::accountAdded,
|
||||||
|
this, &AccountSettings::slotAccountAdded);
|
||||||
connect(this, &AccountSettings::removeAccountFolders,
|
connect(this, &AccountSettings::removeAccountFolders,
|
||||||
AccountManager::instance(), &AccountManager::removeAccountFolders);
|
AccountManager::instance(), &AccountManager::removeAccountFolders);
|
||||||
connect(_ui->_folderList, &QWidget::customContextMenuRequested,
|
connect(_ui->_folderList, &QWidget::customContextMenuRequested,
|
||||||
@@ -204,13 +207,36 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
|
|||||||
_ui->encryptionMessage->hide();
|
_ui->encryptionMessage->hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(UserModel::instance(), &UserModel::addAccount,
|
|
||||||
this, &AccountSettings::slotOpenAccountWizard);
|
|
||||||
|
|
||||||
customizeStyle();
|
customizeStyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AccountSettings::createAccountToolbox()
|
||||||
|
{
|
||||||
|
QMenu *menu = new QMenu();
|
||||||
|
|
||||||
|
connect(menu, &QMenu::aboutToShow, this, &AccountSettings::slotMenuBeforeShow);
|
||||||
|
|
||||||
|
_addAccountAction = new QAction(tr("Add new"), this);
|
||||||
|
menu->addAction(_addAccountAction);
|
||||||
|
connect(_addAccountAction, &QAction::triggered, this, &AccountSettings::slotOpenAccountWizard);
|
||||||
|
|
||||||
|
_toggleSignInOutAction = new QAction(tr("Log out"), this);
|
||||||
|
connect(_toggleSignInOutAction, &QAction::triggered, this, &AccountSettings::slotToggleSignInState);
|
||||||
|
menu->addAction(_toggleSignInOutAction);
|
||||||
|
|
||||||
|
QAction *action = new QAction(tr("Remove"), this);
|
||||||
|
menu->addAction(action);
|
||||||
|
connect(action, &QAction::triggered, this, &AccountSettings::slotDeleteAccount);
|
||||||
|
|
||||||
|
_ui->_accountToolbox->setText(tr("Account") + QLatin1Char(' '));
|
||||||
|
_ui->_accountToolbox->setMenu(menu);
|
||||||
|
_ui->_accountToolbox->setPopupMode(QToolButton::InstantPopup);
|
||||||
|
|
||||||
|
slotAccountAdded(_accountState);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void AccountSettings::slotNewMnemonicGenerated()
|
void AccountSettings::slotNewMnemonicGenerated()
|
||||||
{
|
{
|
||||||
_ui->encryptionMessage->setText(tr("This account supports end-to-end encryption"));
|
_ui->encryptionMessage->setText(tr("This account supports end-to-end encryption"));
|
||||||
@@ -223,6 +249,24 @@ void AccountSettings::slotNewMnemonicGenerated()
|
|||||||
_ui->encryptionMessage->show();
|
_ui->encryptionMessage->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AccountSettings::slotMenuBeforeShow() {
|
||||||
|
if (_menuShown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto menu = _ui->_accountToolbox->menu();
|
||||||
|
|
||||||
|
// We can't check this during the initial creation as there is no account yet then
|
||||||
|
if (_accountState->account()->capabilities().clientSideEncryptionAvaliable()) {
|
||||||
|
QAction *mnemonic = new QAction(tr("Show E2E mnemonic"), this);
|
||||||
|
connect(mnemonic, &QAction::triggered, this, &AccountSettings::requesetMnemonic);
|
||||||
|
menu->addAction(mnemonic);
|
||||||
|
}
|
||||||
|
|
||||||
|
_menuShown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
QString AccountSettings::selectedFolderAlias() const
|
QString AccountSettings::selectedFolderAlias() const
|
||||||
{
|
{
|
||||||
QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex();
|
QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex();
|
||||||
@@ -1016,12 +1060,21 @@ void AccountSettings::slotAccountStateChanged()
|
|||||||
// sync user interface buttons.
|
// sync user interface buttons.
|
||||||
refreshSelectiveSyncStatus();
|
refreshSelectiveSyncStatus();
|
||||||
|
|
||||||
|
/* set the correct label for the Account toolbox button */
|
||||||
|
if (_accountState) {
|
||||||
|
if (_accountState->isSignedOut()) {
|
||||||
|
_toggleSignInOutAction->setText(tr("Log in"));
|
||||||
|
} else {
|
||||||
|
_toggleSignInOutAction->setText(tr("Log out"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (state == AccountState::State::Connected) {
|
if (state == AccountState::State::Connected) {
|
||||||
/* TODO: We should probably do something better here.
|
/* TODO: We should probably do something better here.
|
||||||
* Verify if the user has a private key already uploaded to the server,
|
* Verify if the user has a private key already uploaded to the server,
|
||||||
* if it has, do not offer to create one.
|
* if it has, do not offer to create one.
|
||||||
*/
|
*/
|
||||||
qCInfo(lcAccountSettings) << "Account" << accountsState()->account()->displayName()
|
qCInfo(lcAccountSettings) << "Accout" << accountsState()->account()->displayName()
|
||||||
<< "Client Side Encryption" << accountsState()->account()->capabilities().clientSideEncryptionAvaliable();
|
<< "Client Side Encryption" << accountsState()->account()->capabilities().clientSideEncryptionAvaliable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1137,6 +1190,18 @@ void AccountSettings::refreshSelectiveSyncStatus()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AccountSettings::slotAccountAdded(AccountState *)
|
||||||
|
{
|
||||||
|
// if the theme is limited to single account, the button must hide if
|
||||||
|
// there is already one account.
|
||||||
|
int s = AccountManager::instance()->accounts().size();
|
||||||
|
if (s > 0 && !Theme::instance()->multiAccount()) {
|
||||||
|
_addAccountAction->setVisible(false);
|
||||||
|
} else {
|
||||||
|
_addAccountAction->setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AccountSettings::slotDeleteAccount()
|
void AccountSettings::slotDeleteAccount()
|
||||||
{
|
{
|
||||||
// Deleting the account potentially deletes 'this', so
|
// Deleting the account potentially deletes 'this', so
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ protected slots:
|
|||||||
void slotDeleteAccount();
|
void slotDeleteAccount();
|
||||||
void slotToggleSignInState();
|
void slotToggleSignInState();
|
||||||
void slotOpenAccountWizard();
|
void slotOpenAccountWizard();
|
||||||
|
void slotAccountAdded(AccountState *);
|
||||||
void refreshSelectiveSyncStatus();
|
void refreshSelectiveSyncStatus();
|
||||||
void slotMarkSubfolderEncrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
void slotMarkSubfolderEncrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
||||||
void slotMarkSubfolderDecrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
void slotMarkSubfolderDecrypted(const FolderStatusModel::SubFolderInfo* folderInfo);
|
||||||
@@ -99,6 +100,8 @@ protected slots:
|
|||||||
void doExpand();
|
void doExpand();
|
||||||
void slotLinkActivated(const QString &link);
|
void slotLinkActivated(const QString &link);
|
||||||
|
|
||||||
|
void slotMenuBeforeShow();
|
||||||
|
|
||||||
// Encryption Related Stuff.
|
// Encryption Related Stuff.
|
||||||
void slotShowMnemonic(const QString &mnemonic);
|
void slotShowMnemonic(const QString &mnemonic);
|
||||||
void slotNewMnemonicGenerated();
|
void slotNewMnemonicGenerated();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>581</width>
|
<width>582</width>
|
||||||
<height>557</height>
|
<height>557</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
@@ -184,6 +184,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<widget class="QToolButton" name="_accountToolbox">
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
#include "creds/httpcredentials.h"
|
#include "creds/httpcredentials.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "configfile.h"
|
#include "configfile.h"
|
||||||
#include "ocsnavigationappsjob.h"
|
|
||||||
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
@@ -28,7 +27,6 @@
|
|||||||
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
|
|
||||||
@@ -42,9 +40,9 @@ AccountState::AccountState(AccountPtr account)
|
|||||||
, _state(AccountState::Disconnected)
|
, _state(AccountState::Disconnected)
|
||||||
, _connectionStatus(ConnectionValidator::Undefined)
|
, _connectionStatus(ConnectionValidator::Undefined)
|
||||||
, _waitingForNewCredentials(false)
|
, _waitingForNewCredentials(false)
|
||||||
|
, _notificationsEtagResponseHeader("*")
|
||||||
, _maintenanceToConnectedDelay(60000 + (qrand() % (4 * 60000))) // 1-5min delay
|
, _maintenanceToConnectedDelay(60000 + (qrand() % (4 * 60000))) // 1-5min delay
|
||||||
, _remoteWipe(new RemoteWipe(_account))
|
, _remoteWipe(new RemoteWipe(_account))
|
||||||
, _hasTalk(false)
|
|
||||||
{
|
{
|
||||||
qRegisterMetaType<AccountState *>("AccountState*");
|
qRegisterMetaType<AccountState *>("AccountState*");
|
||||||
|
|
||||||
@@ -76,11 +74,6 @@ AccountPtr AccountState::account() const
|
|||||||
return _account;
|
return _account;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AccountState::hasTalk() const
|
|
||||||
{
|
|
||||||
return _hasTalk;
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountState::ConnectionStatus AccountState::connectionStatus() const
|
AccountState::ConnectionStatus AccountState::connectionStatus() const
|
||||||
{
|
{
|
||||||
return _connectionStatus;
|
return _connectionStatus;
|
||||||
@@ -244,9 +237,6 @@ void AccountState::checkConnectivity()
|
|||||||
// Use a small authed propfind as a minimal ping when we're
|
// Use a small authed propfind as a minimal ping when we're
|
||||||
// already connected.
|
// already connected.
|
||||||
conValidator->checkAuthentication();
|
conValidator->checkAuthentication();
|
||||||
|
|
||||||
// Get the Apps available on the server.
|
|
||||||
fetchNavigationApps();
|
|
||||||
} else {
|
} else {
|
||||||
// Check the server and then the auth.
|
// Check the server and then the auth.
|
||||||
|
|
||||||
@@ -277,7 +267,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
|
|||||||
// Come online gradually from 503 or maintenance mode
|
// Come online gradually from 503 or maintenance mode
|
||||||
if (status == ConnectionValidator::Connected
|
if (status == ConnectionValidator::Connected
|
||||||
&& (_connectionStatus == ConnectionValidator::ServiceUnavailable
|
&& (_connectionStatus == ConnectionValidator::ServiceUnavailable
|
||||||
|| _connectionStatus == ConnectionValidator::MaintenanceMode)) {
|
|| _connectionStatus == ConnectionValidator::MaintenanceMode)) {
|
||||||
if (!_timeSinceMaintenanceOver.isValid()) {
|
if (!_timeSinceMaintenanceOver.isValid()) {
|
||||||
qCInfo(lcAccountState) << "AccountState reconnection: delaying for"
|
qCInfo(lcAccountState) << "AccountState reconnection: delaying for"
|
||||||
<< _maintenanceToConnectedDelay << "ms";
|
<< _maintenanceToConnectedDelay << "ms";
|
||||||
@@ -303,9 +293,6 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
|
|||||||
case ConnectionValidator::Connected:
|
case ConnectionValidator::Connected:
|
||||||
if (_state != Connected) {
|
if (_state != Connected) {
|
||||||
setState(Connected);
|
setState(Connected);
|
||||||
|
|
||||||
// Get the Apps available on the server.
|
|
||||||
fetchNavigationApps();
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ConnectionValidator::Undefined:
|
case ConnectionValidator::Undefined:
|
||||||
@@ -418,110 +405,4 @@ std::unique_ptr<QSettings> AccountState::settings()
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AccountState::fetchNavigationApps(){
|
|
||||||
OcsNavigationAppsJob *job = new OcsNavigationAppsJob(_account);
|
|
||||||
job->addRawHeader("If-None-Match", navigationAppsEtagResponseHeader());
|
|
||||||
connect(job, &OcsNavigationAppsJob::appsJobFinished, this, &AccountState::slotNavigationAppsFetched);
|
|
||||||
connect(job, &OcsNavigationAppsJob::etagResponseHeaderReceived, this, &AccountState::slotEtagResponseHeaderReceived);
|
|
||||||
connect(job, &OcsNavigationAppsJob::ocsError, this, &AccountState::slotOcsError);
|
|
||||||
job->getNavigationApps();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AccountState::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){
|
|
||||||
if(statusCode == 200){
|
|
||||||
qCDebug(lcAccountState) << "New navigation apps ETag Response Header received " << value;
|
|
||||||
setNavigationAppsEtagResponseHeader(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AccountState::slotOcsError(int statusCode, const QString &message)
|
|
||||||
{
|
|
||||||
qCDebug(lcAccountState) << "Error " << statusCode << " while fetching new navigation apps: " << message;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AccountState::slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode)
|
|
||||||
{
|
|
||||||
if(_account){
|
|
||||||
if (statusCode == 304) {
|
|
||||||
qCWarning(lcAccountState) << "Status code " << statusCode << " Not Modified - No new navigation apps.";
|
|
||||||
} else {
|
|
||||||
_apps.clear();
|
|
||||||
_hasTalk = false;
|
|
||||||
|
|
||||||
if(!reply.isEmpty()){
|
|
||||||
auto element = reply.object().value("ocs").toObject().value("data");
|
|
||||||
auto navLinks = element.toArray();
|
|
||||||
|
|
||||||
if(navLinks.size() > 0){
|
|
||||||
foreach (const QJsonValue &value, navLinks) {
|
|
||||||
auto navLink = value.toObject();
|
|
||||||
|
|
||||||
AccountApp *app = new AccountApp(navLink.value("name").toString(), QUrl(navLink.value("href").toString()),
|
|
||||||
navLink.value("id").toString(), QUrl(navLink.value("icon").toString()));
|
|
||||||
|
|
||||||
_apps << app;
|
|
||||||
|
|
||||||
if(app->id() == QLatin1String("spreed"))
|
|
||||||
_hasTalk = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emit hasFetchedNavigationApps();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountAppList AccountState::appList() const
|
|
||||||
{
|
|
||||||
return _apps;
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountApp* AccountState::findApp(const QString &appId) const
|
|
||||||
{
|
|
||||||
if(!appId.isEmpty()) {
|
|
||||||
foreach(AccountApp *app, appList()) {
|
|
||||||
if(app->id() == appId)
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
AccountApp::AccountApp(const QString &name, const QUrl &url,
|
|
||||||
const QString &id, const QUrl &iconUrl,
|
|
||||||
QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
, _name(name)
|
|
||||||
, _url(url)
|
|
||||||
, _id(id)
|
|
||||||
, _iconUrl(iconUrl)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AccountApp::name() const
|
|
||||||
{
|
|
||||||
return _name;
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl AccountApp::url() const
|
|
||||||
{
|
|
||||||
return _url;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString AccountApp::id() const
|
|
||||||
{
|
|
||||||
return _id;
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl AccountApp::iconUrl() const
|
|
||||||
{
|
|
||||||
return _iconUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|||||||
@@ -29,11 +29,9 @@ namespace OCC {
|
|||||||
|
|
||||||
class AccountState;
|
class AccountState;
|
||||||
class Account;
|
class Account;
|
||||||
class AccountApp;
|
|
||||||
class RemoteWipe;
|
class RemoteWipe;
|
||||||
|
|
||||||
typedef QExplicitlySharedDataPointer<AccountState> AccountStatePtr;
|
typedef QExplicitlySharedDataPointer<AccountState> AccountStatePtr;
|
||||||
typedef QList<AccountApp*> AccountAppList;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Extra info about an ownCloud server account.
|
* @brief Extra info about an ownCloud server account.
|
||||||
@@ -103,11 +101,6 @@ public:
|
|||||||
|
|
||||||
bool isSignedOut() const;
|
bool isSignedOut() const;
|
||||||
|
|
||||||
bool hasTalk() const;
|
|
||||||
|
|
||||||
AccountAppList appList() const;
|
|
||||||
AccountApp* findApp(const QString &appId) const;
|
|
||||||
|
|
||||||
/** A user-triggered sign out which disconnects, stops syncs
|
/** A user-triggered sign out which disconnects, stops syncs
|
||||||
* for the account and forgets the password. */
|
* for the account and forgets the password. */
|
||||||
void signOutByUi();
|
void signOutByUi();
|
||||||
@@ -168,12 +161,10 @@ public slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void setState(State state);
|
void setState(State state);
|
||||||
void fetchNavigationApps();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void stateChanged(int state);
|
void stateChanged(int state);
|
||||||
void isConnectedChanged();
|
void isConnectedChanged();
|
||||||
void hasFetchedNavigationApps();
|
|
||||||
|
|
||||||
protected Q_SLOTS:
|
protected Q_SLOTS:
|
||||||
void slotConnectionValidatorResult(ConnectionValidator::Status status, const QStringList &errors);
|
void slotConnectionValidatorResult(ConnectionValidator::Status status, const QStringList &errors);
|
||||||
@@ -185,17 +176,12 @@ protected Q_SLOTS:
|
|||||||
void slotCredentialsFetched(AbstractCredentials *creds);
|
void slotCredentialsFetched(AbstractCredentials *creds);
|
||||||
void slotCredentialsAsked(AbstractCredentials *creds);
|
void slotCredentialsAsked(AbstractCredentials *creds);
|
||||||
|
|
||||||
void slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode);
|
|
||||||
void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode);
|
|
||||||
void slotOcsError(int statusCode, const QString &message);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AccountPtr _account;
|
AccountPtr _account;
|
||||||
State _state;
|
State _state;
|
||||||
ConnectionStatus _connectionStatus;
|
ConnectionStatus _connectionStatus;
|
||||||
QStringList _connectionErrors;
|
QStringList _connectionErrors;
|
||||||
bool _waitingForNewCredentials;
|
bool _waitingForNewCredentials;
|
||||||
bool _hasTalk;
|
|
||||||
QElapsedTimer _timeSinceLastETagCheck;
|
QElapsedTimer _timeSinceLastETagCheck;
|
||||||
QPointer<ConnectionValidator> _connectionValidator;
|
QPointer<ConnectionValidator> _connectionValidator;
|
||||||
QByteArray _notificationsEtagResponseHeader;
|
QByteArray _notificationsEtagResponseHeader;
|
||||||
@@ -219,34 +205,7 @@ private:
|
|||||||
*/
|
*/
|
||||||
RemoteWipe *_remoteWipe;
|
RemoteWipe *_remoteWipe;
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds the App names and URLs available on the server
|
|
||||||
*/
|
|
||||||
AccountAppList _apps;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class AccountApp : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
AccountApp(const QString &name, const QUrl &url,
|
|
||||||
const QString &id, const QUrl &iconUrl,
|
|
||||||
QObject* parent = 0);
|
|
||||||
|
|
||||||
QString name() const;
|
|
||||||
QUrl url() const;
|
|
||||||
QString id() const;
|
|
||||||
QUrl iconUrl() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString _name;
|
|
||||||
QUrl _url;
|
|
||||||
|
|
||||||
QString _id;
|
|
||||||
QUrl _iconUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(OCC::AccountState *)
|
Q_DECLARE_METATYPE(OCC::AccountState *)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#include <QtCore>
|
#include <QtCore>
|
||||||
|
|
||||||
#include "ActivityData.h"
|
#include "activitydata.h"
|
||||||
|
|
||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
@@ -56,7 +56,6 @@ public:
|
|||||||
|
|
||||||
Type _type;
|
Type _type;
|
||||||
qlonglong _id;
|
qlonglong _id;
|
||||||
QString _fileAction;
|
|
||||||
QString _objectType;
|
QString _objectType;
|
||||||
QString _subject;
|
QString _subject;
|
||||||
QString _message;
|
QString _message;
|
||||||
@@ -65,8 +64,6 @@ public:
|
|||||||
QUrl _link;
|
QUrl _link;
|
||||||
QDateTime _dateTime;
|
QDateTime _dateTime;
|
||||||
QString _accName;
|
QString _accName;
|
||||||
QString _icon;
|
|
||||||
QString _iconData;
|
|
||||||
|
|
||||||
// Stores information about the error
|
// Stores information about the error
|
||||||
int _status;
|
int _status;
|
||||||
407
src/gui/activityitemdelegate.cpp
Normal file
@@ -0,0 +1,407 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
|
||||||
|
* Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "activityitemdelegate.h"
|
||||||
|
#include "activitylistmodel.h"
|
||||||
|
#include "folderstatusmodel.h"
|
||||||
|
#include "folderman.h"
|
||||||
|
#include "accountstate.h"
|
||||||
|
#include "activitydata.h"
|
||||||
|
#include <theme.h>
|
||||||
|
#include <account.h>
|
||||||
|
|
||||||
|
#include <QFileIconProvider>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
|
#define FIXME_USE_HIGH_DPI_RATIO
|
||||||
|
#ifdef FIXME_USE_HIGH_DPI_RATIO
|
||||||
|
// FIXME: Find a better way to calculate the text width on high-dpi displays (Retina).
|
||||||
|
#include <QDesktopWidget>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define HASQT5_11 (QT_VERSION >= QT_VERSION_CHECK(5,11,0))
|
||||||
|
|
||||||
|
namespace OCC {
|
||||||
|
|
||||||
|
int ActivityItemDelegate::_iconHeight = 0;
|
||||||
|
int ActivityItemDelegate::_margin = 0;
|
||||||
|
int ActivityItemDelegate::_primaryButtonWidth = 0;
|
||||||
|
int ActivityItemDelegate::_secondaryButtonWidth = 0;
|
||||||
|
int ActivityItemDelegate::_spaceBetweenButtons = 0;
|
||||||
|
int ActivityItemDelegate::_timeWidth = 0;
|
||||||
|
int ActivityItemDelegate::_buttonHeight = 0;
|
||||||
|
const QString ActivityItemDelegate::_remote_share("remote_share");
|
||||||
|
const QString ActivityItemDelegate::_call("call");
|
||||||
|
|
||||||
|
ActivityItemDelegate::ActivityItemDelegate()
|
||||||
|
: QStyledItemDelegate()
|
||||||
|
{
|
||||||
|
customizeStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ActivityItemDelegate::iconHeight()
|
||||||
|
{
|
||||||
|
if (_iconHeight == 0) {
|
||||||
|
QStyleOptionViewItem option;
|
||||||
|
QFont font = option.font;
|
||||||
|
|
||||||
|
QFontMetrics fm(font);
|
||||||
|
|
||||||
|
_iconHeight = qRound(fm.height() / 5.0 * 8.0);
|
||||||
|
}
|
||||||
|
return _iconHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ActivityItemDelegate::rowHeight()
|
||||||
|
{
|
||||||
|
if (_margin == 0) {
|
||||||
|
QStyleOptionViewItem opt;
|
||||||
|
|
||||||
|
QFont f = opt.font;
|
||||||
|
QFontMetrics fm(f);
|
||||||
|
|
||||||
|
_margin = fm.height() / 2;
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
_margin += 5;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return iconHeight() + 5 * _margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize ActivityItemDelegate::sizeHint(const QStyleOptionViewItem &option,
|
||||||
|
const QModelIndex & /* index */) const
|
||||||
|
{
|
||||||
|
QFont font = option.font;
|
||||||
|
|
||||||
|
return QSize(0, rowHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||||
|
const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
QStyledItemDelegate::paint(painter, option, index);
|
||||||
|
QFont font = option.font;
|
||||||
|
QFontMetrics fm(font);
|
||||||
|
int margin = fm.height() / 2.5;
|
||||||
|
painter->save();
|
||||||
|
int iconSize = 16;
|
||||||
|
int iconOffset = qRound(fm.height() / 4.0 * 7.0);
|
||||||
|
int offset = 4;
|
||||||
|
const bool isSelected = (option.state & QStyle::State_Selected);
|
||||||
|
#ifdef FIXME_USE_HIGH_DPI_RATIO
|
||||||
|
// FIXME: Find a better way to calculate the text width on high-dpi displays (Retina).
|
||||||
|
const int device_pixel_ration = QApplication::desktop()->devicePixelRatio();
|
||||||
|
int pixel_ratio = (device_pixel_ration > 1 ? device_pixel_ration : 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// get the data
|
||||||
|
Activity::Type activityType = qvariant_cast<Activity::Type>(index.data(ActionRole));
|
||||||
|
QIcon actionIcon;
|
||||||
|
const ActivityListModel::ActionIcon icn = qvariant_cast<ActivityListModel::ActionIcon>(index.data(ActionIconRole));
|
||||||
|
switch(icn.iconType) {
|
||||||
|
case ActivityListModel::ActivityIconType::iconUseCached: actionIcon = icn.cachedIcon; break;
|
||||||
|
case ActivityListModel::ActivityIconType::iconActivity: actionIcon = (isSelected ? _iconActivity_sel : _iconActivity); break;
|
||||||
|
case ActivityListModel::ActivityIconType::iconBell: actionIcon = (isSelected ? _iconBell_sel : _iconBell); break;
|
||||||
|
case ActivityListModel::ActivityIconType::iconStateError: actionIcon = _iconStateError; break;
|
||||||
|
case ActivityListModel::ActivityIconType::iconStateWarning: actionIcon = _iconStateWarning; break;
|
||||||
|
case ActivityListModel::ActivityIconType::iconStateInfo: actionIcon = _iconStateInfo; break;
|
||||||
|
case ActivityListModel::ActivityIconType::iconStateSync: actionIcon = _iconStateSync; break;
|
||||||
|
}
|
||||||
|
QString objectType = qvariant_cast<QString>(index.data(ObjectTypeRole));
|
||||||
|
QString actionText = qvariant_cast<QString>(index.data(ActionTextRole));
|
||||||
|
QString messageText = qvariant_cast<QString>(index.data(MessageRole));
|
||||||
|
QList<QVariant> customList = index.data(ActionsLinksRole).toList();
|
||||||
|
QString timeText = qvariant_cast<QString>(index.data(PointInTimeRole));
|
||||||
|
bool accountOnline = qvariant_cast<bool>(index.data(AccountConnectedRole));
|
||||||
|
|
||||||
|
// activity/notification icons
|
||||||
|
QRect actionIconRect = option.rect;
|
||||||
|
actionIconRect.setLeft(option.rect.left() + iconOffset/3);
|
||||||
|
actionIconRect.setRight(option.rect.left() + iconOffset);
|
||||||
|
actionIconRect.setTop(option.rect.top() + qRound((option.rect.height() - 16)/3.0));
|
||||||
|
|
||||||
|
// subject text rect
|
||||||
|
QRect actionTextBox = actionIconRect;
|
||||||
|
#if (HASQT5_11)
|
||||||
|
int actionTextBoxWidth = fm.horizontalAdvance(actionText);
|
||||||
|
#else
|
||||||
|
int actionTextBoxWidth = fm.width(actionText);
|
||||||
|
#endif
|
||||||
|
actionTextBox.setTop(option.rect.top() + margin + offset/2);
|
||||||
|
actionTextBox.setHeight(fm.height());
|
||||||
|
actionTextBox.setLeft(actionIconRect.right() + margin);
|
||||||
|
#ifdef FIXME_USE_HIGH_DPI_RATIO
|
||||||
|
// FIXME: Find a better way to calculate the text width on high-dpi displays (Retina).
|
||||||
|
actionTextBoxWidth *= pixel_ratio;
|
||||||
|
#endif
|
||||||
|
actionTextBox.setRight(actionTextBox.left() + actionTextBoxWidth + margin);
|
||||||
|
|
||||||
|
// message text rect
|
||||||
|
QRect messageTextBox = actionTextBox;
|
||||||
|
#if (HASQT5_11)
|
||||||
|
int messageTextWidth = fm.horizontalAdvance(messageText);
|
||||||
|
#else
|
||||||
|
int messageTextWidth = fm.width(messageText);
|
||||||
|
#endif
|
||||||
|
int messageTextTop = option.rect.top() + fm.height() + margin;
|
||||||
|
if(actionText.isEmpty()) messageTextTop = option.rect.top() + margin + offset/2;
|
||||||
|
messageTextBox.setTop(messageTextTop);
|
||||||
|
messageTextBox.setHeight(fm.height());
|
||||||
|
messageTextBox.setBottom(messageTextBox.top() + fm.height());
|
||||||
|
messageTextBox.setRight(messageTextBox.left() + messageTextWidth + margin);
|
||||||
|
if(messageText.isEmpty()){
|
||||||
|
messageTextBox.setHeight(0);
|
||||||
|
messageTextBox.setBottom(messageTextBox.top());
|
||||||
|
}
|
||||||
|
|
||||||
|
// time box rect
|
||||||
|
QRect timeBox = messageTextBox;
|
||||||
|
#if (HASQT5_11)
|
||||||
|
int timeTextWidth = fm.horizontalAdvance(timeText);
|
||||||
|
#else
|
||||||
|
int timeTextWidth = fm.width(timeText);
|
||||||
|
#endif
|
||||||
|
int timeTop = option.rect.top() + fm.height() + fm.height() + margin + offset/2;
|
||||||
|
if(messageText.isEmpty() || actionText.isEmpty())
|
||||||
|
timeTop = option.rect.top() + fm.height() + margin;
|
||||||
|
timeBox.setTop(timeTop);
|
||||||
|
timeBox.setHeight(fm.height());
|
||||||
|
timeBox.setBottom(timeBox.top() + fm.height());
|
||||||
|
#ifdef FIXME_USE_HIGH_DPI_RATIO
|
||||||
|
// FIXME: Find a better way to calculate the text width on high-dpi displays (Retina).
|
||||||
|
timeTextWidth *= pixel_ratio;
|
||||||
|
#endif
|
||||||
|
timeBox.setRight(timeBox.left() + timeTextWidth + margin);
|
||||||
|
|
||||||
|
// buttons - default values
|
||||||
|
int rightMargin = margin;
|
||||||
|
int leftMargin = margin * offset;
|
||||||
|
int top = option.rect.top() + margin;
|
||||||
|
int buttonSize = option.rect.height()/2;
|
||||||
|
int right = option.rect.right() - rightMargin;
|
||||||
|
int left = right - buttonSize;
|
||||||
|
|
||||||
|
QStyleOptionButton secondaryButton;
|
||||||
|
secondaryButton.rect = option.rect;
|
||||||
|
secondaryButton.features |= QStyleOptionButton::Flat;
|
||||||
|
secondaryButton.state |= QStyle::State_None;
|
||||||
|
secondaryButton.rect.setLeft(left);
|
||||||
|
secondaryButton.rect.setRight(right);
|
||||||
|
secondaryButton.rect.setTop(top + margin);
|
||||||
|
secondaryButton.rect.setHeight(iconSize);
|
||||||
|
|
||||||
|
QStyleOptionButton primaryButton;
|
||||||
|
primaryButton.rect = option.rect;
|
||||||
|
primaryButton.features |= QStyleOptionButton::DefaultButton;
|
||||||
|
primaryButton.state |= QStyle::State_Raised;
|
||||||
|
primaryButton.rect.setTop(top);
|
||||||
|
primaryButton.rect.setHeight(buttonSize);
|
||||||
|
|
||||||
|
right = secondaryButton.rect.left() - rightMargin;
|
||||||
|
left = secondaryButton.rect.left() - leftMargin;
|
||||||
|
|
||||||
|
primaryButton.rect.setRight(right);
|
||||||
|
|
||||||
|
if(activityType == Activity::Type::NotificationType){
|
||||||
|
|
||||||
|
// Secondary will be 'Dismiss' or '...' multiple options button
|
||||||
|
secondaryButton.icon = (isSelected ? _iconClose_sel : _iconClose);
|
||||||
|
if(customList.size() > 1)
|
||||||
|
secondaryButton.icon = (isSelected ? _iconMore_sel : _iconMore);
|
||||||
|
secondaryButton.iconSize = QSize(iconSize, iconSize);
|
||||||
|
|
||||||
|
// Primary button will be 'More Information' or 'Accept'
|
||||||
|
primaryButton.text = tr("More information");
|
||||||
|
if(objectType == _remote_share) primaryButton.text = tr("Accept");
|
||||||
|
if(objectType == _call) primaryButton.text = tr("Join");
|
||||||
|
|
||||||
|
#if (HASQT5_11)
|
||||||
|
primaryButton.rect.setLeft(left - margin * 2 - fm.horizontalAdvance(primaryButton.text));
|
||||||
|
#else
|
||||||
|
primaryButton.rect.setLeft(left - margin * 2 - fm.width(primaryButton.text));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// save info to be able to filter mouse clicks
|
||||||
|
_buttonHeight = buttonSize;
|
||||||
|
_primaryButtonWidth = primaryButton.rect.size().width();
|
||||||
|
_secondaryButtonWidth = secondaryButton.rect.size().width();
|
||||||
|
_spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right();
|
||||||
|
|
||||||
|
} else if(activityType == Activity::SyncResultType){
|
||||||
|
|
||||||
|
// Secondary will be 'open file manager' with the folder icon
|
||||||
|
secondaryButton.icon = _iconFolder;
|
||||||
|
secondaryButton.iconSize = QSize(iconSize, iconSize);
|
||||||
|
|
||||||
|
// Primary button will be 'open browser'
|
||||||
|
primaryButton.text = tr("Open Browser");
|
||||||
|
|
||||||
|
#if (HASQT5_11)
|
||||||
|
primaryButton.rect.setLeft(left - margin * 2 - fm.horizontalAdvance(primaryButton.text));
|
||||||
|
#else
|
||||||
|
primaryButton.rect.setLeft(left - margin * 2 - fm.width(primaryButton.text));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// save info to be able to filter mouse clicks
|
||||||
|
_buttonHeight = buttonSize;
|
||||||
|
_primaryButtonWidth = primaryButton.rect.size().width();
|
||||||
|
_secondaryButtonWidth = secondaryButton.rect.size().width();
|
||||||
|
_spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right();
|
||||||
|
|
||||||
|
} else if(activityType == Activity::SyncFileItemType){
|
||||||
|
|
||||||
|
// Secondary will be 'open file manager' with the folder icon
|
||||||
|
secondaryButton.icon = _iconFolder;
|
||||||
|
secondaryButton.iconSize = QSize(iconSize, iconSize);
|
||||||
|
|
||||||
|
// No primary button on this case
|
||||||
|
// Whatever error we have at this case it is local, there is no point on opening the browser
|
||||||
|
_primaryButtonWidth = 0;
|
||||||
|
_secondaryButtonWidth = secondaryButton.rect.size().width();
|
||||||
|
_spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
_spaceBetweenButtons = leftMargin;
|
||||||
|
_primaryButtonWidth = 0;
|
||||||
|
_secondaryButtonWidth = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw the icon
|
||||||
|
QPixmap pm = actionIcon.pixmap(iconSize, iconSize, QIcon::Normal);
|
||||||
|
painter->drawPixmap(QPoint(actionIconRect.left(), actionIconRect.top()), pm);
|
||||||
|
|
||||||
|
// change pen color if use is not online
|
||||||
|
QPalette p = option.palette;
|
||||||
|
if(!accountOnline)
|
||||||
|
p.setCurrentColorGroup(QPalette::Disabled);
|
||||||
|
|
||||||
|
// change pen color if the line is selected
|
||||||
|
if (isSelected)
|
||||||
|
painter->setPen(p.color(QPalette::HighlightedText));
|
||||||
|
else
|
||||||
|
painter->setPen(p.color(QPalette::Text));
|
||||||
|
|
||||||
|
// calculate space for text - use the max possible before using the elipses
|
||||||
|
int spaceLeftForText = option.rect.width() - (actionIconRect.width() + margin + rightMargin + leftMargin) -
|
||||||
|
(_primaryButtonWidth + _secondaryButtonWidth + _spaceBetweenButtons);
|
||||||
|
|
||||||
|
// draw the subject
|
||||||
|
const QString elidedAction = fm.elidedText(actionText, Qt::ElideRight, spaceLeftForText);
|
||||||
|
painter->drawText(actionTextBox, elidedAction);
|
||||||
|
|
||||||
|
// draw the buttons
|
||||||
|
if(activityType == Activity::Type::NotificationType || activityType == Activity::Type::SyncResultType) {
|
||||||
|
primaryButton.palette = p;
|
||||||
|
if (isSelected)
|
||||||
|
primaryButton.palette.setColor(QPalette::ButtonText, p.color(QPalette::HighlightedText));
|
||||||
|
else
|
||||||
|
primaryButton.palette.setColor(QPalette::ButtonText, p.color(QPalette::Text));
|
||||||
|
|
||||||
|
QApplication::style()->drawControl(QStyle::CE_PushButton, &primaryButton, painter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since they are errors on local syncing, there is nothing to do in the server
|
||||||
|
if(activityType != Activity::Type::ActivityType)
|
||||||
|
QApplication::style()->drawControl(QStyle::CE_PushButton, &secondaryButton, painter);
|
||||||
|
|
||||||
|
// draw the message
|
||||||
|
// change pen color for the message
|
||||||
|
if(!messageText.isEmpty()){
|
||||||
|
const QString elidedMessage = fm.elidedText(messageText, Qt::ElideRight, spaceLeftForText);
|
||||||
|
painter->drawText(messageTextBox, elidedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// change pen color for the time
|
||||||
|
if (isSelected)
|
||||||
|
painter->setPen(p.color(QPalette::Disabled, QPalette::HighlightedText));
|
||||||
|
else
|
||||||
|
painter->setPen(p.color(QPalette::Disabled, QPalette::Text));
|
||||||
|
|
||||||
|
// draw the time
|
||||||
|
const QString elidedTime = fm.elidedText(timeText, Qt::ElideRight, spaceLeftForText);
|
||||||
|
painter->drawText(timeBox, elidedTime);
|
||||||
|
|
||||||
|
painter->restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActivityItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||||
|
const QStyleOptionViewItem &option, const QModelIndex &index)
|
||||||
|
{
|
||||||
|
Activity::Type activityType = qvariant_cast<Activity::Type>(index.data(ActionRole));
|
||||||
|
if(activityType != Activity::Type::ActivityType){
|
||||||
|
if (event->type() == QEvent::MouseButtonRelease){
|
||||||
|
QMouseEvent *mouseEvent = (QMouseEvent*)event;
|
||||||
|
if(mouseEvent){
|
||||||
|
int mouseEventX = mouseEvent->x();
|
||||||
|
int mouseEventY = mouseEvent->y();
|
||||||
|
int buttonsWidth = _primaryButtonWidth + _spaceBetweenButtons + _secondaryButtonWidth;
|
||||||
|
int x = option.rect.left() + option.rect.width() - buttonsWidth - _timeWidth;
|
||||||
|
int y = option.rect.top();
|
||||||
|
|
||||||
|
// clickable area for ...
|
||||||
|
if (mouseEventX > x && mouseEventX < x + buttonsWidth){
|
||||||
|
if(mouseEventY > y && mouseEventY < y + _buttonHeight){
|
||||||
|
|
||||||
|
// ...primary button ('more information' or 'accept' on notifications or 'open browser' on errors)
|
||||||
|
if (mouseEventX > x && mouseEventX < x + _primaryButtonWidth){
|
||||||
|
emit primaryButtonClickedOnItemView(index);
|
||||||
|
|
||||||
|
// ...secondary button ('dismiss' on notifications or 'open file manager' on errors)
|
||||||
|
} else {
|
||||||
|
x += _primaryButtonWidth + _spaceBetweenButtons;
|
||||||
|
if (mouseEventX > x && mouseEventX < x + _secondaryButtonWidth)
|
||||||
|
emit secondaryButtonClickedOnItemView(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QStyledItemDelegate::editorEvent(event, model, option, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityItemDelegate::slotStyleChanged()
|
||||||
|
{
|
||||||
|
customizeStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityItemDelegate::customizeStyle()
|
||||||
|
{
|
||||||
|
QPalette pal;
|
||||||
|
pal.setColor(QPalette::Base, QColor(0,0,0)); // use dark background colour to invert icons
|
||||||
|
|
||||||
|
_iconClose = Theme::createColorAwareIcon(QLatin1String(":/client/resources/close.svg"));
|
||||||
|
_iconClose_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/close.svg"), pal);
|
||||||
|
_iconMore = Theme::createColorAwareIcon(QLatin1String(":/client/resources/more.svg"));
|
||||||
|
_iconMore_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/more.svg"), pal);
|
||||||
|
|
||||||
|
_iconFolder = QIcon(QLatin1String(":/client/resources/folder.svg"));
|
||||||
|
|
||||||
|
_iconActivity = Theme::createColorAwareIcon(QLatin1String(":/client/resources/activity.png"));
|
||||||
|
_iconActivity_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/activity.png"), pal);
|
||||||
|
_iconBell = Theme::createColorAwareIcon(QLatin1String(":/client/resources/bell.svg"));
|
||||||
|
_iconBell_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/bell.svg"), pal);
|
||||||
|
|
||||||
|
_iconStateError = QIcon(QLatin1String(":/client/resources/state-error.svg"));
|
||||||
|
_iconStateWarning = QIcon(QLatin1String(":/client/resources/state-warning.svg"));
|
||||||
|
_iconStateInfo = QIcon(QLatin1String(":/client/resources/state-info.svg"));
|
||||||
|
_iconStateSync = QIcon(QLatin1String(":/client/resources/state-sync.svg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace OCC
|
||||||
94
src/gui/activityitemdelegate.h
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) by Klaas Freitag <freitag@kde.org>
|
||||||
|
* Copyright (C) by Olivier Goffart <ogoffart@woboq.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
|
||||||
|
class QMouseEvent;
|
||||||
|
|
||||||
|
namespace OCC {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The ActivityItemDelegate class
|
||||||
|
* @ingroup gui
|
||||||
|
*/
|
||||||
|
class ActivityItemDelegate : public QStyledItemDelegate
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum datarole { ActionIconRole = Qt::UserRole + 1,
|
||||||
|
UserIconRole,
|
||||||
|
AccountRole,
|
||||||
|
ObjectTypeRole,
|
||||||
|
ActionsLinksRole,
|
||||||
|
ActionTextRole,
|
||||||
|
ActionRole,
|
||||||
|
MessageRole,
|
||||||
|
PathRole,
|
||||||
|
LinkRole,
|
||||||
|
PointInTimeRole,
|
||||||
|
AccountConnectedRole,
|
||||||
|
SyncFileStatusRole };
|
||||||
|
|
||||||
|
ActivityItemDelegate();
|
||||||
|
|
||||||
|
void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override;
|
||||||
|
QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override;
|
||||||
|
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
|
||||||
|
const QModelIndex &index) override;
|
||||||
|
|
||||||
|
static int rowHeight();
|
||||||
|
static int iconHeight();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void slotStyleChanged();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void primaryButtonClickedOnItemView(const QModelIndex &index);
|
||||||
|
void secondaryButtonClickedOnItemView(const QModelIndex &index);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void customizeStyle();
|
||||||
|
|
||||||
|
static int _margin;
|
||||||
|
static int _iconHeight;
|
||||||
|
static int _primaryButtonWidth;
|
||||||
|
static int _secondaryButtonWidth;
|
||||||
|
static int _spaceBetweenButtons;
|
||||||
|
static int _timeWidth;
|
||||||
|
static int _buttonHeight;
|
||||||
|
static const QString _remote_share;
|
||||||
|
static const QString _call;
|
||||||
|
|
||||||
|
QIcon _iconClose;
|
||||||
|
QIcon _iconClose_sel;
|
||||||
|
QIcon _iconMore;
|
||||||
|
QIcon _iconMore_sel;
|
||||||
|
|
||||||
|
QIcon _iconFolder;
|
||||||
|
|
||||||
|
QIcon _iconActivity;
|
||||||
|
QIcon _iconActivity_sel;
|
||||||
|
QIcon _iconBell;
|
||||||
|
QIcon _iconBell_sel;
|
||||||
|
|
||||||
|
QIcon _iconStateError;
|
||||||
|
QIcon _iconStateWarning;
|
||||||
|
QIcon _iconStateInfo;
|
||||||
|
QIcon _iconStateSync;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace OCC
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
#include <QtCore>
|
#include <QtCore>
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
#include <QIcon>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
|
||||||
@@ -22,44 +23,34 @@
|
|||||||
#include "accountstate.h"
|
#include "accountstate.h"
|
||||||
#include "accountmanager.h"
|
#include "accountmanager.h"
|
||||||
#include "folderman.h"
|
#include "folderman.h"
|
||||||
#include "iconjob.h"
|
|
||||||
#include "accessmanager.h"
|
#include "accessmanager.h"
|
||||||
|
#include "activityitemdelegate.h"
|
||||||
|
|
||||||
#include "ActivityData.h"
|
#include "activitydata.h"
|
||||||
#include "ActivityListModel.h"
|
#include "activitylistmodel.h"
|
||||||
|
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
|
|
||||||
|
#include "servernotificationhandler.h"
|
||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(lcActivity, "nextcloud.gui.activity", QtInfoMsg)
|
Q_LOGGING_CATEGORY(lcActivity, "nextcloud.gui.activity", QtInfoMsg)
|
||||||
|
|
||||||
ActivityListModel::ActivityListModel(AccountState *accountState, QObject *parent)
|
ActivityListModel::ActivityListModel(AccountState *accountState, QWidget *parent)
|
||||||
: QAbstractListModel()
|
: QAbstractListModel(parent)
|
||||||
, _accountState(accountState)
|
, _accountState(accountState)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray> ActivityListModel::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> roles;
|
|
||||||
roles[DisplayPathRole] = "displaypath";
|
|
||||||
roles[PathRole] = "path";
|
|
||||||
roles[LinkRole] = "link";
|
|
||||||
roles[MessageRole] = "message";
|
|
||||||
roles[ActionRole] = "type";
|
|
||||||
roles[ActionIconRole] = "icon";
|
|
||||||
roles[ActionTextRole] = "subject";
|
|
||||||
roles[ActionTextColorRole] = "activityTextTitleColor";
|
|
||||||
roles[ObjectTypeRole] = "objectType";
|
|
||||||
roles[PointInTimeRole] = "dateTime";
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|
QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
Activity a;
|
Activity a;
|
||||||
|
|
||||||
|
// filter the get action here
|
||||||
|
// send only the text of the get action
|
||||||
|
// if there is more than one send the icon? the ...
|
||||||
|
|
||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
@@ -70,44 +61,25 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|
|||||||
QStringList list;
|
QStringList list;
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case DisplayPathRole:
|
case ActivityItemDelegate::PathRole:
|
||||||
if (!a._file.isEmpty()) {
|
if(!a._file.isEmpty()){
|
||||||
auto folder = FolderMan::instance()->folder(a._folder);
|
auto folder = FolderMan::instance()->folder(a._folder);
|
||||||
QString relPath(a._file);
|
QString relPath(a._file);
|
||||||
if (folder) {
|
if(folder) relPath.prepend(folder->remotePath());
|
||||||
relPath.prepend(folder->remotePath());
|
|
||||||
}
|
|
||||||
list = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());
|
list = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());
|
||||||
if (list.count() > 0) {
|
if (list.count() > 0) {
|
||||||
if (relPath.startsWith('/') || relPath.startsWith('\\')) {
|
return QVariant(list.at(0));
|
||||||
return relPath.remove(0, 1);
|
|
||||||
} else {
|
|
||||||
return relPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QString();
|
|
||||||
case PathRole:
|
|
||||||
if (!a._file.isEmpty()) {
|
|
||||||
auto folder = FolderMan::instance()->folder(a._folder);
|
|
||||||
QString relPath(a._file);
|
|
||||||
if (folder)
|
|
||||||
relPath.prepend(folder->remotePath());
|
|
||||||
list = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());
|
|
||||||
if (list.count() > 0) {
|
|
||||||
QString path = "file:///" + QString(list.at(0));
|
|
||||||
return QUrl(path);
|
|
||||||
}
|
}
|
||||||
// File does not exist anymore? Let's try to open its path
|
// File does not exist anymore? Let's try to open its path
|
||||||
if (QFileInfo(relPath).exists()) {
|
if(QFileInfo(relPath).exists()) {
|
||||||
list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(relPath).path(), ast->account());
|
list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(relPath).path(), ast->account());
|
||||||
if (list.count() > 0) {
|
if (list.count() > 0) {
|
||||||
return QVariant(list.at(0));
|
return QVariant(list.at(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return QString();
|
return QVariant();
|
||||||
case ActionsLinksRole: {
|
case ActivityItemDelegate::ActionsLinksRole:{
|
||||||
QList<QVariant> customList;
|
QList<QVariant> customList;
|
||||||
foreach (ActivityLink customItem, a._links) {
|
foreach (ActivityLink customItem, a._links) {
|
||||||
QVariant customVariant;
|
QVariant customVariant;
|
||||||
@@ -116,81 +88,59 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
return customList;
|
return customList;
|
||||||
}
|
}
|
||||||
case ActionIconRole: {
|
case ActivityItemDelegate::ActionIconRole:{
|
||||||
if (a._type == Activity::NotificationType) {
|
ActionIcon actionIcon;
|
||||||
return "qrc:///client/theme/black/bell.svg";
|
if(a._type == Activity::NotificationType){
|
||||||
} else if (a._type == Activity::SyncResultType) {
|
QIcon cachedIcon = ServerNotificationHandler::iconCache.value(a._id);
|
||||||
return "qrc:///client/theme/black/state-error.svg";
|
if(!cachedIcon.isNull()) {
|
||||||
} else if (a._type == Activity::SyncFileItemType) {
|
actionIcon.iconType = ActivityIconType::iconUseCached;
|
||||||
if (a._status == SyncFileItem::NormalError
|
actionIcon.cachedIcon = cachedIcon;
|
||||||
|| a._status == SyncFileItem::FatalError
|
|
||||||
|| a._status == SyncFileItem::DetailError
|
|
||||||
|| a._status == SyncFileItem::BlacklistedError) {
|
|
||||||
return "qrc:///client/theme/black/state-error.svg";
|
|
||||||
} else if (a._status == SyncFileItem::SoftError
|
|
||||||
|| a._status == SyncFileItem::Conflict
|
|
||||||
|| a._status == SyncFileItem::Restoration
|
|
||||||
|| a._status == SyncFileItem::FileLocked) {
|
|
||||||
return "qrc:///client/theme/black/state-warning.svg";
|
|
||||||
} else if (a._status == SyncFileItem::FileIgnored) {
|
|
||||||
return "qrc:///client/theme/black/state-info.svg";
|
|
||||||
} else {
|
} else {
|
||||||
// File sync successful
|
actionIcon.iconType = ActivityIconType::iconBell;
|
||||||
if (a._fileAction == "file_created") {
|
|
||||||
return "qrc:///client/resources/add-color.svg";
|
|
||||||
} else if (a._fileAction == "file_deleted") {
|
|
||||||
return "qrc:///client/resources/delete-color.svg";
|
|
||||||
} else {
|
|
||||||
return "qrc:///client/resources/change.svg";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else if(a._type == Activity::SyncResultType){
|
||||||
|
actionIcon.iconType = ActivityIconType::iconStateError;
|
||||||
|
} else if(a._type == Activity::SyncFileItemType){
|
||||||
|
if(a._status == SyncFileItem::NormalError
|
||||||
|
|| a._status == SyncFileItem::FatalError
|
||||||
|
|| a._status == SyncFileItem::DetailError
|
||||||
|
|| a._status == SyncFileItem::BlacklistedError) {
|
||||||
|
actionIcon.iconType = ActivityIconType::iconStateError;
|
||||||
|
} else if(a._status == SyncFileItem::SoftError
|
||||||
|
|| a._status == SyncFileItem::Conflict
|
||||||
|
|| a._status == SyncFileItem::Restoration
|
||||||
|
|| a._status == SyncFileItem::FileLocked){
|
||||||
|
actionIcon.iconType = ActivityIconType::iconStateWarning;
|
||||||
|
} else if(a._status == SyncFileItem::FileIgnored){
|
||||||
|
actionIcon.iconType = ActivityIconType::iconStateInfo;
|
||||||
|
} else {
|
||||||
|
actionIcon.iconType = ActivityIconType::iconStateSync;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// We have an activity
|
actionIcon.iconType = ActivityIconType::iconActivity;
|
||||||
if (!a._iconData.isEmpty()) {
|
|
||||||
QString svgData = "data:image/svg+xml;utf8," + a._iconData;
|
|
||||||
return svgData;
|
|
||||||
}
|
|
||||||
return "qrc:///client/theme/black/activity.svg";
|
|
||||||
}
|
}
|
||||||
|
QVariant icn;
|
||||||
|
icn.setValue(actionIcon);
|
||||||
|
return icn;
|
||||||
}
|
}
|
||||||
case ObjectTypeRole:
|
case ActivityItemDelegate::ObjectTypeRole:
|
||||||
return a._objectType;
|
return a._objectType;
|
||||||
case ActionRole: {
|
case ActivityItemDelegate::ActionRole:{
|
||||||
switch (a._type) {
|
QVariant type;
|
||||||
case Activity::ActivityType:
|
type.setValue(a._type);
|
||||||
return "Activity";
|
return type;
|
||||||
case Activity::NotificationType:
|
|
||||||
return "Notification";
|
|
||||||
case Activity::SyncFileItemType:
|
|
||||||
return "File";
|
|
||||||
case Activity::SyncResultType:
|
|
||||||
return "Sync";
|
|
||||||
default:
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case ActionTextRole:
|
case ActivityItemDelegate::ActionTextRole:
|
||||||
return a._subject;
|
return a._subject;
|
||||||
case ActionTextColorRole:
|
case ActivityItemDelegate::MessageRole:
|
||||||
return a._id == -1 ? QLatin1String("#808080") : QLatin1String("#222"); // FIXME: This is a temporary workaround for _showMoreActivitiesAvailableEntry
|
|
||||||
case MessageRole:
|
|
||||||
if (a._message.isEmpty()) {
|
|
||||||
return QString("No description available.");
|
|
||||||
}
|
|
||||||
return a._message;
|
return a._message;
|
||||||
case LinkRole: {
|
case ActivityItemDelegate::LinkRole:
|
||||||
if (a._link.isEmpty()) {
|
return a._link;
|
||||||
return "";
|
case ActivityItemDelegate::AccountRole:
|
||||||
} else {
|
|
||||||
return a._link;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case AccountRole:
|
|
||||||
return a._accName;
|
return a._accName;
|
||||||
case PointInTimeRole:
|
case ActivityItemDelegate::PointInTimeRole:
|
||||||
//return a._id == -1 ? "" : QString("%1 - %2").arg(Utility::timeAgoInWords(a._dateTime.toLocalTime()), a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate));
|
return QString("%1 (%2)").arg(a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate), Utility::timeAgoInWords(a._dateTime.toLocalTime()));
|
||||||
return a._id == -1 ? "" : Utility::timeAgoInWords(a._dateTime.toLocalTime());
|
case ActivityItemDelegate::AccountConnectedRole:
|
||||||
case AccountConnectedRole:
|
|
||||||
return (ast && ast->isConnected());
|
return (ast && ast->isConnected());
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
@@ -221,13 +171,13 @@ void ActivityListModel::startFetchJob()
|
|||||||
if (!_accountState->isConnected()) {
|
if (!_accountState->isConnected()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
JsonApiJob *job = new JsonApiJob(_accountState->account(), QLatin1String("ocs/v2.php/apps/activity/api/v2/activity"), this);
|
JsonApiJob *job = new JsonApiJob(_accountState->account(), QLatin1String("ocs/v2.php/cloud/activity"), this);
|
||||||
QObject::connect(job, &JsonApiJob::jsonReceived,
|
QObject::connect(job, &JsonApiJob::jsonReceived,
|
||||||
this, &ActivityListModel::slotActivitiesReceived);
|
this, &ActivityListModel::slotActivitiesReceived);
|
||||||
|
|
||||||
QUrlQuery params;
|
QUrlQuery params;
|
||||||
params.addQueryItem(QLatin1String("since"), QString::number(_currentItem));
|
params.addQueryItem(QLatin1String("start"), QString::number(_currentItem));
|
||||||
params.addQueryItem(QLatin1String("limit"), QString::number(50));
|
params.addQueryItem(QLatin1String("count"), QString::number(100));
|
||||||
job->addQueryParams(params);
|
job->addQueryParams(params);
|
||||||
|
|
||||||
_currentlyFetching = true;
|
_currentlyFetching = true;
|
||||||
@@ -250,43 +200,21 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st
|
|||||||
}
|
}
|
||||||
|
|
||||||
_currentlyFetching = false;
|
_currentlyFetching = false;
|
||||||
|
_currentItem += activities.size();
|
||||||
QDateTime oldestDate = QDateTime::currentDateTime();
|
|
||||||
oldestDate = oldestDate.addDays(_maxActivitiesDays * -1);
|
|
||||||
|
|
||||||
foreach (auto activ, activities) {
|
foreach (auto activ, activities) {
|
||||||
auto json = activ.toObject();
|
auto json = activ.toObject();
|
||||||
|
|
||||||
Activity a;
|
Activity a;
|
||||||
a._type = Activity::ActivityType;
|
a._type = Activity::ActivityType;
|
||||||
a._objectType = json.value("object_type").toString();
|
|
||||||
a._accName = ast->account()->displayName();
|
a._accName = ast->account()->displayName();
|
||||||
a._id = json.value("activity_id").toInt();
|
a._id = json.value("id").toInt();
|
||||||
a._fileAction = json.value("type").toString();
|
|
||||||
a._subject = json.value("subject").toString();
|
a._subject = json.value("subject").toString();
|
||||||
a._message = json.value("message").toString();
|
a._message = json.value("message").toString();
|
||||||
a._file = json.value("object_name").toString();
|
a._file = json.value("file").toString();
|
||||||
a._link = QUrl(json.value("link").toString());
|
a._link = QUrl(json.value("link").toString());
|
||||||
a._dateTime = QDateTime::fromString(json.value("datetime").toString(), Qt::ISODate);
|
a._dateTime = QDateTime::fromString(json.value("date").toString(), Qt::ISODate);
|
||||||
a._icon = json.value("icon").toString();
|
|
||||||
|
|
||||||
if (!a._icon.isEmpty()) {
|
|
||||||
IconJob *iconJob = new IconJob(QUrl(a._icon));
|
|
||||||
iconJob->setProperty("activityId", a._id);
|
|
||||||
connect(iconJob, &IconJob::jobFinished, this, &ActivityListModel::slotIconDownloaded);
|
|
||||||
}
|
|
||||||
|
|
||||||
list.append(a);
|
list.append(a);
|
||||||
_currentItem = list.last()._id;
|
|
||||||
|
|
||||||
_totalActivitiesFetched++;
|
|
||||||
if(_totalActivitiesFetched == _maxActivities ||
|
|
||||||
a._dateTime < oldestDate) {
|
|
||||||
|
|
||||||
_showMoreActivitiesAvailableEntry = true;
|
|
||||||
_doneFetching = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_activityLists.append(list);
|
_activityLists.append(list);
|
||||||
@@ -296,95 +224,76 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st
|
|||||||
combineActivityLists();
|
combineActivityLists();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivityListModel::slotIconDownloaded(QByteArray iconData)
|
void ActivityListModel::addErrorToActivityList(Activity activity) {
|
||||||
{
|
|
||||||
for (size_t i = 0; i < _activityLists.count(); i++) {
|
|
||||||
if (_activityLists[i]._id == sender()->property("activityId").toLongLong()) {
|
|
||||||
_activityLists[i]._iconData = iconData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityListModel::addErrorToActivityList(Activity activity)
|
|
||||||
{
|
|
||||||
qCInfo(lcActivity) << "Error successfully added to the notification list: " << activity._subject;
|
qCInfo(lcActivity) << "Error successfully added to the notification list: " << activity._subject;
|
||||||
_notificationErrorsLists.prepend(activity);
|
_notificationErrorsLists.prepend(activity);
|
||||||
combineActivityLists();
|
combineActivityLists();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivityListModel::addIgnoredFileToList(Activity newActivity)
|
void ActivityListModel::addIgnoredFileToList(Activity newActivity) {
|
||||||
{
|
|
||||||
qCInfo(lcActivity) << "First checking for duplicates then add file to the notification list of ignored files: " << newActivity._file;
|
qCInfo(lcActivity) << "First checking for duplicates then add file to the notification list of ignored files: " << newActivity._file;
|
||||||
|
|
||||||
bool duplicate = false;
|
bool duplicate = false;
|
||||||
if (_listOfIgnoredFiles.size() == 0) {
|
if(_listOfIgnoredFiles.size() == 0){
|
||||||
_notificationIgnoredFiles = newActivity;
|
_notificationIgnoredFiles = newActivity;
|
||||||
_notificationIgnoredFiles._subject = tr("Files from the ignore list as well as symbolic links are not synced. This includes:");
|
_notificationIgnoredFiles._subject = tr("Files from the ignore list as well as symbolic links are not synced. This includes:");
|
||||||
_listOfIgnoredFiles.append(newActivity);
|
_listOfIgnoredFiles.append(newActivity);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Activity activity, _listOfIgnoredFiles) {
|
foreach(Activity activity, _listOfIgnoredFiles){
|
||||||
if (activity._file == newActivity._file) {
|
if(activity._file == newActivity._file){
|
||||||
duplicate = true;
|
duplicate = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!duplicate) {
|
if(!duplicate){
|
||||||
_notificationIgnoredFiles._message.append(", " + newActivity._file);
|
_notificationIgnoredFiles._message.append(", " + newActivity._file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivityListModel::addNotificationToActivityList(Activity activity)
|
void ActivityListModel::addNotificationToActivityList(Activity activity) {
|
||||||
{
|
|
||||||
qCInfo(lcActivity) << "Notification successfully added to the notification list: " << activity._subject;
|
qCInfo(lcActivity) << "Notification successfully added to the notification list: " << activity._subject;
|
||||||
_notificationLists.prepend(activity);
|
_notificationLists.prepend(activity);
|
||||||
combineActivityLists();
|
combineActivityLists();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivityListModel::clearNotifications()
|
void ActivityListModel::clearNotifications() {
|
||||||
{
|
|
||||||
qCInfo(lcActivity) << "Clear the notifications";
|
qCInfo(lcActivity) << "Clear the notifications";
|
||||||
_notificationLists.clear();
|
_notificationLists.clear();
|
||||||
combineActivityLists();
|
combineActivityLists();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivityListModel::removeActivityFromActivityList(int row)
|
void ActivityListModel::removeActivityFromActivityList(int row) {
|
||||||
{
|
|
||||||
Activity activity = _finalList.at(row);
|
Activity activity = _finalList.at(row);
|
||||||
removeActivityFromActivityList(activity);
|
removeActivityFromActivityList(activity);
|
||||||
combineActivityLists();
|
combineActivityLists();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivityListModel::addSyncFileItemToActivityList(Activity activity)
|
void ActivityListModel::addSyncFileItemToActivityList(Activity activity) {
|
||||||
{
|
|
||||||
qCInfo(lcActivity) << "Successfully added to the activity list: " << activity._subject;
|
qCInfo(lcActivity) << "Successfully added to the activity list: " << activity._subject;
|
||||||
_syncFileItemLists.prepend(activity);
|
_syncFileItemLists.prepend(activity);
|
||||||
combineActivityLists();
|
combineActivityLists();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivityListModel::removeActivityFromActivityList(Activity activity)
|
void ActivityListModel::removeActivityFromActivityList(Activity activity) {
|
||||||
{
|
|
||||||
qCInfo(lcActivity) << "Activity/Notification/Error successfully dismissed: " << activity._subject;
|
qCInfo(lcActivity) << "Activity/Notification/Error successfully dismissed: " << activity._subject;
|
||||||
qCInfo(lcActivity) << "Trying to remove Activity/Notification/Error from view... ";
|
qCInfo(lcActivity) << "Trying to remove Activity/Notification/Error from view... ";
|
||||||
|
|
||||||
int index = -1;
|
int index = -1;
|
||||||
if (activity._type == Activity::ActivityType) {
|
if(activity._type == Activity::ActivityType){
|
||||||
index = _activityLists.indexOf(activity);
|
index = _activityLists.indexOf(activity);
|
||||||
if (index != -1)
|
if(index != -1) _activityLists.removeAt(index);
|
||||||
_activityLists.removeAt(index);
|
} else if(activity._type == Activity::NotificationType){
|
||||||
} else if (activity._type == Activity::NotificationType) {
|
|
||||||
index = _notificationLists.indexOf(activity);
|
index = _notificationLists.indexOf(activity);
|
||||||
if (index != -1)
|
if(index != -1) _notificationLists.removeAt(index);
|
||||||
_notificationLists.removeAt(index);
|
|
||||||
} else {
|
} else {
|
||||||
index = _notificationErrorsLists.indexOf(activity);
|
index = _notificationErrorsLists.indexOf(activity);
|
||||||
if (index != -1)
|
if(index != -1) _notificationErrorsLists.removeAt(index);
|
||||||
_notificationErrorsLists.removeAt(index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index != -1) {
|
if(index != -1){
|
||||||
qCInfo(lcActivity) << "Activity/Notification/Error successfully removed from the list.";
|
qCInfo(lcActivity) << "Activity/Notification/Error successfully removed from the list.";
|
||||||
qCInfo(lcActivity) << "Updating Activity/Notification/Error view.";
|
qCInfo(lcActivity) << "Updating Activity/Notification/Error view.";
|
||||||
combineActivityLists();
|
combineActivityLists();
|
||||||
@@ -395,57 +304,38 @@ void ActivityListModel::combineActivityLists()
|
|||||||
{
|
{
|
||||||
ActivityList resultList;
|
ActivityList resultList;
|
||||||
|
|
||||||
if (_notificationErrorsLists.count() > 0) {
|
if(_notificationErrorsLists.count() > 0) {
|
||||||
std::sort(_notificationErrorsLists.begin(), _notificationErrorsLists.end());
|
std::sort(_notificationErrorsLists.begin(), _notificationErrorsLists.end());
|
||||||
resultList.append(_notificationErrorsLists);
|
resultList.append(_notificationErrorsLists);
|
||||||
}
|
}
|
||||||
if (_listOfIgnoredFiles.size() > 0)
|
if(_listOfIgnoredFiles.size() > 0)
|
||||||
resultList.append(_notificationIgnoredFiles);
|
resultList.append(_notificationIgnoredFiles);
|
||||||
|
|
||||||
if (_notificationLists.count() > 0) {
|
if(_notificationLists.count() > 0) {
|
||||||
std::sort(_notificationLists.begin(), _notificationLists.end());
|
std::sort(_notificationLists.begin(), _notificationLists.end());
|
||||||
resultList.append(_notificationLists);
|
resultList.append(_notificationLists);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_syncFileItemLists.count() > 0) {
|
if(_syncFileItemLists.count() > 0) {
|
||||||
std::sort(_syncFileItemLists.begin(), _syncFileItemLists.end());
|
std::sort(_syncFileItemLists.begin(), _syncFileItemLists.end());
|
||||||
resultList.append(_syncFileItemLists);
|
resultList.append(_syncFileItemLists);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_activityLists.count() > 0) {
|
if(_activityLists.count() > 0) {
|
||||||
std::sort(_activityLists.begin(), _activityLists.end());
|
std::sort(_activityLists.begin(), _activityLists.end());
|
||||||
resultList.append(_activityLists);
|
resultList.append(_activityLists);
|
||||||
|
|
||||||
if(_showMoreActivitiesAvailableEntry) {
|
|
||||||
Activity a;
|
|
||||||
a._type = Activity::ActivityType;
|
|
||||||
a._accName = _accountState->account()->displayName();
|
|
||||||
a._id = -1;
|
|
||||||
a._subject = tr("For more activities please open the Activity app.");
|
|
||||||
a._dateTime = QDateTime::currentDateTime();
|
|
||||||
|
|
||||||
AccountApp *app = _accountState->findApp(QLatin1String("activity"));
|
|
||||||
if(app) {
|
|
||||||
a._link = app->url();
|
|
||||||
}
|
|
||||||
|
|
||||||
resultList.append(a);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
_finalList.clear();
|
_finalList.clear();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
|
|
||||||
if (resultList.count() > 0) {
|
beginInsertRows(QModelIndex(), 0, resultList.count());
|
||||||
beginInsertRows(QModelIndex(), 0, resultList.count() - 1);
|
_finalList = resultList;
|
||||||
_finalList = resultList;
|
endInsertRows();
|
||||||
endInsertRows();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ActivityListModel::canFetchActivities() const
|
bool ActivityListModel::canFetchActivities() const {
|
||||||
{
|
|
||||||
return _accountState->isConnected() && _accountState->account()->capabilities().hasActivities();
|
return _accountState->isConnected() && _accountState->account()->capabilities().hasActivities();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,8 +354,6 @@ void ActivityListModel::slotRefreshActivity()
|
|||||||
_activityLists.clear();
|
_activityLists.clear();
|
||||||
_doneFetching = false;
|
_doneFetching = false;
|
||||||
_currentItem = 0;
|
_currentItem = 0;
|
||||||
_totalActivitiesFetched = 0;
|
|
||||||
_showMoreActivitiesAvailableEntry = false;
|
|
||||||
|
|
||||||
if (canFetchActivities()) {
|
if (canFetchActivities()) {
|
||||||
startFetchJob();
|
startFetchJob();
|
||||||
@@ -482,7 +370,5 @@ void ActivityListModel::slotRemoveAccount()
|
|||||||
_currentlyFetching = false;
|
_currentlyFetching = false;
|
||||||
_doneFetching = false;
|
_doneFetching = false;
|
||||||
_currentItem = 0;
|
_currentItem = 0;
|
||||||
_totalActivitiesFetched = 0;
|
|
||||||
_showMoreActivitiesAvailableEntry = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
#include <QtCore>
|
#include <QtCore>
|
||||||
|
|
||||||
#include "ActivityData.h"
|
#include "activitydata.h"
|
||||||
|
|
||||||
class QJsonDocument;
|
class QJsonDocument;
|
||||||
|
|
||||||
@@ -38,24 +38,21 @@ class ActivityListModel : public QAbstractListModel
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum DataRole {
|
enum ActivityIconType {
|
||||||
ActionIconRole = Qt::UserRole + 1,
|
iconUseCached = 0,
|
||||||
UserIconRole,
|
iconActivity,
|
||||||
AccountRole,
|
iconBell,
|
||||||
ObjectTypeRole,
|
iconStateError,
|
||||||
ActionsLinksRole,
|
iconStateWarning,
|
||||||
ActionTextRole,
|
iconStateInfo,
|
||||||
ActionTextColorRole,
|
iconStateSync
|
||||||
ActionRole,
|
};
|
||||||
MessageRole,
|
struct ActionIcon {
|
||||||
DisplayPathRole,
|
ActivityIconType iconType;
|
||||||
PathRole,
|
QIcon cachedIcon;
|
||||||
LinkRole,
|
};
|
||||||
PointInTimeRole,
|
|
||||||
AccountConnectedRole,
|
|
||||||
SyncFileStatusRole};
|
|
||||||
|
|
||||||
explicit ActivityListModel(AccountState *accountState, QObject* parent = 0);
|
explicit ActivityListModel(AccountState *accountState, QWidget *parent = nullptr);
|
||||||
|
|
||||||
QVariant data(const QModelIndex &index, int role) const override;
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
@@ -79,14 +76,10 @@ public slots:
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void slotActivitiesReceived(const QJsonDocument &json, int statusCode);
|
void slotActivitiesReceived(const QJsonDocument &json, int statusCode);
|
||||||
void slotIconDownloaded(QByteArray iconData);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void activityJobStatusCode(int statusCode);
|
void activityJobStatusCode(int statusCode);
|
||||||
|
|
||||||
protected:
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void startFetchJob();
|
void startFetchJob();
|
||||||
void combineActivityLists();
|
void combineActivityLists();
|
||||||
@@ -103,12 +96,9 @@ private:
|
|||||||
bool _currentlyFetching = false;
|
bool _currentlyFetching = false;
|
||||||
bool _doneFetching = false;
|
bool _doneFetching = false;
|
||||||
int _currentItem = 0;
|
int _currentItem = 0;
|
||||||
|
|
||||||
int _totalActivitiesFetched = 0;
|
|
||||||
int _maxActivities = 100;
|
|
||||||
int _maxActivitiesDays = 30;
|
|
||||||
bool _showMoreActivitiesAvailableEntry = false;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(OCC::ActivityListModel::ActionIcon)
|
||||||
|
|
||||||
#endif // ACTIVITYLISTMODEL_H
|
#endif // ACTIVITYLISTMODEL_H
|
||||||
653
src/gui/activitywidget.cpp
Normal file
@@ -0,0 +1,653 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QtGui>
|
||||||
|
#include <QtWidgets>
|
||||||
|
|
||||||
|
#include "activitylistmodel.h"
|
||||||
|
#include "activitywidget.h"
|
||||||
|
#include "syncresult.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "theme.h"
|
||||||
|
#include "folderman.h"
|
||||||
|
#include "syncfileitem.h"
|
||||||
|
#include "folder.h"
|
||||||
|
#include "openfilemanager.h"
|
||||||
|
#include "owncloudpropagator.h"
|
||||||
|
#include "account.h"
|
||||||
|
#include "accountstate.h"
|
||||||
|
#include "accountmanager.h"
|
||||||
|
#include "activityitemdelegate.h"
|
||||||
|
#include "QProgressIndicator.h"
|
||||||
|
#include "notificationconfirmjob.h"
|
||||||
|
#include "servernotificationhandler.h"
|
||||||
|
#include "theme.h"
|
||||||
|
#include "ocsjob.h"
|
||||||
|
#include "configfile.h"
|
||||||
|
#include "guiutility.h"
|
||||||
|
#include "socketapi.h"
|
||||||
|
#include "ui_activitywidget.h"
|
||||||
|
#include "syncengine.h"
|
||||||
|
|
||||||
|
#include <climits>
|
||||||
|
|
||||||
|
// time span in milliseconds which has to be between two
|
||||||
|
// refreshes of the notifications
|
||||||
|
#define NOTIFICATION_REQUEST_FREE_PERIOD 15000
|
||||||
|
|
||||||
|
namespace OCC {
|
||||||
|
|
||||||
|
ActivityWidget::ActivityWidget(AccountState *accountState, QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, _ui(new Ui::ActivityWidget)
|
||||||
|
, _notificationRequestsRunning(0)
|
||||||
|
, _accountState(accountState)
|
||||||
|
, _accept(tr("Accept"))
|
||||||
|
, _remote_share("remote_share")
|
||||||
|
{
|
||||||
|
_ui->setupUi(this);
|
||||||
|
|
||||||
|
// Adjust copyToClipboard() when making changes here!
|
||||||
|
#if defined(Q_OS_MAC)
|
||||||
|
_ui->_activityList->setMinimumWidth(400);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_model = new ActivityListModel(accountState, this);
|
||||||
|
ActivityItemDelegate *delegate = new ActivityItemDelegate;
|
||||||
|
delegate->setParent(this);
|
||||||
|
_ui->_activityList->setItemDelegate(delegate);
|
||||||
|
_ui->_activityList->setAlternatingRowColors(true);
|
||||||
|
_ui->_activityList->setModel(_model);
|
||||||
|
|
||||||
|
showLabels();
|
||||||
|
|
||||||
|
connect(_model, &ActivityListModel::activityJobStatusCode,
|
||||||
|
this, &ActivityWidget::slotAccountActivityStatus);
|
||||||
|
|
||||||
|
connect(_model, &QAbstractItemModel::rowsInserted, this, &ActivityWidget::rowsInserted);
|
||||||
|
|
||||||
|
connect(delegate, &ActivityItemDelegate::primaryButtonClickedOnItemView, this, &ActivityWidget::slotPrimaryButtonClickedOnListView);
|
||||||
|
connect(delegate, &ActivityItemDelegate::secondaryButtonClickedOnItemView, this, &ActivityWidget::slotSecondaryButtonClickedOnListView);
|
||||||
|
connect(_ui->_activityList, &QListView::activated, this, &ActivityWidget::slotOpenFile);
|
||||||
|
|
||||||
|
connect(ProgressDispatcher::instance(), &ProgressDispatcher::progressInfo,
|
||||||
|
this, &ActivityWidget::slotProgressInfo);
|
||||||
|
connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted,
|
||||||
|
this, &ActivityWidget::slotItemCompleted);
|
||||||
|
connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError,
|
||||||
|
this, &ActivityWidget::addError);
|
||||||
|
|
||||||
|
_removeTimer.setInterval(1000);
|
||||||
|
|
||||||
|
// Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching)
|
||||||
|
connect(this, &ActivityWidget::styleChanged, delegate, &ActivityItemDelegate::slotStyleChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityWidget::~ActivityWidget()
|
||||||
|
{
|
||||||
|
delete _ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::slotProgressInfo(const QString &folder, const ProgressInfo &progress)
|
||||||
|
{
|
||||||
|
if (progress.status() == ProgressInfo::Reconcile) {
|
||||||
|
// Wipe all non-persistent entries - as well as the persistent ones
|
||||||
|
// in cases where a local discovery was done.
|
||||||
|
auto f = FolderMan::instance()->folder(folder);
|
||||||
|
if (!f)
|
||||||
|
return;
|
||||||
|
const auto &engine = f->syncEngine();
|
||||||
|
const auto style = engine.lastLocalDiscoveryStyle();
|
||||||
|
foreach (Activity activity, _model->errorsList()) {
|
||||||
|
if (activity._folder != folder){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (style == LocalDiscoveryStyle::FilesystemOnly){
|
||||||
|
_model->removeActivityFromActivityList(activity);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(activity._status == SyncFileItem::Conflict && !QFileInfo(f->path() + activity._file).exists()){
|
||||||
|
_model->removeActivityFromActivityList(activity);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(activity._status == SyncFileItem::FileLocked && !QFileInfo(f->path() + activity._file).exists()){
|
||||||
|
_model->removeActivityFromActivityList(activity);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(activity._status == SyncFileItem::FileIgnored && !QFileInfo(f->path() + activity._file).exists()) {
|
||||||
|
_model->removeActivityFromActivityList(activity);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(!QFileInfo(f->path() + activity._file).exists()){
|
||||||
|
_model->removeActivityFromActivityList(activity);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto path = QFileInfo(activity._file).dir().path().toUtf8();
|
||||||
|
if (path == ".")
|
||||||
|
path.clear();
|
||||||
|
|
||||||
|
if(engine.shouldDiscoverLocally(path))
|
||||||
|
_model->removeActivityFromActivityList(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress.status() == ProgressInfo::Done) {
|
||||||
|
// We keep track very well of pending conflicts.
|
||||||
|
// Inform other components about them.
|
||||||
|
QStringList conflicts;
|
||||||
|
foreach (Activity activity, _model->errorsList()) {
|
||||||
|
if (activity._folder == folder
|
||||||
|
&& activity._status == SyncFileItem::Conflict) {
|
||||||
|
conflicts.append(activity._file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit ProgressDispatcher::instance()->folderConflicts(folder, conflicts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item){
|
||||||
|
auto folderInstance = FolderMan::instance()->folder(folder);
|
||||||
|
|
||||||
|
if (!folderInstance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// check if we are adding it to the right account and if it is useful information (protocol errors)
|
||||||
|
if(folderInstance->accountState() == _accountState){
|
||||||
|
qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in " << item->_errorString;
|
||||||
|
|
||||||
|
Activity activity;
|
||||||
|
activity._type = Activity::SyncFileItemType; //client activity
|
||||||
|
activity._status = item->_status;
|
||||||
|
activity._dateTime = QDateTime::currentDateTime();
|
||||||
|
activity._message = item->_originalFile;
|
||||||
|
activity._link = folderInstance->accountState()->account()->url();
|
||||||
|
activity._accName = folderInstance->accountState()->account()->displayName();
|
||||||
|
activity._file = item->_file;
|
||||||
|
activity._folder = folder;
|
||||||
|
|
||||||
|
if(item->_status == SyncFileItem::NoStatus || item->_status == SyncFileItem::Success){
|
||||||
|
qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully.";
|
||||||
|
activity._message.prepend(" ");
|
||||||
|
activity._message.prepend(tr("Synced"));
|
||||||
|
_model->addSyncFileItemToActivityList(activity);
|
||||||
|
} else {
|
||||||
|
qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in error " << item->_errorString;
|
||||||
|
activity._subject = item->_errorString;
|
||||||
|
|
||||||
|
if(item->_status == SyncFileItem::Status::FileIgnored) {
|
||||||
|
_model->addIgnoredFileToList(activity);
|
||||||
|
} else {
|
||||||
|
// add 'protocol error' to activity list
|
||||||
|
_model->addErrorToActivityList(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::addError(const QString &folderAlias, const QString &message,
|
||||||
|
ErrorCategory category)
|
||||||
|
{
|
||||||
|
auto folderInstance = FolderMan::instance()->folder(folderAlias);
|
||||||
|
if (!folderInstance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(folderInstance->accountState() == _accountState){
|
||||||
|
qCWarning(lcActivity) << "Item " << folderInstance->shortGuiLocalPath() << " retrieved resulted in " << message;
|
||||||
|
|
||||||
|
Activity activity;
|
||||||
|
activity._type = Activity::SyncResultType;
|
||||||
|
activity._status = SyncResult::Error;
|
||||||
|
activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate);
|
||||||
|
activity._subject = message;
|
||||||
|
activity._message = folderInstance->shortGuiLocalPath();
|
||||||
|
activity._link = folderInstance->shortGuiLocalPath();
|
||||||
|
activity._accName = folderInstance->accountState()->account()->displayName();
|
||||||
|
activity._folder = folderAlias;
|
||||||
|
|
||||||
|
|
||||||
|
if (category == ErrorCategory::InsufficientRemoteStorage) {
|
||||||
|
ActivityLink link;
|
||||||
|
link._label = tr("Retry all uploads");
|
||||||
|
link._link = folderInstance->path();
|
||||||
|
link._verb = "";
|
||||||
|
link._isPrimary = true;
|
||||||
|
activity._links.append(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add 'other errors' to activity list
|
||||||
|
_model->addErrorToActivityList(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ActivityWidget::slotPrimaryButtonClickedOnListView(const QModelIndex &index){
|
||||||
|
QUrl link = qvariant_cast<QString>(index.data(ActivityItemDelegate::LinkRole));
|
||||||
|
QString objectType = index.data(ActivityItemDelegate::ObjectTypeRole).toString();
|
||||||
|
if(!link.isEmpty()){
|
||||||
|
qCWarning(lcActivity) << "Opening" << link.toString() << "in browser for Notification/Activity" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
|
||||||
|
Utility::openBrowser(link, this);
|
||||||
|
} else if(objectType == _remote_share){
|
||||||
|
QVariant customItem = index.data(ActivityItemDelegate::ActionsLinksRole).toList().first();
|
||||||
|
ActivityLink actionLink = qvariant_cast<ActivityLink>(customItem);
|
||||||
|
if(actionLink._label == _accept){
|
||||||
|
qCWarning(lcActivity) << objectType << "action" << actionLink._label << "for" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
|
||||||
|
const QString accountName = index.data(ActivityItemDelegate::AccountRole).toString();
|
||||||
|
slotSendNotificationRequest(accountName, actionLink._link, actionLink._verb, index.row());
|
||||||
|
} else {
|
||||||
|
qCWarning(lcActivity) << "Failed: " << objectType << "action" << actionLink._label << "for" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::slotSecondaryButtonClickedOnListView(const QModelIndex &index){
|
||||||
|
QList<QVariant> customList = index.data(ActivityItemDelegate::ActionsLinksRole).toList();
|
||||||
|
QString objectType = index.data(ActivityItemDelegate::ObjectTypeRole).toString();
|
||||||
|
|
||||||
|
QList<ActivityLink> actionLinks;
|
||||||
|
foreach(QVariant customItem, customList){
|
||||||
|
actionLinks << qvariant_cast<ActivityLink>(customItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(objectType == _remote_share && actionLinks.first()._label == _accept)
|
||||||
|
actionLinks.removeFirst();
|
||||||
|
|
||||||
|
if(qvariant_cast<Activity::Type>(index.data(ActivityItemDelegate::ActionRole)) == Activity::Type::NotificationType){
|
||||||
|
const QString accountName = index.data(ActivityItemDelegate::AccountRole).toString();
|
||||||
|
if(actionLinks.size() == 1){
|
||||||
|
if(actionLinks.at(0)._verb == "DELETE"){
|
||||||
|
qCWarning(lcActivity) << "Dismissing Notification/Activity" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
|
||||||
|
slotSendNotificationRequest(index.data(ActivityItemDelegate::AccountRole).toString(), actionLinks.at(0)._link, actionLinks.at(0)._verb, index.row());
|
||||||
|
}
|
||||||
|
} else if(actionLinks.size() > 1){
|
||||||
|
QMenu menu;
|
||||||
|
qCWarning(lcActivity) << "Displaying menu for Notification/Activity" << qvariant_cast<QString>(index.data(ActivityItemDelegate::ActionTextRole));
|
||||||
|
foreach (ActivityLink actionLink, actionLinks) {
|
||||||
|
QAction *menuAction = new QAction(actionLink._label, &menu);
|
||||||
|
connect(menuAction, &QAction::triggered, this, [this, index, accountName, actionLink] {
|
||||||
|
this->slotSendNotificationRequest(accountName, actionLink._link, actionLink._verb, index.row());
|
||||||
|
});
|
||||||
|
menu.addAction(menuAction);
|
||||||
|
}
|
||||||
|
menu.exec(QCursor::pos());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Activity::Type activityType = qvariant_cast<Activity::Type>(index.data(ActivityItemDelegate::ActionRole));
|
||||||
|
if(activityType == Activity::Type::SyncFileItemType || activityType == Activity::Type::SyncResultType)
|
||||||
|
slotOpenFile(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::slotNotificationRequestFinished(int statusCode)
|
||||||
|
{
|
||||||
|
int row = sender()->property("activityRow").toInt();
|
||||||
|
|
||||||
|
// the ocs API returns stat code 100 or 200 inside the xml if it succeeded.
|
||||||
|
if (statusCode != OCS_SUCCESS_STATUS_CODE && statusCode != OCS_SUCCESS_STATUS_CODE_V2) {
|
||||||
|
qCWarning(lcActivity) << "Notification Request to Server failed, leave notification visible.";
|
||||||
|
} else {
|
||||||
|
// to do use the model to rebuild the list or remove the item
|
||||||
|
qCWarning(lcActivity) << "Notification Request to Server successed, rebuilding list.";
|
||||||
|
_model->removeActivityFromActivityList(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::slotRefreshActivities()
|
||||||
|
{
|
||||||
|
_model->slotRefreshActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::slotRefreshNotifications()
|
||||||
|
{
|
||||||
|
// start a server notification handler if no notification requests
|
||||||
|
// are running
|
||||||
|
if (_notificationRequestsRunning == 0) {
|
||||||
|
ServerNotificationHandler *snh = new ServerNotificationHandler(_accountState);
|
||||||
|
connect(snh, &ServerNotificationHandler::newNotificationList,
|
||||||
|
this, &ActivityWidget::slotBuildNotificationDisplay);
|
||||||
|
|
||||||
|
snh->slotFetchNotifications();
|
||||||
|
} else {
|
||||||
|
qCWarning(lcActivity) << "Notification request counter not zero.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::slotRemoveAccount()
|
||||||
|
{
|
||||||
|
_model->slotRemoveAccount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::showLabels()
|
||||||
|
{
|
||||||
|
_ui->_bottomLabel->hide(); // hide whatever was there before
|
||||||
|
QString t("");
|
||||||
|
QSetIterator<QString> i(_accountsWithoutActivities);
|
||||||
|
while (i.hasNext()) {
|
||||||
|
t.append(tr("<br/>Account %1 does not have activities enabled.").arg(i.next()));
|
||||||
|
}
|
||||||
|
if(!t.isEmpty()){
|
||||||
|
_ui->_bottomLabel->setTextFormat(Qt::RichText);
|
||||||
|
_ui->_bottomLabel->setText(t);
|
||||||
|
_ui->_bottomLabel->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::slotAccountActivityStatus(int statusCode)
|
||||||
|
{
|
||||||
|
if (!(_accountState && _accountState->account())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (statusCode == 999) {
|
||||||
|
_accountsWithoutActivities.insert(_accountState->account()->displayName());
|
||||||
|
} else {
|
||||||
|
_accountsWithoutActivities.remove(_accountState->account()->displayName());
|
||||||
|
}
|
||||||
|
|
||||||
|
checkActivityWidgetVisibility();
|
||||||
|
showLabels();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Reused from protocol widget. Move over to utilities.
|
||||||
|
QString ActivityWidget::timeString(QDateTime dt, QLocale::FormatType format) const
|
||||||
|
{
|
||||||
|
const QLocale loc = QLocale::system();
|
||||||
|
QString dtFormat = loc.dateTimeFormat(format);
|
||||||
|
static const QRegExp re("(HH|H|hh|h):mm(?!:s)");
|
||||||
|
dtFormat.replace(re, "\\1:mm:ss");
|
||||||
|
return loc.toString(dt, dtFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::storeActivityList(QTextStream &ts)
|
||||||
|
{
|
||||||
|
ActivityList activities = _model->activityList();
|
||||||
|
|
||||||
|
foreach (Activity activity, activities) {
|
||||||
|
ts << right
|
||||||
|
// account name
|
||||||
|
<< qSetFieldWidth(activity._accName.length())
|
||||||
|
<< activity._accName
|
||||||
|
// separator
|
||||||
|
<< qSetFieldWidth(2) << " - "
|
||||||
|
|
||||||
|
// date and time
|
||||||
|
<< qSetFieldWidth(activity._dateTime.toString().length())
|
||||||
|
<< activity._dateTime.toString()
|
||||||
|
// separator
|
||||||
|
<< qSetFieldWidth(2) << " - "
|
||||||
|
|
||||||
|
// fileq
|
||||||
|
<< qSetFieldWidth(activity._file.length())
|
||||||
|
<< activity._file
|
||||||
|
// separator
|
||||||
|
<< qSetFieldWidth(2) << " - "
|
||||||
|
|
||||||
|
// subject
|
||||||
|
<< qSetFieldWidth(activity._subject.length())
|
||||||
|
<< activity._subject
|
||||||
|
// separator
|
||||||
|
<< qSetFieldWidth(2) << " - "
|
||||||
|
|
||||||
|
// message
|
||||||
|
<< qSetFieldWidth(activity._message.length())
|
||||||
|
<< activity._message
|
||||||
|
<< endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::checkActivityWidgetVisibility()
|
||||||
|
{
|
||||||
|
int accountCount = AccountManager::instance()->accounts().count();
|
||||||
|
bool hasAccountsWithActivity =
|
||||||
|
_accountsWithoutActivities.count() != accountCount;
|
||||||
|
|
||||||
|
_ui->_activityList->setVisible(hasAccountsWithActivity);
|
||||||
|
|
||||||
|
emit hideActivityTab(!hasAccountsWithActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::slotOpenFile(QModelIndex indx)
|
||||||
|
{
|
||||||
|
qCDebug(lcActivity) << indx.isValid() << indx.data(ActivityItemDelegate::PathRole).toString() << QFile::exists(indx.data(ActivityItemDelegate::PathRole).toString());
|
||||||
|
if (indx.isValid()) {
|
||||||
|
QString fullPath = indx.data(ActivityItemDelegate::PathRole).toString();
|
||||||
|
if(!fullPath.isEmpty()){
|
||||||
|
if (QFile::exists(fullPath)) {
|
||||||
|
showInFileManager(fullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GUI: Display the notifications.
|
||||||
|
// All notifications in list are coming from the same account
|
||||||
|
// but in the _widgetForNotifId hash widgets for all accounts are
|
||||||
|
// collected.
|
||||||
|
void ActivityWidget::slotBuildNotificationDisplay(const ActivityList &list)
|
||||||
|
{
|
||||||
|
// Whether a new notification was added to the list
|
||||||
|
bool newNotificationShown = false;
|
||||||
|
|
||||||
|
_model->clearNotifications();
|
||||||
|
|
||||||
|
foreach (auto activity, list) {
|
||||||
|
if (_blacklistedNotifications.contains(activity)) {
|
||||||
|
qCInfo(lcActivity) << "Activity in blacklist, skip";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle gui logs. In order to NOT annoy the user with every fetching of the
|
||||||
|
// notifications the notification id is stored in a Set. Only if an id
|
||||||
|
// is not in the set, it qualifies for guiLog.
|
||||||
|
// Important: The _guiLoggedNotifications set must be wiped regularly which
|
||||||
|
// will repeat the gui log.
|
||||||
|
|
||||||
|
// after one hour, clear the gui log notification store
|
||||||
|
if (_guiLogTimer.elapsed() > 60 * 60 * 1000) {
|
||||||
|
_guiLoggedNotifications.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_guiLoggedNotifications.contains(activity._id)) {
|
||||||
|
newNotificationShown = true;
|
||||||
|
_guiLoggedNotifications.insert(activity._id);
|
||||||
|
|
||||||
|
// Assemble a tray notification for the NEW notification
|
||||||
|
ConfigFile cfg;
|
||||||
|
if(cfg.optionalServerNotifications()){
|
||||||
|
if(AccountManager::instance()->accounts().count() == 1){
|
||||||
|
emit guiLog(activity._subject, "");
|
||||||
|
} else {
|
||||||
|
emit guiLog(activity._subject, activity._accName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_model->addNotificationToActivityList(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// restart the gui log timer now that we show a new notification
|
||||||
|
if(newNotificationShown) {
|
||||||
|
_guiLogTimer.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row)
|
||||||
|
{
|
||||||
|
qCInfo(lcActivity) << "Server Notification Request " << verb << link << "on account" << accountName;
|
||||||
|
|
||||||
|
const QStringList validVerbs = QStringList() << "GET"
|
||||||
|
<< "PUT"
|
||||||
|
<< "POST"
|
||||||
|
<< "DELETE";
|
||||||
|
|
||||||
|
if (validVerbs.contains(verb)) {
|
||||||
|
AccountStatePtr acc = AccountManager::instance()->account(accountName);
|
||||||
|
if (acc) {
|
||||||
|
NotificationConfirmJob *job = new NotificationConfirmJob(acc->account());
|
||||||
|
QUrl l(link);
|
||||||
|
job->setLinkAndVerb(l, verb);
|
||||||
|
job->setProperty("activityRow", QVariant::fromValue(row));
|
||||||
|
connect(job, &AbstractNetworkJob::networkError,
|
||||||
|
this, &ActivityWidget::slotNotifyNetworkError);
|
||||||
|
connect(job, &NotificationConfirmJob::jobFinished,
|
||||||
|
this, &ActivityWidget::slotNotifyServerFinished);
|
||||||
|
job->start();
|
||||||
|
|
||||||
|
// count the number of running notification requests. If this member var
|
||||||
|
// is larger than zero, no new fetching of notifications is started
|
||||||
|
_notificationRequestsRunning++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qCWarning(lcActivity) << "Notification Links: Invalid verb:" << verb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::endNotificationRequest(int replyCode)
|
||||||
|
{
|
||||||
|
_notificationRequestsRunning--;
|
||||||
|
slotNotificationRequestFinished(replyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::slotNotifyNetworkError(QNetworkReply *reply)
|
||||||
|
{
|
||||||
|
NotificationConfirmJob *job = qobject_cast<NotificationConfirmJob *>(sender());
|
||||||
|
if (!job) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int resultCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
|
||||||
|
endNotificationRequest(resultCode);
|
||||||
|
qCWarning(lcActivity) << "Server notify job failed with code " << resultCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::slotNotifyServerFinished(const QString &reply, int replyCode)
|
||||||
|
{
|
||||||
|
NotificationConfirmJob *job = qobject_cast<NotificationConfirmJob *>(sender());
|
||||||
|
if (!job) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
endNotificationRequest(replyCode);
|
||||||
|
qCInfo(lcActivity) << "Server Notification reply code" << replyCode << reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWidget::slotStyleChanged()
|
||||||
|
{
|
||||||
|
// Notify the other widgets (Dark-/Light-Mode switching)
|
||||||
|
emit styleChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================================================================== */
|
||||||
|
|
||||||
|
ActivitySettings::ActivitySettings(AccountState *accountState, QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, _accountState(accountState)
|
||||||
|
{
|
||||||
|
_vbox = new QVBoxLayout(this);
|
||||||
|
setLayout(_vbox);
|
||||||
|
|
||||||
|
_activityWidget = new ActivityWidget(_accountState, this);
|
||||||
|
|
||||||
|
_vbox->insertWidget(1, _activityWidget);
|
||||||
|
connect(_activityWidget, &ActivityWidget::guiLog, this, &ActivitySettings::guiLog);
|
||||||
|
connect(&_notificationCheckTimer, &QTimer::timeout,
|
||||||
|
this, &ActivitySettings::slotRegularNotificationCheck);
|
||||||
|
|
||||||
|
// Add a progress indicator to spin if the acitivity list is updated.
|
||||||
|
_progressIndicator = new QProgressIndicator(this);
|
||||||
|
|
||||||
|
// connect a model signal to stop the animation
|
||||||
|
connect(_activityWidget, &ActivityWidget::rowsInserted, _progressIndicator, &QProgressIndicator::stopAnimation);
|
||||||
|
connect(_activityWidget, &ActivityWidget::rowsInserted, this, &ActivitySettings::slotDisplayActivities);
|
||||||
|
|
||||||
|
// Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching)
|
||||||
|
connect(this, &ActivitySettings::styleChanged, _activityWidget, &ActivityWidget::slotStyleChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivitySettings::slotDisplayActivities(){
|
||||||
|
_vbox->removeWidget(_progressIndicator);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivitySettings::setNotificationRefreshInterval(std::chrono::milliseconds interval)
|
||||||
|
{
|
||||||
|
qCDebug(lcActivity) << "Starting Notification refresh timer with " << interval.count() / 1000 << " sec interval";
|
||||||
|
_notificationCheckTimer.start(interval.count());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivitySettings::slotRemoveAccount()
|
||||||
|
{
|
||||||
|
_activityWidget->slotRemoveAccount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivitySettings::slotRefresh()
|
||||||
|
{
|
||||||
|
// QElapsedTimer isn't actually constructed as invalid.
|
||||||
|
if (!_timeSinceLastCheck.contains(_accountState)) {
|
||||||
|
_timeSinceLastCheck[_accountState].invalidate();
|
||||||
|
}
|
||||||
|
QElapsedTimer &timer = _timeSinceLastCheck[_accountState];
|
||||||
|
|
||||||
|
// Fetch Activities only if visible and if last check is longer than 15 secs ago
|
||||||
|
if (timer.isValid() && timer.elapsed() < NOTIFICATION_REQUEST_FREE_PERIOD) {
|
||||||
|
qCDebug(lcActivity) << "Do not check as last check is only secs ago: " << timer.elapsed() / 1000;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_accountState && _accountState->isConnected()) {
|
||||||
|
if (isVisible() || !timer.isValid()) {
|
||||||
|
_vbox->insertWidget(0, _progressIndicator);
|
||||||
|
_vbox->setAlignment(_progressIndicator, Qt::AlignHCenter);
|
||||||
|
_progressIndicator->startAnimation();
|
||||||
|
_activityWidget->slotRefreshActivities();
|
||||||
|
}
|
||||||
|
_activityWidget->slotRefreshNotifications();
|
||||||
|
timer.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivitySettings::slotRegularNotificationCheck()
|
||||||
|
{
|
||||||
|
slotRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ActivitySettings::event(QEvent *e)
|
||||||
|
{
|
||||||
|
if (e->type() == QEvent::Show) {
|
||||||
|
slotRefresh();
|
||||||
|
}
|
||||||
|
return QWidget::event(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivitySettings::~ActivitySettings()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivitySettings::slotStyleChanged()
|
||||||
|
{
|
||||||
|
if(_progressIndicator)
|
||||||
|
_progressIndicator->setColor(QGuiApplication::palette().color(QPalette::Text));
|
||||||
|
|
||||||
|
// Notify the other widgets (Dark-/Light-Mode switching)
|
||||||
|
emit styleChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
165
src/gui/activitywidget.h
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ACTIVITYWIDGET_H
|
||||||
|
#define ACTIVITYWIDGET_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QLocale>
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "progressdispatcher.h"
|
||||||
|
#include "owncloudgui.h"
|
||||||
|
#include "account.h"
|
||||||
|
#include "activitydata.h"
|
||||||
|
#include "accountmanager.h"
|
||||||
|
|
||||||
|
#include "ui_activitywidget.h"
|
||||||
|
|
||||||
|
class QPushButton;
|
||||||
|
class QProgressIndicator;
|
||||||
|
|
||||||
|
namespace OCC {
|
||||||
|
|
||||||
|
class Account;
|
||||||
|
class AccountStatusPtr;
|
||||||
|
class JsonApiJob;
|
||||||
|
class ActivityListModel;
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class ActivityWidget;
|
||||||
|
}
|
||||||
|
class Application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The ActivityWidget class
|
||||||
|
* @ingroup gui
|
||||||
|
*
|
||||||
|
* The list widget to display the activities, contained in the
|
||||||
|
* subsequent ActivitySettings widget.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ActivityWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ActivityWidget(AccountState *accountState, QWidget *parent = nullptr);
|
||||||
|
~ActivityWidget();
|
||||||
|
QSize sizeHint() const override { return ownCloudGui::settingsDialogSize(); }
|
||||||
|
void storeActivityList(QTextStream &ts);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjusts the activity tab's and some widgets' visibility
|
||||||
|
*
|
||||||
|
* Based on whether activities are enabled and whether notifications are
|
||||||
|
* available.
|
||||||
|
*/
|
||||||
|
void checkActivityWidgetVisibility();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void slotOpenFile(QModelIndex indx);
|
||||||
|
void slotRefreshActivities();
|
||||||
|
void slotRefreshNotifications();
|
||||||
|
void slotRemoveAccount();
|
||||||
|
void slotAccountActivityStatus(int statusCode);
|
||||||
|
void addError(const QString &folderAlias, const QString &message, ErrorCategory category);
|
||||||
|
void slotProgressInfo(const QString &folder, const ProgressInfo &progress);
|
||||||
|
void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item);
|
||||||
|
void slotStyleChanged();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void guiLog(const QString &, const QString &);
|
||||||
|
void rowsInserted();
|
||||||
|
void hideActivityTab(bool);
|
||||||
|
void sendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row);
|
||||||
|
void styleChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void slotBuildNotificationDisplay(const ActivityList &list);
|
||||||
|
void slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row);
|
||||||
|
void slotNotifyNetworkError(QNetworkReply *);
|
||||||
|
void slotNotifyServerFinished(const QString &reply, int replyCode);
|
||||||
|
void endNotificationRequest(int replyCode);
|
||||||
|
void slotNotificationRequestFinished(int statusCode);
|
||||||
|
void slotPrimaryButtonClickedOnListView(const QModelIndex &index);
|
||||||
|
void slotSecondaryButtonClickedOnListView(const QModelIndex &index);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void customizeStyle();
|
||||||
|
void showLabels();
|
||||||
|
QString timeString(QDateTime dt, QLocale::FormatType format) const;
|
||||||
|
Ui::ActivityWidget *_ui;
|
||||||
|
QSet<QString> _accountsWithoutActivities;
|
||||||
|
QElapsedTimer _guiLogTimer;
|
||||||
|
QSet<int> _guiLoggedNotifications;
|
||||||
|
ActivityList _blacklistedNotifications;
|
||||||
|
|
||||||
|
QTimer _removeTimer;
|
||||||
|
|
||||||
|
// number of currently running notification requests. If non zero,
|
||||||
|
// no query for notifications is started.
|
||||||
|
int _notificationRequestsRunning;
|
||||||
|
|
||||||
|
ActivityListModel *_model;
|
||||||
|
AccountState *_accountState;
|
||||||
|
const QString _accept;
|
||||||
|
const QString _remote_share;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The ActivitySettings class
|
||||||
|
* @ingroup gui
|
||||||
|
*
|
||||||
|
* Implements a tab for the settings dialog, displaying the three activity
|
||||||
|
* lists.
|
||||||
|
*/
|
||||||
|
class ActivitySettings : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ActivitySettings(AccountState *accountState, QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
~ActivitySettings();
|
||||||
|
QSize sizeHint() const override { return ownCloudGui::settingsDialogSize(); }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void slotRefresh();
|
||||||
|
void slotRemoveAccount();
|
||||||
|
void setNotificationRefreshInterval(std::chrono::milliseconds interval);
|
||||||
|
void slotStyleChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void slotRegularNotificationCheck();
|
||||||
|
void slotDisplayActivities();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void guiLog(const QString &, const QString &);
|
||||||
|
void styleChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool event(QEvent *e) override;
|
||||||
|
|
||||||
|
ActivityWidget *_activityWidget;
|
||||||
|
QProgressIndicator *_progressIndicator;
|
||||||
|
QVBoxLayout *_vbox;
|
||||||
|
QTimer _notificationCheckTimer;
|
||||||
|
QHash<AccountState *, QElapsedTimer> _timeSinceLastCheck;
|
||||||
|
|
||||||
|
AccountState *_accountState;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif // ActivityWIDGET_H
|
||||||
98
src/gui/activitywidget.ui
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>OCC::ActivityWidget</class>
|
||||||
|
<widget class="QWidget" name="OCC::ActivityWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>652</width>
|
||||||
|
<height>556</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string notr="true">Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SetDefaultConstraint</enum>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QListView" name="_activityList">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::StyledPanel</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Sunken</enum>
|
||||||
|
</property>
|
||||||
|
<property name="lineWidth">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
<property name="editTriggers">
|
||||||
|
<set>QAbstractItemView::NoEditTriggers</set>
|
||||||
|
</property>
|
||||||
|
<property name="showDropIndicator" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="defaultDropAction">
|
||||||
|
<enum>Qt::IgnoreAction</enum>
|
||||||
|
</property>
|
||||||
|
<property name="resizeMode">
|
||||||
|
<enum>QListView::Adjust</enum>
|
||||||
|
</property>
|
||||||
|
<property name="viewMode">
|
||||||
|
<enum>QListView::ListMode</enum>
|
||||||
|
</property>
|
||||||
|
<property name="modelColumn">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="_bottomLabel">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">TextLabel</string>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::RichText</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<tabstops>
|
||||||
|
<tabstop>_activityList</tabstop>
|
||||||
|
</tabstops>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
@@ -52,7 +52,6 @@
|
|||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QGuiApplication>
|
|
||||||
|
|
||||||
class QSocket;
|
class QSocket;
|
||||||
|
|
||||||
@@ -109,6 +108,7 @@ Application::Application(int &argc, char **argv)
|
|||||||
, _userTriggeredConnect(false)
|
, _userTriggeredConnect(false)
|
||||||
, _debugMode(false)
|
, _debugMode(false)
|
||||||
, _backgroundMode(false)
|
, _backgroundMode(false)
|
||||||
|
, _isQuitting(false)
|
||||||
{
|
{
|
||||||
_startedAt.start();
|
_startedAt.start();
|
||||||
|
|
||||||
@@ -123,15 +123,6 @@ Application::Application(int &argc, char **argv)
|
|||||||
// TODO: Can't set this without breaking current config paths
|
// TODO: Can't set this without breaking current config paths
|
||||||
// setOrganizationName(QLatin1String(APPLICATION_VENDOR));
|
// setOrganizationName(QLatin1String(APPLICATION_VENDOR));
|
||||||
setOrganizationDomain(QLatin1String(APPLICATION_REV_DOMAIN));
|
setOrganizationDomain(QLatin1String(APPLICATION_REV_DOMAIN));
|
||||||
|
|
||||||
// setDesktopFilename to provide wayland compatibility (in general: conformance with naming standards)
|
|
||||||
// but only on Qt >= 5.7, where setDesktopFilename was introduced
|
|
||||||
#if (QT_VERSION >= 0x050700)
|
|
||||||
QString desktopFileName = QString(QLatin1String(LINUX_APPLICATION_ID)
|
|
||||||
+ QLatin1String(".desktop"));
|
|
||||||
setDesktopFileName(desktopFileName);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
setApplicationName(_theme->appName());
|
setApplicationName(_theme->appName());
|
||||||
setWindowIcon(_theme->applicationIcon());
|
setWindowIcon(_theme->applicationIcon());
|
||||||
setAttribute(Qt::AA_UseHighDpiPixmaps, true);
|
setAttribute(Qt::AA_UseHighDpiPixmaps, true);
|
||||||
@@ -266,8 +257,6 @@ Application::Application(int &argc, char **argv)
|
|||||||
|
|
||||||
// Allow other classes to hook into isShowingSettingsDialog() signals (re-auth widgets, for example)
|
// Allow other classes to hook into isShowingSettingsDialog() signals (re-auth widgets, for example)
|
||||||
connect(_gui.data(), &ownCloudGui::isShowingSettingsDialog, this, &Application::slotGuiIsShowingSettings);
|
connect(_gui.data(), &ownCloudGui::isShowingSettingsDialog, this, &Application::slotGuiIsShowingSettings);
|
||||||
|
|
||||||
_gui->createTray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Application::~Application()
|
Application::~Application()
|
||||||
@@ -300,7 +289,7 @@ void Application::slotAccountStateRemoved(AccountState *accountState)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if there is no more account, show the wizard.
|
// if there is no more account, show the wizard.
|
||||||
if (AccountManager::instance()->accounts().isEmpty()) {
|
if (!_isQuitting && AccountManager::instance()->accounts().isEmpty()) {
|
||||||
// allow to add a new account if there is non any more. Always think
|
// allow to add a new account if there is non any more. Always think
|
||||||
// about single account theming!
|
// about single account theming!
|
||||||
OwncloudSetupWizard::runWizard(this, SLOT(slotownCloudWizardDone(int)));
|
OwncloudSetupWizard::runWizard(this, SLOT(slotownCloudWizardDone(int)));
|
||||||
@@ -323,6 +312,8 @@ void Application::slotAccountStateAdded(AccountState *accountState)
|
|||||||
|
|
||||||
void Application::slotCleanup()
|
void Application::slotCleanup()
|
||||||
{
|
{
|
||||||
|
_isQuitting = true;
|
||||||
|
|
||||||
AccountManager::instance()->save();
|
AccountManager::instance()->save();
|
||||||
FolderMan::instance()->unloadAndDeleteAllFolders();
|
FolderMan::instance()->unloadAndDeleteAllFolders();
|
||||||
|
|
||||||
@@ -392,7 +383,7 @@ void Application::slotownCloudWizardDone(int res)
|
|||||||
Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), true);
|
Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Systray::instance()->showWindow();
|
_gui->slotShowSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ private:
|
|||||||
bool _userTriggeredConnect;
|
bool _userTriggeredConnect;
|
||||||
bool _debugMode;
|
bool _debugMode;
|
||||||
bool _backgroundMode;
|
bool _backgroundMode;
|
||||||
|
bool _isQuitting;
|
||||||
|
|
||||||
ClientProxy _proxy;
|
ClientProxy _proxy;
|
||||||
|
|
||||||
|
|||||||
@@ -249,11 +249,6 @@ void ConnectionValidator::slotCapabilitiesRecieved(const QJsonDocument &json)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for the directEditing capability
|
|
||||||
QUrl directEditingURL = QUrl(caps["files"].toObject()["directEditing"].toObject()["url"].toString());
|
|
||||||
QString directEditingETag = caps["files"].toObject()["directEditing"].toObject()["etag"].toString();
|
|
||||||
_account->fetchDirectEditors(directEditingURL, directEditingETag);
|
|
||||||
|
|
||||||
fetchUser();
|
fetchUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -914,8 +914,6 @@ void Folder::slotItemCompleted(const SyncFileItemPtr &item)
|
|||||||
_folderWatcher->removePath(path() + item->_file);
|
_folderWatcher->removePath(path() + item->_file);
|
||||||
_folderWatcher->addPath(path() + item->destination());
|
_folderWatcher->addPath(path() + item->destination());
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1206,7 +1204,7 @@ QString FolderDefinition::absoluteJournalPath() const
|
|||||||
|
|
||||||
QString FolderDefinition::defaultJournalPath(AccountPtr account)
|
QString FolderDefinition::defaultJournalPath(AccountPtr account)
|
||||||
{
|
{
|
||||||
return SyncJournalDb::makeDbName(account->url(), targetPath, account->credentials()->user());
|
return SyncJournalDb::makeDbName(localPath, account->url(), targetPath, account->credentials()->user());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public:
|
|||||||
QString alias;
|
QString alias;
|
||||||
/// path on local machine
|
/// path on local machine
|
||||||
QString localPath;
|
QString localPath;
|
||||||
/// path to the journal, usually in QStandardPaths::AppDataLocation
|
/// path to the journal, usually relative to localPath
|
||||||
QString journalPath;
|
QString journalPath;
|
||||||
/// path on remote
|
/// path on remote
|
||||||
QString targetPath;
|
QString targetPath;
|
||||||
|
|||||||
@@ -210,27 +210,6 @@ void FolderMan::setupFoldersHelper(QSettings &settings, AccountStatePtr account,
|
|||||||
folderDefinition.journalPath = defaultJournalPath;
|
folderDefinition.journalPath = defaultJournalPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migration #2: journalPath now in DataAppDir, not root of local tree (cross-platform persistent user roaming files)
|
|
||||||
if (folderDefinition.journalPath.at(0) == QChar('.')) {
|
|
||||||
QFile oldJournal(folderDefinition.localPath + "/" + folderDefinition.journalPath);
|
|
||||||
QFile oldJournalShm(folderDefinition.localPath + "/" + folderDefinition.journalPath.append("-shm"));
|
|
||||||
QFile oldJournalWal(folderDefinition.localPath + "/" + folderDefinition.journalPath.append("-wal"));
|
|
||||||
|
|
||||||
folderDefinition.journalPath = defaultJournalPath;
|
|
||||||
|
|
||||||
socketApi()->slotUnregisterPath(folderAlias);
|
|
||||||
auto settings = account->settings();
|
|
||||||
|
|
||||||
Folder *f = addFolderInternal(folderDefinition, account.data());
|
|
||||||
f->saveToSettings();
|
|
||||||
|
|
||||||
oldJournal.remove();
|
|
||||||
oldJournalShm.remove();
|
|
||||||
oldJournalWal.remove();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migration: ._ files sometimes don't work
|
// Migration: ._ files sometimes don't work
|
||||||
// So if the configured journalPath is the default one ("._sync_*.db")
|
// So if the configured journalPath is the default one ("._sync_*.db")
|
||||||
// but the current default doesn't have the underscore, switch to the
|
// but the current default doesn't have the underscore, switch to the
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>516</width>
|
<width>785</width>
|
||||||
<height>523</height>
|
<height>523</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
|||||||
@@ -23,9 +23,6 @@ IconJob::IconJob(const QUrl &url, QObject *parent) :
|
|||||||
this, &IconJob::finished);
|
this, &IconJob::finished);
|
||||||
|
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
#if (QT_VERSION >= 0x050600)
|
|
||||||
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
|
||||||
#endif
|
|
||||||
_accessManager.get(request);
|
_accessManager.get(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ LegalNotice::LegalNotice(QDialog *parent)
|
|||||||
{
|
{
|
||||||
_ui->setupUi(this);
|
_ui->setupUi(this);
|
||||||
|
|
||||||
QString notice = tr("<p>Copyright 2017-2020 Nextcloud GmbH<br />"
|
QString notice = tr("<p>Copyright 2017-2019 Nextcloud GmbH<br />"
|
||||||
"Copyright 2012-2018 ownCloud GmbH</p>");
|
"Copyright 2012-2018 ownCloud GmbH</p>");
|
||||||
|
|
||||||
notice += tr("<p>Licensed under the GNU General Public License (GPL) Version 2.0 or any later version.</p>");
|
notice += tr("<p>Licensed under the GNU General Public License (GPL) Version 2.0 or any later version.</p>");
|
||||||
|
|||||||
@@ -32,11 +32,6 @@ NavigationPaneHelper::NavigationPaneHelper(FolderMan *folderMan)
|
|||||||
|
|
||||||
_updateCloudStorageRegistryTimer.setSingleShot(true);
|
_updateCloudStorageRegistryTimer.setSingleShot(true);
|
||||||
connect(&_updateCloudStorageRegistryTimer, &QTimer::timeout, this, &NavigationPaneHelper::updateCloudStorageRegistry);
|
connect(&_updateCloudStorageRegistryTimer, &QTimer::timeout, this, &NavigationPaneHelper::updateCloudStorageRegistry);
|
||||||
|
|
||||||
// Ensure that the folder integration stays persistent in Explorer,
|
|
||||||
// the uninstaller removes the folder upon updating the client.
|
|
||||||
_showInExplorerNavigationPane = !_showInExplorerNavigationPane;
|
|
||||||
setShowInExplorerNavigationPane(!_showInExplorerNavigationPane);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigationPaneHelper::setShowInExplorerNavigationPane(bool show)
|
void NavigationPaneHelper::setShowInExplorerNavigationPane(bool show)
|
||||||
@@ -144,9 +139,7 @@ void NavigationPaneHelper::updateCloudStorageRegistry()
|
|||||||
#else
|
#else
|
||||||
// This code path should only occur on Windows (the config will be false, and the checkbox invisible on other platforms).
|
// This code path should only occur on Windows (the config will be false, and the checkbox invisible on other platforms).
|
||||||
// Add runtime checks rather than #ifdefing out the whole code to help catch breakages when developing on other platforms.
|
// Add runtime checks rather than #ifdefing out the whole code to help catch breakages when developing on other platforms.
|
||||||
|
Q_ASSERT(false);
|
||||||
// Don't crash, by any means!
|
|
||||||
// Q_ASSERT(false);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>516</width>
|
<width>563</width>
|
||||||
<height>444</height>
|
<height>444</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
|||||||
@@ -73,17 +73,6 @@ void OcsShareJob::setPassword(const QString &shareId, const QString &password)
|
|||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OcsShareJob::setNote(const QString &shareId, const QString ¬e)
|
|
||||||
{
|
|
||||||
appendPath(shareId);
|
|
||||||
setVerb("PUT");
|
|
||||||
|
|
||||||
addParam(QString::fromLatin1("note"), note);
|
|
||||||
_value = note;
|
|
||||||
|
|
||||||
start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void OcsShareJob::setPublicUpload(const QString &shareId, bool publicUpload)
|
void OcsShareJob::setPublicUpload(const QString &shareId, bool publicUpload)
|
||||||
{
|
{
|
||||||
appendPath(shareId);
|
appendPath(shareId);
|
||||||
|
|||||||
@@ -61,14 +61,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
void setExpireDate(const QString &shareId, const QDate &date);
|
void setExpireDate(const QString &shareId, const QDate &date);
|
||||||
|
|
||||||
/**
|
|
||||||
* Set note a share
|
|
||||||
*
|
|
||||||
* @param note The note to a share, if the note is empty the
|
|
||||||
* share will be removed
|
|
||||||
*/
|
|
||||||
void setNote(const QString &shareId, const QString ¬e);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the password of a share
|
* Set the password of a share
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include "application.h"
|
#include "application.h"
|
||||||
#include "owncloudgui.h"
|
#include "owncloudgui.h"
|
||||||
|
#include "ocsnavigationappsjob.h"
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
#include "folderman.h"
|
#include "folderman.h"
|
||||||
#include "progressdispatcher.h"
|
#include "progressdispatcher.h"
|
||||||
@@ -45,11 +46,9 @@
|
|||||||
#include <QX11Info>
|
#include <QX11Info>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <QQmlEngine>
|
#include <QJsonDocument>
|
||||||
#include <QQmlComponent>
|
#include <QJsonObject>
|
||||||
#include <QQmlApplicationEngine>
|
#include <QJsonArray>
|
||||||
#include <QQuickItem>
|
|
||||||
#include <QQmlContext>
|
|
||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
@@ -63,32 +62,22 @@ ownCloudGui::ownCloudGui(Application *parent)
|
|||||||
#ifdef WITH_LIBCLOUDPROVIDERS
|
#ifdef WITH_LIBCLOUDPROVIDERS
|
||||||
, _bus(QDBusConnection::sessionBus())
|
, _bus(QDBusConnection::sessionBus())
|
||||||
#endif
|
#endif
|
||||||
|
, _recentActionsMenu(nullptr)
|
||||||
, _app(parent)
|
, _app(parent)
|
||||||
{
|
{
|
||||||
_tray = Systray::instance();
|
_tray = new Systray();
|
||||||
_tray->setParent(this);
|
_tray->setParent(this);
|
||||||
// for the beginning, set the offline icon until the account was verified
|
|
||||||
_tray->setIcon(Theme::instance()->folderOfflineIcon(/*systray?*/ true));
|
|
||||||
|
|
||||||
_tray->show();
|
// for the beginning, set the offline icon until the account was verified
|
||||||
|
_tray->setIcon(Theme::instance()->folderOfflineIcon(/*systray?*/ true, /*currently visible?*/ false));
|
||||||
|
|
||||||
connect(_tray.data(), &QSystemTrayIcon::activated,
|
connect(_tray.data(), &QSystemTrayIcon::activated,
|
||||||
this, &ownCloudGui::slotTrayClicked);
|
this, &ownCloudGui::slotTrayClicked);
|
||||||
|
|
||||||
connect(_tray.data(), &Systray::pauseSync,
|
setupActions();
|
||||||
this, &ownCloudGui::slotPauseAllFolders);
|
setupContextMenu();
|
||||||
|
|
||||||
connect(_tray.data(), &Systray::pauseSync,
|
_tray->show();
|
||||||
this, &ownCloudGui::slotUnpauseAllFolders);
|
|
||||||
|
|
||||||
connect(_tray.data(), &Systray::openHelp,
|
|
||||||
this, &ownCloudGui::slotHelp);
|
|
||||||
|
|
||||||
connect(_tray.data(), &Systray::openSettings,
|
|
||||||
this, &ownCloudGui::slotShowSettings);
|
|
||||||
|
|
||||||
connect(_tray.data(), &Systray::shutdown,
|
|
||||||
this, &ownCloudGui::slotShutdown);
|
|
||||||
|
|
||||||
ProgressDispatcher *pd = ProgressDispatcher::instance();
|
ProgressDispatcher *pd = ProgressDispatcher::instance();
|
||||||
connect(pd, &ProgressDispatcher::progressInfo, this,
|
connect(pd, &ProgressDispatcher::progressInfo, this,
|
||||||
@@ -98,18 +87,17 @@ ownCloudGui::ownCloudGui(Application *parent)
|
|||||||
connect(folderMan, &FolderMan::folderSyncStateChange,
|
connect(folderMan, &FolderMan::folderSyncStateChange,
|
||||||
this, &ownCloudGui::slotSyncStateChange);
|
this, &ownCloudGui::slotSyncStateChange);
|
||||||
|
|
||||||
|
connect(AccountManager::instance(), &AccountManager::accountAdded,
|
||||||
|
this, &ownCloudGui::updateContextMenuNeeded);
|
||||||
|
connect(AccountManager::instance(), &AccountManager::accountRemoved,
|
||||||
|
this, &ownCloudGui::updateContextMenuNeeded);
|
||||||
|
|
||||||
connect(Logger::instance(), &Logger::guiLog,
|
connect(Logger::instance(), &Logger::guiLog,
|
||||||
this, &ownCloudGui::slotShowTrayMessage);
|
this, &ownCloudGui::slotShowTrayMessage);
|
||||||
connect(Logger::instance(), &Logger::optionalGuiLog,
|
connect(Logger::instance(), &Logger::optionalGuiLog,
|
||||||
this, &ownCloudGui::slotShowOptionalTrayMessage);
|
this, &ownCloudGui::slotShowOptionalTrayMessage);
|
||||||
connect(Logger::instance(), &Logger::guiMessage,
|
connect(Logger::instance(), &Logger::guiMessage,
|
||||||
this, &ownCloudGui::slotShowGuiMessage);
|
this, &ownCloudGui::slotShowGuiMessage);
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void ownCloudGui::createTray()
|
|
||||||
{
|
|
||||||
_tray->create();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef WITH_LIBCLOUDPROVIDERS
|
#ifdef WITH_LIBCLOUDPROVIDERS
|
||||||
@@ -152,6 +140,13 @@ void ownCloudGui::slotOpenSettingsDialog()
|
|||||||
|
|
||||||
void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason)
|
void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason)
|
||||||
{
|
{
|
||||||
|
if (_workaroundFakeDoubleClick) {
|
||||||
|
static QElapsedTimer last_click;
|
||||||
|
if (last_click.isValid() && last_click.elapsed() < 200) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
last_click.start();
|
||||||
|
}
|
||||||
|
|
||||||
// Left click
|
// Left click
|
||||||
if (reason == QSystemTrayIcon::Trigger) {
|
if (reason == QSystemTrayIcon::Trigger) {
|
||||||
@@ -163,15 +158,17 @@ void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason)
|
|||||||
Q_ASSERT(shareDialog.data());
|
Q_ASSERT(shareDialog.data());
|
||||||
raiseDialog(shareDialog);
|
raiseDialog(shareDialog);
|
||||||
}
|
}
|
||||||
} else if (_tray->isOpen()) {
|
|
||||||
_tray->hideWindow();
|
|
||||||
} else {
|
} else {
|
||||||
if (AccountManager::instance()->accounts().isEmpty()) {
|
#ifdef Q_OS_MAC
|
||||||
this->slotOpenSettingsDialog();
|
// on macOS, a left click always opens menu.
|
||||||
} else {
|
// However if the settings dialog is already visible but hidden
|
||||||
_tray->showWindow();
|
// by other applications, this will bring it to the front.
|
||||||
|
if (!_settingsDialog.isNull() && _settingsDialog->isVisible()) {
|
||||||
|
raiseDialog(_settingsDialog.data());
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
slotOpenSettingsDialog();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// FIXME: Also make sure that any auto updater dialogue https://github.com/owncloud/client/issues/5613
|
// FIXME: Also make sure that any auto updater dialogue https://github.com/owncloud/client/issues/5613
|
||||||
@@ -181,6 +178,7 @@ void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason)
|
|||||||
void ownCloudGui::slotSyncStateChange(Folder *folder)
|
void ownCloudGui::slotSyncStateChange(Folder *folder)
|
||||||
{
|
{
|
||||||
slotComputeOverallSyncStatus();
|
slotComputeOverallSyncStatus();
|
||||||
|
updateContextMenuNeeded();
|
||||||
|
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
return; // Valid, just a general GUI redraw was needed.
|
return; // Valid, just a general GUI redraw was needed.
|
||||||
@@ -196,11 +194,16 @@ void ownCloudGui::slotSyncStateChange(Folder *folder)
|
|||||||
|| result.status() == SyncResult::Error) {
|
|| result.status() == SyncResult::Error) {
|
||||||
Logger::instance()->enterNextLogFile();
|
Logger::instance()->enterNextLogFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.status() == SyncResult::NotYetStarted) {
|
||||||
|
_settingsDialog->slotRefreshActivity(folder->accountState());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ownCloudGui::slotFoldersChanged()
|
void ownCloudGui::slotFoldersChanged()
|
||||||
{
|
{
|
||||||
slotComputeOverallSyncStatus();
|
slotComputeOverallSyncStatus();
|
||||||
|
updateContextMenuNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ownCloudGui::slotOpenPath(const QString &path)
|
void ownCloudGui::slotOpenPath(const QString &path)
|
||||||
@@ -210,6 +213,7 @@ void ownCloudGui::slotOpenPath(const QString &path)
|
|||||||
|
|
||||||
void ownCloudGui::slotAccountStateChanged()
|
void ownCloudGui::slotAccountStateChanged()
|
||||||
{
|
{
|
||||||
|
updateContextMenuNeeded();
|
||||||
slotComputeOverallSyncStatus();
|
slotComputeOverallSyncStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +239,7 @@ void ownCloudGui::slotComputeOverallSyncStatus()
|
|||||||
// Don't overwrite the status if we're currently syncing
|
// Don't overwrite the status if we're currently syncing
|
||||||
if (FolderMan::instance()->currentSyncFolder())
|
if (FolderMan::instance()->currentSyncFolder())
|
||||||
return;
|
return;
|
||||||
//_actionStatus->setText(text);
|
_actionStatus->setText(text);
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (auto a, AccountManager::instance()->accounts()) {
|
foreach (auto a, AccountManager::instance()->accounts()) {
|
||||||
@@ -255,7 +259,7 @@ void ownCloudGui::slotComputeOverallSyncStatus()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!problemAccounts.empty()) {
|
if (!problemAccounts.empty()) {
|
||||||
_tray->setIcon(Theme::instance()->folderOfflineIcon(true));
|
_tray->setIcon(Theme::instance()->folderOfflineIcon(true, contextMenuVisible()));
|
||||||
if (allDisconnected) {
|
if (allDisconnected) {
|
||||||
setStatusText(tr("Disconnected"));
|
setStatusText(tr("Disconnected"));
|
||||||
} else {
|
} else {
|
||||||
@@ -285,12 +289,12 @@ void ownCloudGui::slotComputeOverallSyncStatus()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (allSignedOut) {
|
if (allSignedOut) {
|
||||||
_tray->setIcon(Theme::instance()->folderOfflineIcon(true));
|
_tray->setIcon(Theme::instance()->folderOfflineIcon(true, contextMenuVisible()));
|
||||||
_tray->setToolTip(tr("Please sign in"));
|
_tray->setToolTip(tr("Please sign in"));
|
||||||
setStatusText(tr("Signed out"));
|
setStatusText(tr("Signed out"));
|
||||||
return;
|
return;
|
||||||
} else if (allPaused) {
|
} else if (allPaused) {
|
||||||
_tray->setIcon(Theme::instance()->syncStateIcon(SyncResult::Paused, true));
|
_tray->setIcon(Theme::instance()->syncStateIcon(SyncResult::Paused, true, contextMenuVisible()));
|
||||||
_tray->setToolTip(tr("Account synchronization is disabled"));
|
_tray->setToolTip(tr("Account synchronization is disabled"));
|
||||||
setStatusText(tr("Synchronization is paused"));
|
setStatusText(tr("Synchronization is paused"));
|
||||||
return;
|
return;
|
||||||
@@ -317,7 +321,7 @@ void ownCloudGui::slotComputeOverallSyncStatus()
|
|||||||
iconStatus = SyncResult::Problem;
|
iconStatus = SyncResult::Problem;
|
||||||
}
|
}
|
||||||
|
|
||||||
QIcon statusIcon = Theme::instance()->syncStateIcon(iconStatus, true);
|
QIcon statusIcon = Theme::instance()->syncStateIcon(iconStatus, true, contextMenuVisible());
|
||||||
_tray->setIcon(statusIcon);
|
_tray->setIcon(statusIcon);
|
||||||
|
|
||||||
// create the tray blob message, check if we have an defined state
|
// create the tray blob message, check if we have an defined state
|
||||||
@@ -355,6 +359,381 @@ void ownCloudGui::slotComputeOverallSyncStatus()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ownCloudGui::addAccountContextMenu(AccountStatePtr accountState, QMenu *menu, bool separateMenu)
|
||||||
|
{
|
||||||
|
// Only show the name in the action if it's not part of an
|
||||||
|
// account sub menu.
|
||||||
|
QString browserOpen = tr("Open in browser");
|
||||||
|
if (!separateMenu) {
|
||||||
|
browserOpen = tr("Open %1 in browser").arg(Theme::instance()->appNameGUI());
|
||||||
|
}
|
||||||
|
auto actionOpenoC = menu->addAction(browserOpen);
|
||||||
|
actionOpenoC->setProperty(propertyAccountC, QVariant::fromValue(accountState->account()));
|
||||||
|
QObject::connect(actionOpenoC, &QAction::triggered, this, &ownCloudGui::slotOpenOwnCloud);
|
||||||
|
|
||||||
|
FolderMan *folderMan = FolderMan::instance();
|
||||||
|
bool firstFolder = true;
|
||||||
|
bool singleSyncFolder = folderMan->map().size() == 1 && Theme::instance()->singleSyncFolder();
|
||||||
|
bool onePaused = false;
|
||||||
|
bool allPaused = true;
|
||||||
|
foreach (Folder *folder, folderMan->map()) {
|
||||||
|
if (folder->accountState() != accountState.data()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (folder->syncPaused()) {
|
||||||
|
onePaused = true;
|
||||||
|
} else {
|
||||||
|
allPaused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstFolder && !singleSyncFolder) {
|
||||||
|
firstFolder = false;
|
||||||
|
menu->addSeparator();
|
||||||
|
menu->addAction(tr("Managed Folders:"))->setDisabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction *action = menu->addAction(tr("Open folder '%1'").arg(folder->shortGuiLocalPath()));
|
||||||
|
auto alias = folder->alias();
|
||||||
|
connect(action, &QAction::triggered, this, [this, alias] { this->slotFolderOpenAction(alias); });
|
||||||
|
}
|
||||||
|
|
||||||
|
menu->addSeparator();
|
||||||
|
if (separateMenu) {
|
||||||
|
if (onePaused) {
|
||||||
|
QAction *enable = menu->addAction(tr("Resume all folders"));
|
||||||
|
enable->setProperty(propertyAccountC, QVariant::fromValue(accountState));
|
||||||
|
connect(enable, &QAction::triggered, this, &ownCloudGui::slotUnpauseAllFolders);
|
||||||
|
}
|
||||||
|
if (!allPaused) {
|
||||||
|
QAction *enable = menu->addAction(tr("Pause all folders"));
|
||||||
|
enable->setProperty(propertyAccountC, QVariant::fromValue(accountState));
|
||||||
|
connect(enable, &QAction::triggered, this, &ownCloudGui::slotPauseAllFolders);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountState->isSignedOut()) {
|
||||||
|
QAction *signin = menu->addAction(tr("Log in …"));
|
||||||
|
signin->setProperty(propertyAccountC, QVariant::fromValue(accountState));
|
||||||
|
connect(signin, &QAction::triggered, this, &ownCloudGui::slotLogin);
|
||||||
|
} else {
|
||||||
|
QAction *signout = menu->addAction(tr("Log out"));
|
||||||
|
signout->setProperty(propertyAccountC, QVariant::fromValue(accountState));
|
||||||
|
connect(signout, &QAction::triggered, this, &ownCloudGui::slotLogout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ownCloudGui::slotContextMenuAboutToShow()
|
||||||
|
{
|
||||||
|
_contextMenuVisibleManual = true;
|
||||||
|
|
||||||
|
// Update icon in sys tray, as it might change depending on the context menu state
|
||||||
|
slotComputeOverallSyncStatus();
|
||||||
|
|
||||||
|
if (!_workaroundNoAboutToShowUpdate) {
|
||||||
|
updateContextMenu();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ownCloudGui::slotContextMenuAboutToHide()
|
||||||
|
{
|
||||||
|
_contextMenuVisibleManual = false;
|
||||||
|
|
||||||
|
// Update icon in sys tray, as it might change depending on the context menu state
|
||||||
|
slotComputeOverallSyncStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ownCloudGui::contextMenuVisible() const
|
||||||
|
{
|
||||||
|
// On some platforms isVisible doesn't work and always returns false,
|
||||||
|
// elsewhere aboutToHide is unreliable.
|
||||||
|
if (_workaroundManualVisibility)
|
||||||
|
return _contextMenuVisibleManual;
|
||||||
|
return _contextMenu->isVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool minimalTrayMenu()
|
||||||
|
{
|
||||||
|
static QByteArray var = qgetenv("OWNCLOUD_MINIMAL_TRAY_MENU");
|
||||||
|
return !var.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool updateWhileVisible()
|
||||||
|
{
|
||||||
|
static QByteArray var = qgetenv("OWNCLOUD_TRAY_UPDATE_WHILE_VISIBLE");
|
||||||
|
if (var == "1") {
|
||||||
|
return true;
|
||||||
|
} else if (var == "0") {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// triggers bug on OS X: https://bugreports.qt.io/browse/QTBUG-54845
|
||||||
|
// or flickering on Xubuntu
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static QByteArray envForceQDBusTrayWorkaround()
|
||||||
|
{
|
||||||
|
static QByteArray var = qgetenv("OWNCLOUD_FORCE_QDBUS_TRAY_WORKAROUND");
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QByteArray envForceWorkaroundShowAndHideTray()
|
||||||
|
{
|
||||||
|
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_SHOW_HIDE");
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QByteArray envForceWorkaroundNoAboutToShowUpdate()
|
||||||
|
{
|
||||||
|
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_NO_ABOUT_TO_SHOW");
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QByteArray envForceWorkaroundFakeDoubleClick()
|
||||||
|
{
|
||||||
|
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_FAKE_DOUBLE_CLICK");
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QByteArray envForceWorkaroundManualVisibility()
|
||||||
|
{
|
||||||
|
static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_MANUAL_VISIBILITY");
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ownCloudGui::setupContextMenu()
|
||||||
|
{
|
||||||
|
if (_contextMenu) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_contextMenu.reset(new QMenu());
|
||||||
|
_contextMenu->setTitle(Theme::instance()->appNameGUI());
|
||||||
|
|
||||||
|
_recentActionsMenu = new QMenu(tr("Recent Changes"), _contextMenu.data());
|
||||||
|
|
||||||
|
// this must be called only once after creating the context menu, or
|
||||||
|
// it will trigger a bug in Ubuntu's SNI bridge patch (11.10, 12.04).
|
||||||
|
_tray->setContextMenu(_contextMenu.data());
|
||||||
|
|
||||||
|
// The tray menu is surprisingly problematic. Being able to switch to
|
||||||
|
// a minimal version of it is a useful workaround and testing tool.
|
||||||
|
if (minimalTrayMenu()) {
|
||||||
|
_contextMenu->addAction(_actionQuit);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto applyEnvVariable = [](bool *sw, const QByteArray &value) {
|
||||||
|
if (value == "1")
|
||||||
|
*sw = true;
|
||||||
|
if (value == "0")
|
||||||
|
*sw = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is an old compound flag that people might still depend on
|
||||||
|
bool qdbusmenuWorkarounds = false;
|
||||||
|
applyEnvVariable(&qdbusmenuWorkarounds, envForceQDBusTrayWorkaround());
|
||||||
|
if (qdbusmenuWorkarounds) {
|
||||||
|
_workaroundFakeDoubleClick = true;
|
||||||
|
_workaroundNoAboutToShowUpdate = true;
|
||||||
|
_workaroundShowAndHideTray = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
// https://bugreports.qt.io/browse/QTBUG-54633
|
||||||
|
_workaroundNoAboutToShowUpdate = true;
|
||||||
|
_workaroundManualVisibility = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
// For KDE sessions if the platform plugin is missing,
|
||||||
|
// neither aboutToShow() updates nor the isVisible() call
|
||||||
|
// work. At least aboutToHide is reliable.
|
||||||
|
// https://github.com/owncloud/client/issues/6545
|
||||||
|
static QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP");
|
||||||
|
static QByteArray desktopSession = qgetenv("DESKTOP_SESSION");
|
||||||
|
bool isKde =
|
||||||
|
xdgCurrentDesktop.contains("KDE")
|
||||||
|
|| desktopSession.contains("plasma")
|
||||||
|
|| desktopSession.contains("kde");
|
||||||
|
QObject *platformMenu = reinterpret_cast<QObject *>(_tray->contextMenu()->platformMenu());
|
||||||
|
if (isKde && platformMenu && platformMenu->metaObject()->className() == QLatin1String("QDBusPlatformMenu")) {
|
||||||
|
_workaroundManualVisibility = true;
|
||||||
|
_workaroundNoAboutToShowUpdate = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
applyEnvVariable(&_workaroundNoAboutToShowUpdate, envForceWorkaroundNoAboutToShowUpdate());
|
||||||
|
applyEnvVariable(&_workaroundFakeDoubleClick, envForceWorkaroundFakeDoubleClick());
|
||||||
|
applyEnvVariable(&_workaroundShowAndHideTray, envForceWorkaroundShowAndHideTray());
|
||||||
|
applyEnvVariable(&_workaroundManualVisibility, envForceWorkaroundManualVisibility());
|
||||||
|
|
||||||
|
qCInfo(lcApplication) << "Tray menu workarounds:"
|
||||||
|
<< "noabouttoshow:" << _workaroundNoAboutToShowUpdate
|
||||||
|
<< "fakedoubleclick:" << _workaroundFakeDoubleClick
|
||||||
|
<< "showhide:" << _workaroundShowAndHideTray
|
||||||
|
<< "manualvisibility:" << _workaroundManualVisibility;
|
||||||
|
|
||||||
|
|
||||||
|
connect(&_delayedTrayUpdateTimer, &QTimer::timeout, this, &ownCloudGui::updateContextMenu);
|
||||||
|
_delayedTrayUpdateTimer.setInterval(2 * 1000);
|
||||||
|
_delayedTrayUpdateTimer.setSingleShot(true);
|
||||||
|
|
||||||
|
connect(_contextMenu.data(), SIGNAL(aboutToShow()), SLOT(slotContextMenuAboutToShow()));
|
||||||
|
// unfortunately aboutToHide is unreliable, it seems to work on OSX though
|
||||||
|
connect(_contextMenu.data(), SIGNAL(aboutToHide()), SLOT(slotContextMenuAboutToHide()));
|
||||||
|
|
||||||
|
// Populate the context menu now.
|
||||||
|
updateContextMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ownCloudGui::updateContextMenu()
|
||||||
|
{
|
||||||
|
if (minimalTrayMenu()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's visible, we can't update live, and it won't be updated lazily: reschedule
|
||||||
|
if (contextMenuVisible() && !updateWhileVisible() && _workaroundNoAboutToShowUpdate) {
|
||||||
|
if (!_delayedTrayUpdateTimer.isActive()) {
|
||||||
|
_delayedTrayUpdateTimer.start();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_workaroundShowAndHideTray) {
|
||||||
|
// To make tray menu updates work with these bugs (see setupContextMenu)
|
||||||
|
// we need to hide and show the tray icon. We don't want to do that
|
||||||
|
// while it's visible!
|
||||||
|
if (contextMenuVisible()) {
|
||||||
|
if (!_delayedTrayUpdateTimer.isActive()) {
|
||||||
|
_delayedTrayUpdateTimer.start();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_tray->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
_contextMenu->clear();
|
||||||
|
slotRebuildRecentMenus();
|
||||||
|
|
||||||
|
// We must call deleteLater because we might be called from the press in one of the actions.
|
||||||
|
foreach (auto menu, _accountMenus) {
|
||||||
|
menu->deleteLater();
|
||||||
|
}
|
||||||
|
_accountMenus.clear();
|
||||||
|
|
||||||
|
auto accountList = AccountManager::instance()->accounts();
|
||||||
|
|
||||||
|
bool isConfigured = (!accountList.isEmpty());
|
||||||
|
bool atLeastOneConnected = false;
|
||||||
|
bool atLeastOnePaused = false;
|
||||||
|
bool atLeastOneNotPaused = false;
|
||||||
|
foreach (auto a, accountList) {
|
||||||
|
if (a->isConnected()) {
|
||||||
|
atLeastOneConnected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (auto f, FolderMan::instance()->map()) {
|
||||||
|
if (f->syncPaused()) {
|
||||||
|
atLeastOnePaused = true;
|
||||||
|
} else {
|
||||||
|
atLeastOneNotPaused = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountList.count() > 1) {
|
||||||
|
foreach (AccountStatePtr account, accountList) {
|
||||||
|
QMenu *accountMenu = new QMenu(account->account()->displayName(), _contextMenu.data());
|
||||||
|
_accountMenus.append(accountMenu);
|
||||||
|
_contextMenu->addMenu(accountMenu);
|
||||||
|
|
||||||
|
addAccountContextMenu(account, accountMenu, true);
|
||||||
|
fetchNavigationApps(account);
|
||||||
|
}
|
||||||
|
} else if (accountList.count() == 1) {
|
||||||
|
addAccountContextMenu(accountList.first(), _contextMenu.data(), false);
|
||||||
|
fetchNavigationApps(accountList.first());
|
||||||
|
}
|
||||||
|
|
||||||
|
_contextMenu->addSeparator();
|
||||||
|
|
||||||
|
_contextMenu->addAction(_actionStatus);
|
||||||
|
if (isConfigured && atLeastOneConnected) {
|
||||||
|
_contextMenu->addMenu(_recentActionsMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
_contextMenu->addSeparator();
|
||||||
|
|
||||||
|
if (_navLinksMenu) {
|
||||||
|
_contextMenu->addMenu(_navLinksMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
_contextMenu->addSeparator();
|
||||||
|
|
||||||
|
if (accountList.isEmpty()) {
|
||||||
|
_contextMenu->addAction(_actionNewAccountWizard);
|
||||||
|
}
|
||||||
|
_contextMenu->addAction(_actionSettings);
|
||||||
|
if (!Theme::instance()->helpUrl().isEmpty()) {
|
||||||
|
_contextMenu->addAction(_actionHelp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_actionCrash) {
|
||||||
|
_contextMenu->addAction(_actionCrash);
|
||||||
|
}
|
||||||
|
|
||||||
|
_contextMenu->addSeparator();
|
||||||
|
|
||||||
|
if (atLeastOnePaused) {
|
||||||
|
QString text;
|
||||||
|
if (accountList.count() > 1) {
|
||||||
|
text = tr("Resume all synchronization");
|
||||||
|
} else {
|
||||||
|
text = tr("Resume synchronization");
|
||||||
|
}
|
||||||
|
QAction *action = _contextMenu->addAction(text);
|
||||||
|
connect(action, &QAction::triggered, this, &ownCloudGui::slotUnpauseAllFolders);
|
||||||
|
}
|
||||||
|
if (atLeastOneNotPaused) {
|
||||||
|
QString text;
|
||||||
|
if (accountList.count() > 1) {
|
||||||
|
text = tr("Pause all synchronization");
|
||||||
|
} else {
|
||||||
|
text = tr("Pause synchronization");
|
||||||
|
}
|
||||||
|
QAction *action = _contextMenu->addAction(text);
|
||||||
|
connect(action, &QAction::triggered, this, &ownCloudGui::slotPauseAllFolders);
|
||||||
|
}
|
||||||
|
_contextMenu->addAction(_actionQuit);
|
||||||
|
|
||||||
|
if (_workaroundShowAndHideTray) {
|
||||||
|
_tray->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ownCloudGui::updateContextMenuNeeded()
|
||||||
|
{
|
||||||
|
// if it's visible and we can update live: update now
|
||||||
|
if (contextMenuVisible() && updateWhileVisible()) {
|
||||||
|
// Note: don't update while visible on OSX
|
||||||
|
// https://bugreports.qt.io/browse/QTBUG-54845
|
||||||
|
updateContextMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we can't lazily update: update later
|
||||||
|
if (_workaroundNoAboutToShowUpdate) {
|
||||||
|
// Note: don't update immediately even in the invisible case
|
||||||
|
// as that can lead to extremely frequent menu updates
|
||||||
|
if (!_delayedTrayUpdateTimer.isActive()) {
|
||||||
|
_delayedTrayUpdateTimer.start();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ownCloudGui::slotShowTrayMessage(const QString &title, const QString &msg)
|
void ownCloudGui::slotShowTrayMessage(const QString &title, const QString &msg)
|
||||||
{
|
{
|
||||||
if (_tray)
|
if (_tray)
|
||||||
@@ -368,6 +747,7 @@ void ownCloudGui::slotShowOptionalTrayMessage(const QString &title, const QStrin
|
|||||||
slotShowTrayMessage(title, msg);
|
slotShowTrayMessage(title, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* open the folder with the given Alias
|
* open the folder with the given Alias
|
||||||
*/
|
*/
|
||||||
@@ -391,17 +771,156 @@ void ownCloudGui::slotFolderOpenAction(const QString &alias)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ownCloudGui::setupActions()
|
||||||
|
{
|
||||||
|
_actionStatus = new QAction(tr("Unknown status"), this);
|
||||||
|
_actionStatus->setEnabled(false);
|
||||||
|
_navLinksMenu = new QMenu(tr("Apps"));
|
||||||
|
_navLinksMenu->setEnabled(false);
|
||||||
|
_actionSettings = new QAction(tr("Settings …"), this);
|
||||||
|
_actionNewAccountWizard = new QAction(tr("New account …"), this);
|
||||||
|
_actionRecent = new QAction(tr("View more activity …"), this);
|
||||||
|
_actionRecent->setEnabled(true);
|
||||||
|
|
||||||
|
QObject::connect(_actionRecent, &QAction::triggered, this, &ownCloudGui::slotShowSyncProtocol);
|
||||||
|
QObject::connect(_actionSettings, &QAction::triggered, this, &ownCloudGui::slotShowSettings);
|
||||||
|
QObject::connect(_actionNewAccountWizard, &QAction::triggered, this, &ownCloudGui::slotNewAccountWizard);
|
||||||
|
_actionHelp = new QAction(tr("Help"), this);
|
||||||
|
QObject::connect(_actionHelp, &QAction::triggered, this, &ownCloudGui::slotHelp);
|
||||||
|
_actionQuit = new QAction(tr("Quit %1").arg(Theme::instance()->appNameGUI()), this);
|
||||||
|
QObject::connect(_actionQuit, SIGNAL(triggered(bool)), _app, SLOT(quit()));
|
||||||
|
|
||||||
|
if (_app->debugMode()) {
|
||||||
|
_actionCrash = new QAction(tr("Crash now", "Only shows in debug mode to allow testing the crash handler"), this);
|
||||||
|
connect(_actionCrash, &QAction::triggered, _app, &Application::slotCrash);
|
||||||
|
} else {
|
||||||
|
_actionCrash = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ownCloudGui::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){
|
||||||
|
if(statusCode == 200){
|
||||||
|
qCDebug(lcApplication) << "New navigation apps ETag Response Header received " << value;
|
||||||
|
auto account = qvariant_cast<AccountStatePtr>(sender()->property(propertyAccountC));
|
||||||
|
account->setNavigationAppsEtagResponseHeader(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ownCloudGui::fetchNavigationApps(AccountStatePtr account){
|
||||||
|
OcsNavigationAppsJob *job = new OcsNavigationAppsJob(account->account());
|
||||||
|
job->setProperty(propertyAccountC, QVariant::fromValue(account));
|
||||||
|
job->addRawHeader("If-None-Match", account->navigationAppsEtagResponseHeader());
|
||||||
|
connect(job, &OcsNavigationAppsJob::appsJobFinished, this, &ownCloudGui::slotNavigationAppsFetched);
|
||||||
|
connect(job, &OcsNavigationAppsJob::etagResponseHeaderReceived, this, &ownCloudGui::slotEtagResponseHeaderReceived);
|
||||||
|
connect(job, &OcsNavigationAppsJob::ocsError, this, &ownCloudGui::slotOcsError);
|
||||||
|
job->getNavigationApps();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ownCloudGui::buildNavigationAppsMenu(AccountStatePtr account, QMenu *accountMenu){
|
||||||
|
auto navLinks = _navApps.value(account);
|
||||||
|
|
||||||
|
_navLinksMenu->clear();
|
||||||
|
_navLinksMenu->setEnabled(navLinks.size() > 0);
|
||||||
|
|
||||||
|
if(navLinks.size() > 0){
|
||||||
|
// when there is only one account add the nav links above the settings
|
||||||
|
QAction *actionBefore = _actionSettings;
|
||||||
|
|
||||||
|
// when there is more than one account add the nav links above pause/unpause folder or logout action
|
||||||
|
if(AccountManager::instance()->accounts().size() > 1){
|
||||||
|
foreach(QAction *action, accountMenu->actions()){
|
||||||
|
|
||||||
|
// pause/unpause folder and logout actions have propertyAccountC
|
||||||
|
if(auto actionAccount = qvariant_cast<AccountStatePtr>(action->property(propertyAccountC))){
|
||||||
|
if(actionAccount == account){
|
||||||
|
actionBefore = action;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create submenu with links
|
||||||
|
foreach (const QJsonValue &value, navLinks) {
|
||||||
|
auto navLink = value.toObject();
|
||||||
|
QAction *action = new QAction(navLink.value("name").toString(), this);
|
||||||
|
QUrl href(navLink.value("href").toString());
|
||||||
|
connect(action, &QAction::triggered, this, [href] { QDesktopServices::openUrl(href); });
|
||||||
|
_navLinksMenu->addAction(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ownCloudGui::slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode)
|
||||||
|
{
|
||||||
|
if(auto account = qvariant_cast<AccountStatePtr>(sender()->property(propertyAccountC))){
|
||||||
|
if (statusCode == 304) {
|
||||||
|
qCWarning(lcApplication) << "Status code " << statusCode << " Not Modified - No new navigation apps.";
|
||||||
|
} else {
|
||||||
|
if(!reply.isEmpty()){
|
||||||
|
auto element = reply.object().value("ocs").toObject().value("data");
|
||||||
|
auto navLinks = element.toArray();
|
||||||
|
_navApps.insert(account, navLinks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO see pull #523
|
||||||
|
auto accountList = AccountManager::instance()->accounts();
|
||||||
|
if(accountList.size() > 1){
|
||||||
|
// the list of apps will be displayed under the account that it belongs to
|
||||||
|
foreach (QMenu *accountMenu, _accountMenus) {
|
||||||
|
if(accountMenu->title() == account->account()->displayName()){
|
||||||
|
buildNavigationAppsMenu(account, accountMenu);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(accountList.size() == 1){
|
||||||
|
buildNavigationAppsMenu(account, _contextMenu.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ownCloudGui::slotOcsError(int statusCode, const QString &message)
|
||||||
|
{
|
||||||
|
emit serverError(statusCode, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ownCloudGui::slotRebuildRecentMenus()
|
||||||
|
{
|
||||||
|
_recentActionsMenu->clear();
|
||||||
|
if (!_recentItemsActions.isEmpty()) {
|
||||||
|
foreach (QAction *a, _recentItemsActions) {
|
||||||
|
_recentActionsMenu->addAction(a);
|
||||||
|
}
|
||||||
|
_recentActionsMenu->addSeparator();
|
||||||
|
} else {
|
||||||
|
_recentActionsMenu->addAction(tr("No items synced recently"))->setEnabled(false);
|
||||||
|
}
|
||||||
|
// add a more... entry.
|
||||||
|
_recentActionsMenu->addAction(_actionRecent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the completion of a given item should show up in the
|
||||||
|
/// 'Recent Activity' menu
|
||||||
|
static bool shouldShowInRecentsMenu(const SyncFileItem &item)
|
||||||
|
{
|
||||||
|
return !Progress::isIgnoredKind(item._status)
|
||||||
|
&& item._instruction != CSYNC_INSTRUCTION_EVAL
|
||||||
|
&& item._instruction != CSYNC_INSTRUCTION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &progress)
|
void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &progress)
|
||||||
{
|
{
|
||||||
Q_UNUSED(folder);
|
Q_UNUSED(folder);
|
||||||
|
|
||||||
if (progress.status() == ProgressInfo::Discovery) {
|
if (progress.status() == ProgressInfo::Discovery) {
|
||||||
if (!progress._currentDiscoveredRemoteFolder.isEmpty()) {
|
if (!progress._currentDiscoveredRemoteFolder.isEmpty()) {
|
||||||
//_actionStatus->setText(tr("Checking for changes in remote '%1'")
|
_actionStatus->setText(tr("Checking for changes in remote '%1'")
|
||||||
//.arg(progress._currentDiscoveredRemoteFolder));
|
.arg(progress._currentDiscoveredRemoteFolder));
|
||||||
} else if (!progress._currentDiscoveredLocalFolder.isEmpty()) {
|
} else if (!progress._currentDiscoveredLocalFolder.isEmpty()) {
|
||||||
//_actionStatus->setText(tr("Checking for changes in local '%1'")
|
_actionStatus->setText(tr("Checking for changes in local '%1'")
|
||||||
//.arg(progress._currentDiscoveredLocalFolder));
|
.arg(progress._currentDiscoveredLocalFolder));
|
||||||
}
|
}
|
||||||
} else if (progress.status() == ProgressInfo::Done) {
|
} else if (progress.status() == ProgressInfo::Done) {
|
||||||
QTimer::singleShot(2000, this, &ownCloudGui::slotComputeOverallSyncStatus);
|
QTimer::singleShot(2000, this, &ownCloudGui::slotComputeOverallSyncStatus);
|
||||||
@@ -424,7 +943,7 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &
|
|||||||
.arg(currentFile)
|
.arg(currentFile)
|
||||||
.arg(totalFileCount);
|
.arg(totalFileCount);
|
||||||
}
|
}
|
||||||
//_actionStatus->setText(msg);
|
_actionStatus->setText(msg);
|
||||||
} else {
|
} else {
|
||||||
QString totalSizeStr = Utility::octetsToString(progress.totalSize());
|
QString totalSizeStr = Utility::octetsToString(progress.totalSize());
|
||||||
QString msg;
|
QString msg;
|
||||||
@@ -435,10 +954,18 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &
|
|||||||
msg = tr("Syncing %1")
|
msg = tr("Syncing %1")
|
||||||
.arg(totalSizeStr);
|
.arg(totalSizeStr);
|
||||||
}
|
}
|
||||||
//_actionStatus->setText(msg);
|
_actionStatus->setText(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!progress._lastCompletedItem.isEmpty()) {
|
_actionRecent->setIcon(QIcon()); // Fixme: Set a "in-progress"-item eventually.
|
||||||
|
|
||||||
|
if (!progress._lastCompletedItem.isEmpty()
|
||||||
|
&& shouldShowInRecentsMenu(progress._lastCompletedItem)) {
|
||||||
|
if (Progress::isWarningKind(progress._lastCompletedItem._status)) {
|
||||||
|
// display a warn icon if warnings happened.
|
||||||
|
QIcon warnIcon(":/client/resources/warning");
|
||||||
|
_actionRecent->setIcon(warnIcon);
|
||||||
|
}
|
||||||
|
|
||||||
QString kindStr = Progress::asResultString(progress._lastCompletedItem);
|
QString kindStr = Progress::asResultString(progress._lastCompletedItem);
|
||||||
QString timeStr = QTime::currentTime().toString("hh:mm");
|
QString timeStr = QTime::currentTime().toString("hh:mm");
|
||||||
@@ -457,6 +984,12 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &
|
|||||||
_recentItemsActions.takeFirst()->deleteLater();
|
_recentItemsActions.takeFirst()->deleteLater();
|
||||||
}
|
}
|
||||||
_recentItemsActions.append(action);
|
_recentItemsActions.append(action);
|
||||||
|
|
||||||
|
// Update the "Recent" menu if the context menu is being shown,
|
||||||
|
// otherwise it'll be updated later, when the context menu is opened.
|
||||||
|
if (updateWhileVisible() && contextMenuVisible()) {
|
||||||
|
slotRebuildRecentMenus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,7 +1097,6 @@ void ownCloudGui::slotShutdown()
|
|||||||
_settingsDialog->close();
|
_settingsDialog->close();
|
||||||
if (!_logBrowser.isNull())
|
if (!_logBrowser.isNull())
|
||||||
_logBrowser->deleteLater();
|
_logBrowser->deleteLater();
|
||||||
_app->quit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ownCloudGui::slotToggleLogBrowser()
|
void ownCloudGui::slotToggleLogBrowser()
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ public:
|
|||||||
void setupCloudProviders();
|
void setupCloudProviders();
|
||||||
bool cloudProviderApiAvailable();
|
bool cloudProviderApiAvailable();
|
||||||
#endif
|
#endif
|
||||||
void createTray();
|
|
||||||
|
/// Whether the tray menu is visible
|
||||||
|
bool contextMenuVisible() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void setupProxy();
|
void setupProxy();
|
||||||
@@ -71,10 +73,16 @@ signals:
|
|||||||
void isShowingSettingsDialog();
|
void isShowingSettingsDialog();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
void setupContextMenu();
|
||||||
|
void updateContextMenu();
|
||||||
|
void updateContextMenuNeeded();
|
||||||
|
void slotContextMenuAboutToShow();
|
||||||
|
void slotContextMenuAboutToHide();
|
||||||
void slotComputeOverallSyncStatus();
|
void slotComputeOverallSyncStatus();
|
||||||
void slotShowTrayMessage(const QString &title, const QString &msg);
|
void slotShowTrayMessage(const QString &title, const QString &msg);
|
||||||
void slotShowOptionalTrayMessage(const QString &title, const QString &msg);
|
void slotShowOptionalTrayMessage(const QString &title, const QString &msg);
|
||||||
void slotFolderOpenAction(const QString &alias);
|
void slotFolderOpenAction(const QString &alias);
|
||||||
|
void slotRebuildRecentMenus();
|
||||||
void slotUpdateProgress(const QString &folder, const ProgressInfo &progress);
|
void slotUpdateProgress(const QString &folder, const ProgressInfo &progress);
|
||||||
void slotShowGuiMessage(const QString &title, const QString &message);
|
void slotShowGuiMessage(const QString &title, const QString &message);
|
||||||
void slotFoldersChanged();
|
void slotFoldersChanged();
|
||||||
@@ -91,6 +99,8 @@ public slots:
|
|||||||
void slotOpenPath(const QString &path);
|
void slotOpenPath(const QString &path);
|
||||||
void slotAccountStateChanged();
|
void slotAccountStateChanged();
|
||||||
void slotTrayMessageIfServerUnsupported(Account *account);
|
void slotTrayMessageIfServerUnsupported(Account *account);
|
||||||
|
void slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode);
|
||||||
|
void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,6 +114,9 @@ public slots:
|
|||||||
|
|
||||||
void slotRemoveDestroyedShareDialogs();
|
void slotRemoveDestroyedShareDialogs();
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void slotOcsError(int statusCode, const QString &message);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void slotLogin();
|
void slotLogin();
|
||||||
void slotLogout();
|
void slotLogout();
|
||||||
@@ -113,21 +126,47 @@ private slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void setPauseOnAllFoldersHelper(bool pause);
|
void setPauseOnAllFoldersHelper(bool pause);
|
||||||
|
void setupActions();
|
||||||
|
void addAccountContextMenu(AccountStatePtr accountState, QMenu *menu, bool separateMenu);
|
||||||
|
void fetchNavigationApps(AccountStatePtr account);
|
||||||
|
void buildNavigationAppsMenu(AccountStatePtr account, QMenu *accountMenu);
|
||||||
|
|
||||||
QPointer<Systray> _tray;
|
QPointer<Systray> _tray;
|
||||||
QPointer<SettingsDialog> _settingsDialog;
|
QPointer<SettingsDialog> _settingsDialog;
|
||||||
QPointer<LogBrowser> _logBrowser;
|
QPointer<LogBrowser> _logBrowser;
|
||||||
|
// tray's menu
|
||||||
|
QScopedPointer<QMenu> _contextMenu;
|
||||||
|
|
||||||
|
// Manually tracking whether the context menu is visible via aboutToShow
|
||||||
|
// and aboutToHide. Unfortunately aboutToHide isn't reliable everywhere
|
||||||
|
// so this only gets used with _workaroundManualVisibility (when the tray's
|
||||||
|
// isVisible() is unreliable)
|
||||||
|
bool _contextMenuVisibleManual = false;
|
||||||
|
|
||||||
#ifdef WITH_LIBCLOUDPROVIDERS
|
#ifdef WITH_LIBCLOUDPROVIDERS
|
||||||
QDBusConnection _bus;
|
QDBusConnection _bus;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
QMenu *_recentActionsMenu;
|
||||||
|
QVector<QMenu *> _accountMenus;
|
||||||
|
bool _workaroundShowAndHideTray = false;
|
||||||
|
bool _workaroundNoAboutToShowUpdate = false;
|
||||||
|
bool _workaroundFakeDoubleClick = false;
|
||||||
|
bool _workaroundManualVisibility = false;
|
||||||
|
QTimer _delayedTrayUpdateTimer;
|
||||||
QMap<QString, QPointer<ShareDialog>> _shareDialogs;
|
QMap<QString, QPointer<ShareDialog>> _shareDialogs;
|
||||||
|
|
||||||
QAction *_actionNewAccountWizard;
|
QAction *_actionNewAccountWizard;
|
||||||
QAction *_actionSettings;
|
QAction *_actionSettings;
|
||||||
|
QAction *_actionStatus;
|
||||||
QAction *_actionEstimate;
|
QAction *_actionEstimate;
|
||||||
|
QAction *_actionRecent;
|
||||||
|
QAction *_actionHelp;
|
||||||
|
QAction *_actionQuit;
|
||||||
|
QAction *_actionCrash;
|
||||||
|
|
||||||
|
QMenu *_navLinksMenu;
|
||||||
|
QMap<AccountStatePtr, QJsonArray> _navApps;
|
||||||
|
|
||||||
QList<QAction *> _recentItemsActions;
|
QList<QAction *> _recentItemsActions;
|
||||||
Application *_app;
|
Application *_app;
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
#include "NotificationHandler.h"
|
/*
|
||||||
|
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "servernotificationhandler.h"
|
||||||
#include "accountstate.h"
|
#include "accountstate.h"
|
||||||
#include "capabilities.h"
|
#include "capabilities.h"
|
||||||
#include "networkjobs.h"
|
#include "networkjobs.h"
|
||||||
@@ -17,7 +30,7 @@ const QString notificationsPath = QLatin1String("ocs/v2.php/apps/notifications/a
|
|||||||
const char propertyAccountStateC[] = "oc_account_state";
|
const char propertyAccountStateC[] = "oc_account_state";
|
||||||
const int successStatusCode = 200;
|
const int successStatusCode = 200;
|
||||||
const int notModifiedStatusCode = 304;
|
const int notModifiedStatusCode = 304;
|
||||||
QMap<int, QByteArray> ServerNotificationHandler::iconCache;
|
QMap<int, QIcon> ServerNotificationHandler::iconCache;
|
||||||
|
|
||||||
ServerNotificationHandler::ServerNotificationHandler(AccountState *accountState, QObject *parent)
|
ServerNotificationHandler::ServerNotificationHandler(AccountState *accountState, QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
@@ -28,7 +41,9 @@ ServerNotificationHandler::ServerNotificationHandler(AccountState *accountState,
|
|||||||
void ServerNotificationHandler::slotFetchNotifications()
|
void ServerNotificationHandler::slotFetchNotifications()
|
||||||
{
|
{
|
||||||
// check connectivity and credentials
|
// check connectivity and credentials
|
||||||
if (!(_accountState && _accountState->isConnected() && _accountState->account() && _accountState->account()->credentials() && _accountState->account()->credentials()->ready())) {
|
if (!(_accountState && _accountState->isConnected() &&
|
||||||
|
_accountState->account() && _accountState->account()->credentials() &&
|
||||||
|
_accountState->account()->credentials()->ready())) {
|
||||||
deleteLater();
|
deleteLater();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -53,18 +68,18 @@ void ServerNotificationHandler::slotFetchNotifications()
|
|||||||
_notificationJob->start();
|
_notificationJob->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerNotificationHandler::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode)
|
void ServerNotificationHandler::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){
|
||||||
{
|
if(statusCode == successStatusCode){
|
||||||
if (statusCode == successStatusCode) {
|
|
||||||
qCWarning(lcServerNotification) << "New Notification ETag Response Header received " << value;
|
qCWarning(lcServerNotification) << "New Notification ETag Response Header received " << value;
|
||||||
AccountState *account = qvariant_cast<AccountState *>(sender()->property(propertyAccountStateC));
|
AccountState *account = qvariant_cast<AccountState *>(sender()->property(propertyAccountStateC));
|
||||||
account->setNotificationsEtagResponseHeader(value);
|
account->setNotificationsEtagResponseHeader(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerNotificationHandler::slotIconDownloaded(QByteArray iconData)
|
void ServerNotificationHandler::slotIconDownloaded(QByteArray iconData){
|
||||||
{
|
QPixmap pixmap;
|
||||||
iconCache.insert(sender()->property("activityId").toInt(),iconData);
|
pixmap.loadFromData(iconData);
|
||||||
|
iconCache.insert(sender()->property("activityId").toInt(), QIcon(pixmap));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &json, int statusCode)
|
void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &json, int statusCode)
|
||||||
@@ -92,7 +107,7 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
|
|||||||
auto json = element.toObject();
|
auto json = element.toObject();
|
||||||
a._type = Activity::NotificationType;
|
a._type = Activity::NotificationType;
|
||||||
a._accName = ai->account()->displayName();
|
a._accName = ai->account()->displayName();
|
||||||
a._id = json.value("activity_id").toInt();
|
a._id = json.value("notification_id").toInt();
|
||||||
|
|
||||||
//need to know, specially for remote_share
|
//need to know, specially for remote_share
|
||||||
a._objectType = json.value("object_type").toString();
|
a._objectType = json.value("object_type").toString();
|
||||||
@@ -100,17 +115,16 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
|
|||||||
|
|
||||||
a._subject = json.value("subject").toString();
|
a._subject = json.value("subject").toString();
|
||||||
a._message = json.value("message").toString();
|
a._message = json.value("message").toString();
|
||||||
a._icon = json.value("icon").toString();
|
|
||||||
|
|
||||||
if (!a._icon.isEmpty()) {
|
if(!json.value("icon").toString().isEmpty()){
|
||||||
IconJob *iconJob = new IconJob(QUrl(a._icon));
|
IconJob *iconJob = new IconJob(QUrl(json.value("icon").toString()));
|
||||||
iconJob->setProperty("activityId", a._id);
|
iconJob->setProperty("activityId", a._id);
|
||||||
connect(iconJob, &IconJob::jobFinished, this, &ServerNotificationHandler::slotIconDownloaded);
|
connect(iconJob, &IconJob::jobFinished, this, &ServerNotificationHandler::slotIconDownloaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl link(json.value("link").toString());
|
QUrl link(json.value("link").toString());
|
||||||
if (!link.isEmpty()) {
|
if (!link.isEmpty()) {
|
||||||
if (link.host().isEmpty()) {
|
if(link.host().isEmpty()){
|
||||||
link.setScheme(ai->account()->url().scheme());
|
link.setScheme(ai->account()->url().scheme());
|
||||||
link.setHost(ai->account()->url().host());
|
link.setHost(ai->account()->url().host());
|
||||||
}
|
}
|
||||||
@@ -137,8 +151,8 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
|
|||||||
// https://github.com/owncloud/notifications/blob/master/docs/ocs-endpoint-v1.md#deleting-a-notification-for-a-user
|
// https://github.com/owncloud/notifications/blob/master/docs/ocs-endpoint-v1.md#deleting-a-notification-for-a-user
|
||||||
ActivityLink al;
|
ActivityLink al;
|
||||||
al._label = tr("Dismiss");
|
al._label = tr("Dismiss");
|
||||||
al._link = Utility::concatUrlPath(ai->account()->url(), notificationsPath + "/" + QString::number(a._id)).toString();
|
al._link = Utility::concatUrlPath(ai->account()->url(), notificationsPath + "/" + QString::number(a._id)).toString();
|
||||||
al._verb = "DELETE";
|
al._verb = "DELETE";
|
||||||
al._isPrimary = false;
|
al._isPrimary = false;
|
||||||
a._links.append(al);
|
a._links.append(al);
|
||||||
|
|
||||||
@@ -148,4 +162,4 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
|
|||||||
|
|
||||||
deleteLater();
|
deleteLater();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
50
src/gui/servernotificationhandler.h
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SERVERNOTIFICATIONHANDLER_H
|
||||||
|
#define SERVERNOTIFICATIONHANDLER_H
|
||||||
|
|
||||||
|
#include <QtCore>
|
||||||
|
|
||||||
|
#include "activitywidget.h"
|
||||||
|
|
||||||
|
class QJsonDocument;
|
||||||
|
|
||||||
|
namespace OCC {
|
||||||
|
|
||||||
|
class ServerNotificationHandler : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ServerNotificationHandler(AccountState *accountState, QObject *parent = nullptr);
|
||||||
|
static QMap<int, QIcon> iconCache;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void newNotificationList(ActivityList);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void slotFetchNotifications();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void slotNotificationsReceived(const QJsonDocument &json, int statusCode);
|
||||||
|
void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode);
|
||||||
|
void slotIconDownloaded(QByteArray iconData);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPointer<JsonApiJob> _notificationJob;
|
||||||
|
AccountState *_accountState;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SERVERNOTIFICATIONHANDLER_H
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "configfile.h"
|
#include "configfile.h"
|
||||||
#include "progressdispatcher.h"
|
#include "progressdispatcher.h"
|
||||||
#include "owncloudgui.h"
|
#include "owncloudgui.h"
|
||||||
|
#include "activitywidget.h"
|
||||||
#include "accountmanager.h"
|
#include "accountmanager.h"
|
||||||
|
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
@@ -188,13 +189,39 @@ void SettingsDialog::showFirstPage()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SettingsDialog::showActivityPage()
|
||||||
|
{
|
||||||
|
if (auto account = qvariant_cast<AccountState*>(sender()->property("account"))) {
|
||||||
|
_activitySettings[account]->show();
|
||||||
|
_ui->stack->setCurrentWidget(_activitySettings[account]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SettingsDialog::showIssuesList(AccountState *account) {
|
void SettingsDialog::showIssuesList(AccountState *account) {
|
||||||
/*for (auto it = _actionGroupWidgets.begin(); it != _actionGroupWidgets.end(); ++it) {
|
for (auto it = _actionGroupWidgets.begin(); it != _actionGroupWidgets.end(); ++it) {
|
||||||
if (it.value() == _activitySettings[account]) {
|
if (it.value() == _activitySettings[account]) {
|
||||||
it.key()->activate(QAction::ActionEvent::Trigger);
|
it.key()->activate(QAction::ActionEvent::Trigger);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsDialog::activityAdded(AccountState *s){
|
||||||
|
_ui->stack->addWidget(_activitySettings[s]);
|
||||||
|
connect(_activitySettings[s], &ActivitySettings::guiLog, _gui,
|
||||||
|
&ownCloudGui::slotShowOptionalTrayMessage);
|
||||||
|
|
||||||
|
ConfigFile cfg;
|
||||||
|
_activitySettings[s]->setNotificationRefreshInterval(cfg.notificationRefreshInterval());
|
||||||
|
|
||||||
|
// Note: all the actions have a '\n' because the account name is in two lines and
|
||||||
|
// all buttons must have the same size in order to keep a good layout
|
||||||
|
QAction *action = createColorAwareAction(QLatin1String(":/client/resources/activity.png"), tr("Activity"));
|
||||||
|
action->setProperty("account", QVariant::fromValue(s));
|
||||||
|
_toolBar->insertAction(_actionBefore, action);
|
||||||
|
_actionGroup->addAction(action);
|
||||||
|
_actionGroupWidgets.insert(action, _activitySettings[s]);
|
||||||
|
connect(action, &QAction::triggered, this, &SettingsDialog::showActivityPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsDialog::accountAdded(AccountState *s)
|
void SettingsDialog::accountAdded(AccountState *s)
|
||||||
@@ -202,6 +229,14 @@ void SettingsDialog::accountAdded(AccountState *s)
|
|||||||
auto height = _toolBar->sizeHint().height();
|
auto height = _toolBar->sizeHint().height();
|
||||||
bool brandingSingleAccount = !Theme::instance()->multiAccount();
|
bool brandingSingleAccount = !Theme::instance()->multiAccount();
|
||||||
|
|
||||||
|
_activitySettings[s] = new ActivitySettings(s, this);
|
||||||
|
|
||||||
|
// if this is not the first account, then before we continue to add more accounts we add a separator
|
||||||
|
if(AccountManager::instance()->accounts().first().data() != s &&
|
||||||
|
AccountManager::instance()->accounts().size() >= 1){
|
||||||
|
_actionGroupWidgets.insert(_toolBar->insertSeparator(_actionBefore), _activitySettings[s]);
|
||||||
|
}
|
||||||
|
|
||||||
QAction *accountAction;
|
QAction *accountAction;
|
||||||
QImage avatar = s->account()->avatar();
|
QImage avatar = s->account()->avatar();
|
||||||
const QString actionText = brandingSingleAccount ? tr("Account") : s->account()->displayName();
|
const QString actionText = brandingSingleAccount ? tr("Account") : s->account()->displayName();
|
||||||
@@ -229,11 +264,19 @@ void SettingsDialog::accountAdded(AccountState *s)
|
|||||||
connect(accountSettings, &AccountSettings::folderChanged, _gui, &ownCloudGui::slotFoldersChanged);
|
connect(accountSettings, &AccountSettings::folderChanged, _gui, &ownCloudGui::slotFoldersChanged);
|
||||||
connect(accountSettings, &AccountSettings::openFolderAlias,
|
connect(accountSettings, &AccountSettings::openFolderAlias,
|
||||||
_gui, &ownCloudGui::slotFolderOpenAction);
|
_gui, &ownCloudGui::slotFolderOpenAction);
|
||||||
|
connect(accountSettings, &AccountSettings::showIssuesList, this, &SettingsDialog::showIssuesList);
|
||||||
connect(s->account().data(), &Account::accountChangedAvatar, this, &SettingsDialog::slotAccountAvatarChanged);
|
connect(s->account().data(), &Account::accountChangedAvatar, this, &SettingsDialog::slotAccountAvatarChanged);
|
||||||
connect(s->account().data(), &Account::accountChangedDisplayName, this, &SettingsDialog::slotAccountDisplayNameChanged);
|
connect(s->account().data(), &Account::accountChangedDisplayName, this, &SettingsDialog::slotAccountDisplayNameChanged);
|
||||||
|
|
||||||
|
// Refresh immediatly when getting online
|
||||||
|
connect(s, &AccountState::isConnectedChanged, this, &SettingsDialog::slotRefreshActivityAccountStateSender);
|
||||||
|
|
||||||
// Connect styleChanged event, to adapt (Dark-/Light-Mode switching)
|
// Connect styleChanged event, to adapt (Dark-/Light-Mode switching)
|
||||||
connect(this, &SettingsDialog::styleChanged, accountSettings, &AccountSettings::slotStyleChanged);
|
connect(this, &SettingsDialog::styleChanged, accountSettings, &AccountSettings::slotStyleChanged);
|
||||||
|
connect(this, &SettingsDialog::styleChanged, _activitySettings[s], &ActivitySettings::slotStyleChanged);
|
||||||
|
|
||||||
|
activityAdded(s);
|
||||||
|
slotRefreshActivity(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsDialog::slotAccountAvatarChanged()
|
void SettingsDialog::slotAccountAvatarChanged()
|
||||||
@@ -289,6 +332,19 @@ void SettingsDialog::accountRemoved(AccountState *s)
|
|||||||
_actionForAccount.remove(s->account().data());
|
_actionForAccount.remove(s->account().data());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(_activitySettings.contains(s)){
|
||||||
|
_activitySettings[s]->slotRemoveAccount();
|
||||||
|
_activitySettings[s]->hide();
|
||||||
|
|
||||||
|
// get the settings widget and the separator
|
||||||
|
foreach(QAction *action, _actionGroupWidgets.keys(_activitySettings[s])){
|
||||||
|
_actionGroupWidgets.remove(action);
|
||||||
|
_toolBar->removeAction(action);
|
||||||
|
}
|
||||||
|
_toolBar->widgetForAction(_actionBefore)->hide();
|
||||||
|
_activitySettings.remove(s);
|
||||||
|
}
|
||||||
|
|
||||||
// Hide when the last account is deleted. We want to enter the same
|
// Hide when the last account is deleted. We want to enter the same
|
||||||
// state we'd be in the client was started up without an account
|
// state we'd be in the client was started up without an account
|
||||||
// configured.
|
// configured.
|
||||||
@@ -358,4 +414,15 @@ QAction *SettingsDialog::createColorAwareAction(const QString &iconPath, const Q
|
|||||||
return createActionWithIcon(coloredIcon, text, iconPath);
|
return createActionWithIcon(coloredIcon, text, iconPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SettingsDialog::slotRefreshActivityAccountStateSender()
|
||||||
|
{
|
||||||
|
slotRefreshActivity(qobject_cast<AccountState*>(sender()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SettingsDialog::slotRefreshActivity(AccountState *accountState)
|
||||||
|
{
|
||||||
|
if (accountState->isConnected())
|
||||||
|
_activitySettings[accountState]->slotRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class AccountSettings;
|
|||||||
class Application;
|
class Application;
|
||||||
class FolderMan;
|
class FolderMan;
|
||||||
class ownCloudGui;
|
class ownCloudGui;
|
||||||
|
class ActivitySettings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The SettingsDialog class
|
* @brief The SettingsDialog class
|
||||||
@@ -54,8 +55,11 @@ public:
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void showFirstPage();
|
void showFirstPage();
|
||||||
|
void showActivityPage();
|
||||||
void showIssuesList(AccountState *account);
|
void showIssuesList(AccountState *account);
|
||||||
void slotSwitchPage(QAction *action);
|
void slotSwitchPage(QAction *action);
|
||||||
|
void slotRefreshActivity(AccountState *accountState);
|
||||||
|
void slotRefreshActivityAccountStateSender();
|
||||||
void slotAccountAvatarChanged();
|
void slotAccountAvatarChanged();
|
||||||
void slotAccountDisplayNameChanged();
|
void slotAccountDisplayNameChanged();
|
||||||
|
|
||||||
@@ -74,6 +78,7 @@ private slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void customizeStyle();
|
void customizeStyle();
|
||||||
|
void activityAdded(AccountState *);
|
||||||
|
|
||||||
QAction *createColorAwareAction(const QString &iconName, const QString &fileName);
|
QAction *createColorAwareAction(const QString &iconName, const QString &fileName);
|
||||||
QAction *createActionWithIcon(const QIcon &icon, const QString &text, const QString &iconPath = QString());
|
QAction *createActionWithIcon(const QIcon &icon, const QString &text, const QString &iconPath = QString());
|
||||||
@@ -90,6 +95,7 @@ private:
|
|||||||
QHash<Account *, QAction *> _actionForAccount;
|
QHash<Account *, QAction *> _actionForAccount;
|
||||||
|
|
||||||
QToolBar *_toolBar;
|
QToolBar *_toolBar;
|
||||||
|
QMap<AccountState *, ActivitySettings *> _activitySettings;
|
||||||
|
|
||||||
ownCloudGui *_gui;
|
ownCloudGui *_gui;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>516</width>
|
<width>693</width>
|
||||||
<height>457</height>
|
<height>457</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QFileIconProvider>
|
#include <QFileIconProvider>
|
||||||
#include <QInputDialog>
|
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QFrame>
|
#include <QFrame>
|
||||||
@@ -138,7 +137,6 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
|
|||||||
_manager = new ShareManager(accountState->account(), this);
|
_manager = new ShareManager(accountState->account(), this);
|
||||||
connect(_manager, &ShareManager::sharesFetched, this, &ShareDialog::slotSharesFetched);
|
connect(_manager, &ShareManager::sharesFetched, this, &ShareDialog::slotSharesFetched);
|
||||||
connect(_manager, &ShareManager::linkShareCreated, this, &ShareDialog::slotAddLinkShareWidget);
|
connect(_manager, &ShareManager::linkShareCreated, this, &ShareDialog::slotAddLinkShareWidget);
|
||||||
connect(_manager, &ShareManager::linkShareRequiresPassword, this, &ShareDialog::slotLinkShareRequiresPassword);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +168,7 @@ void ShareDialog::initLinkShareWidget(){
|
|||||||
_emptyShareLinkWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, this);
|
_emptyShareLinkWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, this);
|
||||||
_linkWidgetList.append(_emptyShareLinkWidget);
|
_linkWidgetList.append(_emptyShareLinkWidget);
|
||||||
|
|
||||||
connect(_emptyShareLinkWidget, &ShareLinkWidget::resizeRequested, this, &ShareDialog::slotAdjustScrollWidgetSize);
|
// connect(_emptyShareLinkWidget, &ShareLinkWidget::resizeRequested, this, &ShareDialog::slotAdjustScrollWidgetSize);
|
||||||
// connect(this, &ShareDialog::toggleAnimation, _emptyShareLinkWidget, &ShareLinkWidget::slotToggleAnimation);
|
// connect(this, &ShareDialog::toggleAnimation, _emptyShareLinkWidget, &ShareLinkWidget::slotToggleAnimation);
|
||||||
connect(_emptyShareLinkWidget, &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare);
|
connect(_emptyShareLinkWidget, &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare);
|
||||||
|
|
||||||
@@ -211,6 +209,7 @@ void ShareDialog::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
|
|||||||
emit toggleAnimation(false);
|
emit toggleAnimation(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
void ShareDialog::slotAdjustScrollWidgetSize()
|
void ShareDialog::slotAdjustScrollWidgetSize()
|
||||||
{
|
{
|
||||||
int count = this->findChildren<ShareLinkWidget *>().count();
|
int count = this->findChildren<ShareLinkWidget *>().count();
|
||||||
@@ -304,24 +303,6 @@ void ShareDialog::slotCreateLinkShare()
|
|||||||
_manager->createLinkShare(_sharePath, QString(), QString());
|
_manager->createLinkShare(_sharePath, QString(), QString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareDialog::slotLinkShareRequiresPassword()
|
|
||||||
{
|
|
||||||
bool ok;
|
|
||||||
QString password = QInputDialog::getText(this,
|
|
||||||
tr("Password for share required"),
|
|
||||||
tr("Please enter a password for your link share:"),
|
|
||||||
QLineEdit::Normal,
|
|
||||||
QString(),
|
|
||||||
&ok);
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
// The dialog was canceled so no need to do anything
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to create the link share again with the newly entered password
|
|
||||||
_manager->createLinkShare(_sharePath, QString(), password);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShareDialog::slotDeleteShare()
|
void ShareDialog::slotDeleteShare()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ private slots:
|
|||||||
void slotAddLinkShareWidget(const QSharedPointer<LinkShare> &linkShare);
|
void slotAddLinkShareWidget(const QSharedPointer<LinkShare> &linkShare);
|
||||||
void slotDeleteShare();
|
void slotDeleteShare();
|
||||||
void slotCreateLinkShare();
|
void slotCreateLinkShare();
|
||||||
void slotLinkShareRequiresPassword();
|
|
||||||
void slotAdjustScrollWidgetSize();
|
void slotAdjustScrollWidgetSize();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|||||||
@@ -6,151 +6,159 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>385</width>
|
<width>372</width>
|
||||||
<height>150</height>
|
<height>150</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="shareDialogVerticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
<property name="sizeConstraint">
|
<property name="sizeConstraint">
|
||||||
<enum>QLayout::SetFixedSize</enum>
|
<enum>QLayout::SetFixedSize</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0" columnstretch="0,0">
|
||||||
<property name="sizeConstraint">
|
<property name="leftMargin">
|
||||||
<enum>QLayout::SetFixedSize</enum>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<property name="topMargin">
|
||||||
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0" columnstretch="0,0">
|
<number>0</number>
|
||||||
<property name="leftMargin">
|
</property>
|
||||||
<number>0</number>
|
<property name="rightMargin">
|
||||||
</property>
|
<number>0</number>
|
||||||
<property name="topMargin">
|
</property>
|
||||||
<number>0</number>
|
<property name="spacing">
|
||||||
</property>
|
<number>10</number>
|
||||||
<property name="rightMargin">
|
</property>
|
||||||
<number>0</number>
|
<item row="1" column="1">
|
||||||
</property>
|
<widget class="QLabel" name="label_sharePath">
|
||||||
<property name="spacing">
|
|
||||||
<number>10</number>
|
|
||||||
</property>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QLabel" name="label_name">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>315</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>share label</string>
|
|
||||||
</property>
|
|
||||||
<property name="textFormat">
|
|
||||||
<enum>Qt::PlainText</enum>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QLabel" name="label_sharePath">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>315</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<weight>50</weight>
|
|
||||||
<bold>false</bold>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Nextcloud Path:</string>
|
|
||||||
</property>
|
|
||||||
<property name="textFormat">
|
|
||||||
<enum>Qt::PlainText</enum>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0" rowspan="2">
|
|
||||||
<widget class="QLabel" name="label_icon">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>40</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>16777215</width>
|
|
||||||
<height>16777215</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Icon</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QScrollArea" name="scrollArea">
|
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="frameShape">
|
<property name="minimumSize">
|
||||||
<enum>QFrame::NoFrame</enum>
|
<size>
|
||||||
|
<width>315</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="horizontalScrollBarPolicy">
|
<property name="font">
|
||||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeAdjustPolicy">
|
<property name="text">
|
||||||
<enum>QAbstractScrollArea::AdjustIgnored</enum>
|
<string>ownCloud Path:</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="widgetResizable">
|
<property name="textFormat">
|
||||||
|
<enum>Qt::PlainText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
</widget>
|
||||||
<property name="geometry">
|
</item>
|
||||||
<rect>
|
<item row="0" column="1">
|
||||||
<x>0</x>
|
<widget class="QLabel" name="label_name">
|
||||||
<y>0</y>
|
<property name="sizePolicy">
|
||||||
<width>69</width>
|
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
|
||||||
<height>69</height>
|
<horstretch>0</horstretch>
|
||||||
</rect>
|
<verstretch>0</verstretch>
|
||||||
</property>
|
</sizepolicy>
|
||||||
<layout class="QVBoxLayout" name="scrollAreaVerticalLayout"/>
|
</property>
|
||||||
</widget>
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>315</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">share label</string>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::PlainText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" rowspan="2">
|
||||||
|
<widget class="QLabel" name="label_icon">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">Icon</string>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QScrollArea" name="scrollArea">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::NoFrame</enum>
|
||||||
|
</property>
|
||||||
|
<property name="horizontalScrollBarPolicy">
|
||||||
|
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
|
||||||
|
</property>
|
||||||
|
<property name="widgetResizable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>367</width>
|
||||||
|
<height>85</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="scrollAreaVerticalLayout"/>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QTextEdit>
|
|
||||||
#include <QToolButton>
|
#include <QToolButton>
|
||||||
#include <QPropertyAnimation>
|
#include <QPropertyAnimation>
|
||||||
|
|
||||||
@@ -48,7 +47,6 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
|||||||
, _localPath(localPath)
|
, _localPath(localPath)
|
||||||
, _linkShare(nullptr)
|
, _linkShare(nullptr)
|
||||||
, _passwordRequired(false)
|
, _passwordRequired(false)
|
||||||
, _noteRequired(false)
|
|
||||||
, _expiryRequired(false)
|
, _expiryRequired(false)
|
||||||
, _namesSupported(true)
|
, _namesSupported(true)
|
||||||
, _linkContextMenu(nullptr)
|
, _linkContextMenu(nullptr)
|
||||||
@@ -57,13 +55,14 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
|||||||
, _allowUploadEditingLinkAction(nullptr)
|
, _allowUploadEditingLinkAction(nullptr)
|
||||||
, _allowUploadLinkAction(nullptr)
|
, _allowUploadLinkAction(nullptr)
|
||||||
, _passwordProtectLinkAction(nullptr)
|
, _passwordProtectLinkAction(nullptr)
|
||||||
, _noteLinkAction(nullptr)
|
|
||||||
, _expirationDateLinkAction(nullptr)
|
, _expirationDateLinkAction(nullptr)
|
||||||
, _unshareLinkAction(nullptr)
|
, _unshareLinkAction(nullptr)
|
||||||
{
|
{
|
||||||
_ui->setupUi(this);
|
_ui->setupUi(this);
|
||||||
|
|
||||||
QSizePolicy sp = _ui->shareLinkToolButton->sizePolicy();
|
QSizePolicy sp = _ui->shareLinkToolButton->sizePolicy();
|
||||||
|
sp.setRetainSizeWhenHidden(true);
|
||||||
|
_ui->shareLinkToolButton->setSizePolicy(sp);
|
||||||
_ui->shareLinkToolButton->hide();
|
_ui->shareLinkToolButton->hide();
|
||||||
|
|
||||||
//Is this a file or folder?
|
//Is this a file or folder?
|
||||||
@@ -73,8 +72,6 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
|||||||
connect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCreateShareLink);
|
connect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCreateShareLink);
|
||||||
connect(_ui->lineEdit_password, &QLineEdit::returnPressed, this, &ShareLinkWidget::slotCreatePassword);
|
connect(_ui->lineEdit_password, &QLineEdit::returnPressed, this, &ShareLinkWidget::slotCreatePassword);
|
||||||
connect(_ui->confirmPassword, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCreatePassword);
|
connect(_ui->confirmPassword, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCreatePassword);
|
||||||
connect(_ui->textEdit_note, &QTextEdit::textChanged, this, &ShareLinkWidget::slotCreateNote);
|
|
||||||
connect(_ui->confirmNote, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCreateNote);
|
|
||||||
connect(_ui->confirmExpirationDate, &QAbstractButton::clicked, this, &ShareLinkWidget::slotSetExpireDate);
|
connect(_ui->confirmExpirationDate, &QAbstractButton::clicked, this, &ShareLinkWidget::slotSetExpireDate);
|
||||||
connect(_ui->calendar, &QDateTimeEdit::dateChanged, this, &ShareLinkWidget::slotSetExpireDate);
|
connect(_ui->calendar, &QDateTimeEdit::dateChanged, this, &ShareLinkWidget::slotSetExpireDate);
|
||||||
|
|
||||||
@@ -100,7 +97,6 @@ ShareLinkWidget::ShareLinkWidget(AccountPtr account,
|
|||||||
|
|
||||||
togglePasswordOptions(false);
|
togglePasswordOptions(false);
|
||||||
toggleExpireDateOptions(false);
|
toggleExpireDateOptions(false);
|
||||||
toggleNoteOptions(false);
|
|
||||||
_ui->calendar->setMinimumDate(QDate::currentDate().addDays(1));
|
_ui->calendar->setMinimumDate(QDate::currentDate().addDays(1));
|
||||||
|
|
||||||
// check if the file is already inside of a synced folder
|
// check if the file is already inside of a synced folder
|
||||||
@@ -115,8 +111,7 @@ ShareLinkWidget::~ShareLinkWidget()
|
|||||||
delete _ui;
|
delete _ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareLinkWidget::slotToggleAnimation(bool start)
|
void ShareLinkWidget::slotToggleAnimation(bool start){
|
||||||
{
|
|
||||||
if (start) {
|
if (start) {
|
||||||
if (!_ui->progressIndicator->isAnimated())
|
if (!_ui->progressIndicator->isAnimated())
|
||||||
_ui->progressIndicator->startAnimation();
|
_ui->progressIndicator->startAnimation();
|
||||||
@@ -125,25 +120,21 @@ void ShareLinkWidget::slotToggleAnimation(bool start)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareLinkWidget::setLinkShare(QSharedPointer<LinkShare> linkShare)
|
void ShareLinkWidget::setLinkShare(QSharedPointer<LinkShare> linkShare){
|
||||||
{
|
|
||||||
_linkShare = linkShare;
|
_linkShare = linkShare;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<LinkShare> ShareLinkWidget::getLinkShare()
|
QSharedPointer<LinkShare> ShareLinkWidget::getLinkShare(){
|
||||||
{
|
|
||||||
return _linkShare;
|
return _linkShare;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareLinkWidget::setupUiOptions()
|
void ShareLinkWidget::setupUiOptions(){
|
||||||
{
|
|
||||||
connect(_linkShare.data(), &LinkShare::expireDateSet, this, &ShareLinkWidget::slotExpireDateSet);
|
connect(_linkShare.data(), &LinkShare::expireDateSet, this, &ShareLinkWidget::slotExpireDateSet);
|
||||||
connect(_linkShare.data(), &LinkShare::noteSet, this, &ShareLinkWidget::slotNoteSet);
|
|
||||||
connect(_linkShare.data(), &LinkShare::passwordSet, this, &ShareLinkWidget::slotPasswordSet);
|
connect(_linkShare.data(), &LinkShare::passwordSet, this, &ShareLinkWidget::slotPasswordSet);
|
||||||
connect(_linkShare.data(), &LinkShare::passwordSetError, this, &ShareLinkWidget::slotPasswordSetError);
|
connect(_linkShare.data(), &LinkShare::passwordSetError, this, &ShareLinkWidget::slotPasswordSetError);
|
||||||
|
|
||||||
// Prepare permissions check and create group action
|
// Prepare permissions check and create group action
|
||||||
const QDate expireDate = _linkShare.data()->getExpireDate().isValid() ? _linkShare.data()->getExpireDate() : QDate();
|
const QDate expireDate = _linkShare.data()->getExpireDate().isValid()? _linkShare.data()->getExpireDate() : QDate();
|
||||||
const SharePermissions perm = _linkShare.data()->getPermissions();
|
const SharePermissions perm = _linkShare.data()->getPermissions();
|
||||||
bool checked = false;
|
bool checked = false;
|
||||||
QActionGroup *permissionsGroup = new QActionGroup(this);
|
QActionGroup *permissionsGroup = new QActionGroup(this);
|
||||||
@@ -154,7 +145,7 @@ void ShareLinkWidget::setupUiOptions()
|
|||||||
// radio button style
|
// radio button style
|
||||||
permissionsGroup->setExclusive(true);
|
permissionsGroup->setExclusive(true);
|
||||||
|
|
||||||
if (_isFile) {
|
if(_isFile){
|
||||||
checked = perm & (SharePermissionRead & SharePermissionUpdate);
|
checked = perm & (SharePermissionRead & SharePermissionUpdate);
|
||||||
_allowEditingLinkAction = _linkContextMenu->addAction(tr("Allow Editing"));
|
_allowEditingLinkAction = _linkContextMenu->addAction(tr("Allow Editing"));
|
||||||
_allowEditingLinkAction->setCheckable(true);
|
_allowEditingLinkAction->setCheckable(true);
|
||||||
@@ -166,7 +157,10 @@ void ShareLinkWidget::setupUiOptions()
|
|||||||
_readOnlyLinkAction->setCheckable(true);
|
_readOnlyLinkAction->setCheckable(true);
|
||||||
_readOnlyLinkAction->setChecked(checked);
|
_readOnlyLinkAction->setChecked(checked);
|
||||||
|
|
||||||
checked = perm & (SharePermissionRead & SharePermissionCreate & SharePermissionUpdate & SharePermissionDelete);
|
checked = perm & (SharePermissionRead &
|
||||||
|
SharePermissionCreate &
|
||||||
|
SharePermissionUpdate &
|
||||||
|
SharePermissionDelete);
|
||||||
_allowUploadEditingLinkAction = permissionsGroup->addAction(tr("Allow Upload && Editing"));
|
_allowUploadEditingLinkAction = permissionsGroup->addAction(tr("Allow Upload && Editing"));
|
||||||
_allowUploadEditingLinkAction->setCheckable(true);
|
_allowUploadEditingLinkAction->setCheckable(true);
|
||||||
_allowUploadEditingLinkAction->setChecked(checked);
|
_allowUploadEditingLinkAction->setChecked(checked);
|
||||||
@@ -178,7 +172,7 @@ void ShareLinkWidget::setupUiOptions()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adds permissions actions (radio button style)
|
// Adds permissions actions (radio button style)
|
||||||
if (_isFile) {
|
if(_isFile){
|
||||||
_linkContextMenu->addAction(_allowEditingLinkAction);
|
_linkContextMenu->addAction(_allowEditingLinkAction);
|
||||||
} else {
|
} else {
|
||||||
_linkContextMenu->addAction(_readOnlyLinkAction);
|
_linkContextMenu->addAction(_readOnlyLinkAction);
|
||||||
@@ -186,21 +180,11 @@ void ShareLinkWidget::setupUiOptions()
|
|||||||
_linkContextMenu->addAction(_allowUploadLinkAction);
|
_linkContextMenu->addAction(_allowUploadLinkAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds action to display note widget (check box)
|
|
||||||
_noteLinkAction = _linkContextMenu->addAction(tr("Add note to recipient"));
|
|
||||||
_noteLinkAction->setCheckable(true);
|
|
||||||
|
|
||||||
if (_linkShare->getNote().isSimpleText()) {
|
|
||||||
_ui->textEdit_note->setText(_linkShare->getNote());
|
|
||||||
_noteLinkAction->setChecked(true);
|
|
||||||
showNoteOptions(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds action to display password widget (check box)
|
// Adds action to display password widget (check box)
|
||||||
_passwordProtectLinkAction = _linkContextMenu->addAction(tr("Password Protect"));
|
_passwordProtectLinkAction = _linkContextMenu->addAction(tr("Password Protect"));
|
||||||
_passwordProtectLinkAction->setCheckable(true);
|
_passwordProtectLinkAction->setCheckable(true);
|
||||||
|
|
||||||
if (_linkShare.data()->isPasswordSet()) {
|
if(_linkShare.data()->isPasswordSet()){
|
||||||
_passwordProtectLinkAction->setChecked(true);
|
_passwordProtectLinkAction->setChecked(true);
|
||||||
_ui->lineEdit_password->setPlaceholderText("********");
|
_ui->lineEdit_password->setPlaceholderText("********");
|
||||||
showPasswordOptions(true);
|
showPasswordOptions(true);
|
||||||
@@ -216,7 +200,7 @@ void ShareLinkWidget::setupUiOptions()
|
|||||||
// Adds action to display expiration date widget (check box)
|
// Adds action to display expiration date widget (check box)
|
||||||
_expirationDateLinkAction = _linkContextMenu->addAction(tr("Expiration Date"));
|
_expirationDateLinkAction = _linkContextMenu->addAction(tr("Expiration Date"));
|
||||||
_expirationDateLinkAction->setCheckable(true);
|
_expirationDateLinkAction->setCheckable(true);
|
||||||
if (!expireDate.isNull()) {
|
if(!expireDate.isNull()){
|
||||||
_ui->calendar->setDate(expireDate);
|
_ui->calendar->setDate(expireDate);
|
||||||
_expirationDateLinkAction->setChecked(true);
|
_expirationDateLinkAction->setChecked(true);
|
||||||
showExpireDateOptions(true);
|
showExpireDateOptions(true);
|
||||||
@@ -233,12 +217,12 @@ void ShareLinkWidget::setupUiOptions()
|
|||||||
|
|
||||||
// Adds action to unshare widget (check box)
|
// Adds action to unshare widget (check box)
|
||||||
_unshareLinkAction = _linkContextMenu->addAction(QIcon(":/client/resources/delete.png"),
|
_unshareLinkAction = _linkContextMenu->addAction(QIcon(":/client/resources/delete.png"),
|
||||||
tr("Unshare"));
|
tr("Unshare"));
|
||||||
|
|
||||||
_linkContextMenu->addSeparator();
|
_linkContextMenu->addSeparator();
|
||||||
|
|
||||||
_addAnotherLinkAction = _linkContextMenu->addAction(QIcon(":/client/resources/add.png"),
|
_addAnotherLinkAction = _linkContextMenu->addAction(QIcon(":/client/resources/add.png"),
|
||||||
tr("Add another link"));
|
tr("Add another link"));
|
||||||
|
|
||||||
_ui->enableShareLink->setIcon(QIcon(":/client/resources/copy.svg"));
|
_ui->enableShareLink->setIcon(QIcon(":/client/resources/copy.svg"));
|
||||||
disconnect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCreateShareLink);
|
disconnect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCreateShareLink);
|
||||||
@@ -261,27 +245,7 @@ void ShareLinkWidget::setupUiOptions()
|
|||||||
customizeStyle();
|
customizeStyle();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareLinkWidget::setNote(const QString ¬e)
|
void ShareLinkWidget::slotCopyLinkShare(bool clicked){
|
||||||
{
|
|
||||||
if (_linkShare) {
|
|
||||||
slotToggleAnimation(true);
|
|
||||||
_ui->errorLabel->hide();
|
|
||||||
_linkShare->setNote(note);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShareLinkWidget::slotCreateNote()
|
|
||||||
{
|
|
||||||
setNote(_ui->textEdit_note->toPlainText());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShareLinkWidget::slotNoteSet()
|
|
||||||
{
|
|
||||||
slotToggleAnimation(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShareLinkWidget::slotCopyLinkShare(bool clicked)
|
|
||||||
{
|
|
||||||
Q_UNUSED(clicked);
|
Q_UNUSED(clicked);
|
||||||
|
|
||||||
QApplication::clipboard()->setText(_linkShare->getLink().toString());
|
QApplication::clipboard()->setText(_linkShare->getLink().toString());
|
||||||
@@ -294,7 +258,7 @@ void ShareLinkWidget::slotExpireDateSet()
|
|||||||
|
|
||||||
void ShareLinkWidget::slotSetExpireDate()
|
void ShareLinkWidget::slotSetExpireDate()
|
||||||
{
|
{
|
||||||
if (!_linkShare) {
|
if(!_linkShare){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,8 +298,8 @@ void ShareLinkWidget::slotPasswordSet()
|
|||||||
slotToggleAnimation(false);
|
slotToggleAnimation(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareLinkWidget::startAnimation(const int start, const int end)
|
void ShareLinkWidget::startAnimation(const int start, const int end){
|
||||||
{
|
|
||||||
QPropertyAnimation *animation = new QPropertyAnimation(this, "maximumHeight", this);
|
QPropertyAnimation *animation = new QPropertyAnimation(this, "maximumHeight", this);
|
||||||
|
|
||||||
animation->setDuration(500);
|
animation->setDuration(500);
|
||||||
@@ -343,7 +307,7 @@ void ShareLinkWidget::startAnimation(const int start, const int end)
|
|||||||
animation->setEndValue(end);
|
animation->setEndValue(end);
|
||||||
|
|
||||||
connect(animation, &QAbstractAnimation::finished, this, &ShareLinkWidget::slotAnimationFinished);
|
connect(animation, &QAbstractAnimation::finished, this, &ShareLinkWidget::slotAnimationFinished);
|
||||||
if (end < start) // that is to remove the widget, not to show it
|
if(end < start) // that is to remove the widget, not to show it
|
||||||
connect(animation, &QAbstractAnimation::finished, this, &ShareLinkWidget::slotDeleteAnimationFinished);
|
connect(animation, &QAbstractAnimation::finished, this, &ShareLinkWidget::slotDeleteAnimationFinished);
|
||||||
connect(animation, &QVariantAnimation::valueChanged, this, &ShareLinkWidget::resizeRequested);
|
connect(animation, &QVariantAnimation::valueChanged, this, &ShareLinkWidget::resizeRequested);
|
||||||
|
|
||||||
@@ -359,32 +323,10 @@ void ShareLinkWidget::slotDeleteShareFetched()
|
|||||||
|
|
||||||
_linkShare.clear();
|
_linkShare.clear();
|
||||||
togglePasswordOptions(false);
|
togglePasswordOptions(false);
|
||||||
toggleNoteOptions(false);
|
|
||||||
toggleExpireDateOptions(false);
|
toggleExpireDateOptions(false);
|
||||||
emit deleteLinkShare();
|
emit deleteLinkShare();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShareLinkWidget::showNoteOptions(bool show)
|
|
||||||
{
|
|
||||||
_ui->noteLabel->setVisible(show);
|
|
||||||
_ui->textEdit_note->setVisible(show);
|
|
||||||
_ui->confirmNote->setVisible(show);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ShareLinkWidget::toggleNoteOptions(bool enable)
|
|
||||||
{
|
|
||||||
showNoteOptions(enable);
|
|
||||||
|
|
||||||
if (enable) {
|
|
||||||
_ui->textEdit_note->setFocus();
|
|
||||||
} else {
|
|
||||||
// 'deletes' note
|
|
||||||
if (_linkShare)
|
|
||||||
_linkShare->setNote(QString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShareLinkWidget::slotAnimationFinished()
|
void ShareLinkWidget::slotAnimationFinished()
|
||||||
{
|
{
|
||||||
emit resizeRequested();
|
emit resizeRequested();
|
||||||
@@ -425,11 +367,11 @@ void ShareLinkWidget::togglePasswordOptions(bool enable)
|
|||||||
{
|
{
|
||||||
showPasswordOptions(enable);
|
showPasswordOptions(enable);
|
||||||
|
|
||||||
if (enable) {
|
if(enable) {
|
||||||
_ui->lineEdit_password->setFocus();
|
_ui->lineEdit_password->setFocus();
|
||||||
} else {
|
} else {
|
||||||
// 'deletes' password
|
// 'deletes' password
|
||||||
if (_linkShare)
|
if(_linkShare)
|
||||||
_linkShare->setPassword(QString());
|
_linkShare->setPassword(QString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -452,7 +394,7 @@ void ShareLinkWidget::toggleExpireDateOptions(bool enable)
|
|||||||
_ui->calendar->setFocus();
|
_ui->calendar->setFocus();
|
||||||
} else {
|
} else {
|
||||||
// 'deletes' expire date
|
// 'deletes' expire date
|
||||||
if (_linkShare)
|
if(_linkShare)
|
||||||
_linkShare->setExpireDate(QDate());
|
_linkShare->setExpireDate(QDate());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -473,11 +415,11 @@ void ShareLinkWidget::confirmAndDeleteShare()
|
|||||||
|
|
||||||
connect(messageBox, &QMessageBox::finished, this,
|
connect(messageBox, &QMessageBox::finished, this,
|
||||||
[messageBox, yesButton, this]() {
|
[messageBox, yesButton, this]() {
|
||||||
if (messageBox->clickedButton() == yesButton) {
|
if (messageBox->clickedButton() == yesButton) {
|
||||||
this->slotToggleAnimation(true);
|
this->slotToggleAnimation(true);
|
||||||
this->_linkShare->deleteShare();
|
this->_linkShare->deleteShare();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
messageBox->open();
|
messageBox->open();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,10 +440,11 @@ void ShareLinkWidget::slotContextMenuButtonClicked()
|
|||||||
|
|
||||||
void ShareLinkWidget::slotLinkContextMenuActionTriggered(QAction *action)
|
void ShareLinkWidget::slotLinkContextMenuActionTriggered(QAction *action)
|
||||||
{
|
{
|
||||||
|
|
||||||
bool state = action->isChecked();
|
bool state = action->isChecked();
|
||||||
SharePermissions perm = SharePermissionRead;
|
SharePermissions perm = SharePermissionRead;
|
||||||
|
|
||||||
if (action == _addAnotherLinkAction) {
|
if(action == _addAnotherLinkAction){
|
||||||
emit createLinkShare();
|
emit createLinkShare();
|
||||||
|
|
||||||
} else if (action == _readOnlyLinkAction && state) {
|
} else if (action == _readOnlyLinkAction && state) {
|
||||||
@@ -525,9 +468,6 @@ void ShareLinkWidget::slotLinkContextMenuActionTriggered(QAction *action)
|
|||||||
} else if (action == _expirationDateLinkAction) {
|
} else if (action == _expirationDateLinkAction) {
|
||||||
toggleExpireDateOptions(state);
|
toggleExpireDateOptions(state);
|
||||||
|
|
||||||
} else if (action == _noteLinkAction) {
|
|
||||||
toggleNoteOptions(state);
|
|
||||||
|
|
||||||
} else if (action == _unshareLinkAction) {
|
} else if (action == _unshareLinkAction) {
|
||||||
confirmAndDeleteShare();
|
confirmAndDeleteShare();
|
||||||
}
|
}
|
||||||
@@ -565,12 +505,15 @@ void ShareLinkWidget::customizeStyle()
|
|||||||
_addAnotherLinkAction->setIcon(Theme::createColorAwareIcon(":/client/resources/add.png"));
|
_addAnotherLinkAction->setIcon(Theme::createColorAwareIcon(":/client/resources/add.png"));
|
||||||
|
|
||||||
_ui->enableShareLink->setIcon(Theme::createColorAwareIcon(":/client/resources/copy.svg"));
|
_ui->enableShareLink->setIcon(Theme::createColorAwareIcon(":/client/resources/copy.svg"));
|
||||||
|
|
||||||
_ui->shareLinkIconLabel->setPixmap(Theme::createColorAwarePixmap(":/client/resources/public.svg"));
|
// only on master, not in stable-2.6 yet
|
||||||
|
// _ui->shareLinkIconLabel->setPixmap(Theme::createColorAwarePixmap(":/client/resources/public.svg"));
|
||||||
|
_ui->createShareButton->setIcon(Theme::createColorAwareIcon(":/client/resources/public.svg"));
|
||||||
|
|
||||||
_ui->shareLinkToolButton->setIcon(Theme::createColorAwareIcon(":/client/resources/more.svg"));
|
_ui->shareLinkToolButton->setIcon(Theme::createColorAwareIcon(":/client/resources/more.svg"));
|
||||||
|
|
||||||
_ui->confirmNote->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.svg"));
|
// only on master, not in stable-2.6 yet
|
||||||
|
// _ui->confirmNote->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.svg"));
|
||||||
_ui->confirmPassword->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.svg"));
|
_ui->confirmPassword->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.svg"));
|
||||||
_ui->confirmExpirationDate->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.svg"));
|
_ui->confirmExpirationDate->setIcon(Theme::createColorAwareIcon(":/client/resources/confirm.svg"));
|
||||||
|
|
||||||
|
|||||||
@@ -74,9 +74,6 @@ private slots:
|
|||||||
void slotPasswordSet();
|
void slotPasswordSet();
|
||||||
void slotPasswordSetError(int code, const QString &message);
|
void slotPasswordSetError(int code, const QString &message);
|
||||||
|
|
||||||
void slotCreateNote();
|
|
||||||
void slotNoteSet();
|
|
||||||
|
|
||||||
void slotSetExpireDate();
|
void slotSetExpireDate();
|
||||||
void slotExpireDateSet();
|
void slotExpireDateSet();
|
||||||
|
|
||||||
@@ -98,10 +95,6 @@ private:
|
|||||||
void showPasswordOptions(bool show);
|
void showPasswordOptions(bool show);
|
||||||
void togglePasswordOptions(bool enable);
|
void togglePasswordOptions(bool enable);
|
||||||
|
|
||||||
void showNoteOptions(bool show);
|
|
||||||
void toggleNoteOptions(bool enable);
|
|
||||||
void setNote(const QString ¬e);
|
|
||||||
|
|
||||||
void showExpireDateOptions(bool show);
|
void showExpireDateOptions(bool show);
|
||||||
void toggleExpireDateOptions(bool enable);
|
void toggleExpireDateOptions(bool enable);
|
||||||
|
|
||||||
@@ -129,7 +122,6 @@ private:
|
|||||||
bool _passwordRequired;
|
bool _passwordRequired;
|
||||||
bool _expiryRequired;
|
bool _expiryRequired;
|
||||||
bool _namesSupported;
|
bool _namesSupported;
|
||||||
bool _noteRequired;
|
|
||||||
|
|
||||||
QMenu *_linkContextMenu;
|
QMenu *_linkContextMenu;
|
||||||
QAction *_readOnlyLinkAction;
|
QAction *_readOnlyLinkAction;
|
||||||
@@ -140,7 +132,6 @@ private:
|
|||||||
QAction *_expirationDateLinkAction;
|
QAction *_expirationDateLinkAction;
|
||||||
QAction *_unshareLinkAction;
|
QAction *_unshareLinkAction;
|
||||||
QAction *_addAnotherLinkAction;
|
QAction *_addAnotherLinkAction;
|
||||||
QAction *_noteLinkAction;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>365</width>
|
<width>350</width>
|
||||||
<height>192</height>
|
<height>160</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@@ -16,266 +16,60 @@
|
|||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<item>
|
<property name="leftMargin">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<number>0</number>
|
||||||
<item>
|
</property>
|
||||||
<widget class="QLabel" name="shareLinkIconLabel">
|
<property name="topMargin">
|
||||||
<property name="text">
|
<number>0</number>
|
||||||
<string notr="true"/>
|
</property>
|
||||||
</property>
|
<property name="rightMargin">
|
||||||
<property name="pixmap">
|
<number>0</number>
|
||||||
<pixmap resource="../../client.qrc">:/client/resources/public.svg</pixmap>
|
</property>
|
||||||
</property>
|
<property name="bottomMargin">
|
||||||
<property name="alignment">
|
<number>0</number>
|
||||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
</property>
|
||||||
</property>
|
<item row="2" column="1">
|
||||||
</widget>
|
<widget class="QDateEdit" name="calendar">
|
||||||
</item>
|
<property name="sizePolicy">
|
||||||
<item>
|
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||||
<widget class="QLabel" name="shareLinkLabel">
|
<horstretch>1</horstretch>
|
||||||
<property name="text">
|
<verstretch>0</verstretch>
|
||||||
<string>Share link</string>
|
</sizepolicy>
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>25</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QProgressIndicator" name="progressIndicator" native="true"/>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer_2">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>25</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="enableShareLink">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../client.qrc">
|
|
||||||
<normaloff>:/client/resources/add.png</normaloff>:/client/resources/add.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="checkable">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="flat">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="shareLinkToolButton">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../client.qrc">
|
|
||||||
<normaloff>:/client/resources/more.svg</normaloff>:/client/resources/more.svg</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="popupMode">
|
|
||||||
<enum>QToolButton::InstantPopup</enum>
|
|
||||||
</property>
|
|
||||||
<property name="autoRaise">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
|
||||||
<property name="sizeConstraint">
|
|
||||||
<enum>QLayout::SetFixedSize</enum>
|
|
||||||
</property>
|
</property>
|
||||||
<item>
|
</widget>
|
||||||
<widget class="QLabel" name="noteLabel">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>78</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Note:</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
|
||||||
</property>
|
|
||||||
<property name="indent">
|
|
||||||
<number>10</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QTextEdit" name="textEdit_note">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>0</width>
|
|
||||||
<height>60</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="sizeAdjustPolicy">
|
|
||||||
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="confirmNote">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../client.qrc">
|
|
||||||
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="autoRaise">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="1" column="0">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
<widget class="QLabel" name="passwordLabel">
|
||||||
<item>
|
<property name="sizePolicy">
|
||||||
<widget class="QLabel" name="passwordLabel">
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
<property name="sizePolicy">
|
<horstretch>1</horstretch>
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
<verstretch>0</verstretch>
|
||||||
<horstretch>0</horstretch>
|
</sizepolicy>
|
||||||
<verstretch>0</verstretch>
|
</property>
|
||||||
</sizepolicy>
|
<property name="text">
|
||||||
</property>
|
<string>Password:</string>
|
||||||
<property name="minimumSize">
|
</property>
|
||||||
<size>
|
<property name="indent">
|
||||||
<width>78</width>
|
<number>20</number>
|
||||||
<height>0</height>
|
</property>
|
||||||
</size>
|
</widget>
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Password:</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
|
||||||
</property>
|
|
||||||
<property name="indent">
|
|
||||||
<number>10</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="lineEdit_password">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
|
||||||
<horstretch>1</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="echoMode">
|
|
||||||
<enum>QLineEdit::Password</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="confirmPassword">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../client.qrc">
|
|
||||||
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="autoRaise">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="0" column="3">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
<widget class="QToolButton" name="shareLinkToolButton">
|
||||||
<item>
|
<property name="enabled">
|
||||||
<widget class="QLabel" name="expirationLabel">
|
<bool>false</bool>
|
||||||
<property name="sizePolicy">
|
</property>
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
<property name="icon">
|
||||||
<horstretch>0</horstretch>
|
<iconset resource="../../client.qrc">
|
||||||
<verstretch>0</verstretch>
|
<normaloff>:/client/resources/more.svg</normaloff>:/client/resources/more.svg</iconset>
|
||||||
</sizepolicy>
|
</property>
|
||||||
</property>
|
<property name="popupMode">
|
||||||
<property name="minimumSize">
|
<enum>QToolButton::InstantPopup</enum>
|
||||||
<size>
|
</property>
|
||||||
<width>78</width>
|
</widget>
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Expires:</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
|
||||||
</property>
|
|
||||||
<property name="indent">
|
|
||||||
<number>10</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QDateEdit" name="calendar">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
|
||||||
<horstretch>1</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QToolButton" name="confirmExpirationDate">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../client.qrc">
|
|
||||||
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="autoRaise">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="3" column="0" colspan="4">
|
||||||
<widget class="QLabel" name="errorLabel">
|
<widget class="QLabel" name="errorLabel">
|
||||||
<property name="palette">
|
<property name="palette">
|
||||||
<palette>
|
<palette>
|
||||||
@@ -325,6 +119,130 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLineEdit" name="lineEdit_password">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>1</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="echoMode">
|
||||||
|
<enum>QLineEdit::Password</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QToolButton" name="confirmPassword">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../client.qrc">
|
||||||
|
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="autoRaise">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="2">
|
||||||
|
<widget class="QToolButton" name="confirmExpirationDate">
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../client.qrc">
|
||||||
|
<normaloff>:/client/resources/confirm.svg</normaloff>:/client/resources/confirm.svg</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="autoRaise">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="3">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="createShareButton">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">text-align: left</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Share link</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../client.qrc">
|
||||||
|
<normaloff>:/client/resources/public.svg</normaloff>:/client/resources/public.svg</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="flat">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>25</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressIndicator" name="progressIndicator" native="true"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>25</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="enableShareLink">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../client.qrc">
|
||||||
|
<normaloff>:/client/resources/add.png</normaloff>:/client/resources/add.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="flat">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="expirationLabel">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>1</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Expiration date:</string>
|
||||||
|
</property>
|
||||||
|
<property name="indent">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<layoutdefault spacing="6" margin="11"/>
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
|
|||||||
@@ -200,11 +200,6 @@ QString LinkShare::getName() const
|
|||||||
return _name;
|
return _name;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString LinkShare::getNote() const
|
|
||||||
{
|
|
||||||
return _note;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LinkShare::setName(const QString &name)
|
void LinkShare::setName(const QString &name)
|
||||||
{
|
{
|
||||||
OcsShareJob *job = new OcsShareJob(_account);
|
OcsShareJob *job = new OcsShareJob(_account);
|
||||||
@@ -213,20 +208,6 @@ void LinkShare::setName(const QString &name)
|
|||||||
job->setName(getId(), name);
|
job->setName(getId(), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinkShare::setNote(const QString ¬e)
|
|
||||||
{
|
|
||||||
OcsShareJob *job = new OcsShareJob(_account);
|
|
||||||
connect(job, &OcsShareJob::shareJobFinished, this, &LinkShare::slotNoteSet);
|
|
||||||
connect(job, &OcsJob::ocsError, this, &LinkShare::slotOcsError);
|
|
||||||
job->setNote(getId(), note);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LinkShare::slotNoteSet(const QJsonDocument &, const QVariant ¬e)
|
|
||||||
{
|
|
||||||
_note = note.toString();
|
|
||||||
emit noteSet();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString LinkShare::getToken() const
|
QString LinkShare::getToken() const
|
||||||
{
|
{
|
||||||
return _token;
|
return _token;
|
||||||
|
|||||||
@@ -183,12 +183,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
QString getName() const;
|
QString getName() const;
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns the note of the link share.
|
|
||||||
*/
|
|
||||||
|
|
||||||
QString getNote() const;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set the name of the link share.
|
* Set the name of the link share.
|
||||||
*
|
*
|
||||||
@@ -196,12 +190,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
void setName(const QString &name);
|
void setName(const QString &name);
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Set the note of the link share.
|
|
||||||
*/
|
|
||||||
void setNote(const QString ¬e);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns the token of the link share.
|
* Returns the token of the link share.
|
||||||
*/
|
*/
|
||||||
@@ -236,13 +224,11 @@ public:
|
|||||||
signals:
|
signals:
|
||||||
void expireDateSet();
|
void expireDateSet();
|
||||||
void passwordSet();
|
void passwordSet();
|
||||||
void noteSet();
|
|
||||||
void passwordSetError(int statusCode, const QString &message);
|
void passwordSetError(int statusCode, const QString &message);
|
||||||
void nameSet();
|
void nameSet();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void slotPasswordSet(const QJsonDocument &, const QVariant &value);
|
void slotPasswordSet(const QJsonDocument &, const QVariant &value);
|
||||||
void slotNoteSet(const QJsonDocument &, const QVariant &value);
|
|
||||||
void slotExpireDateSet(const QJsonDocument &reply, const QVariant &value);
|
void slotExpireDateSet(const QJsonDocument &reply, const QVariant &value);
|
||||||
void slotSetPasswordError(int statusCode, const QString &message);
|
void slotSetPasswordError(int statusCode, const QString &message);
|
||||||
void slotNameSet(const QJsonDocument &, const QVariant &value);
|
void slotNameSet(const QJsonDocument &, const QVariant &value);
|
||||||
@@ -251,7 +237,6 @@ private:
|
|||||||
QString _name;
|
QString _name;
|
||||||
QString _token;
|
QString _token;
|
||||||
bool _passwordSet;
|
bool _passwordSet;
|
||||||
QString _note;
|
|
||||||
QDate _expireDate;
|
QDate _expireDate;
|
||||||
QUrl _url;
|
QUrl _url;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,8 +46,6 @@
|
|||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QListWidget>
|
#include <QListWidget>
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
|
ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
|
||||||
@@ -208,8 +206,7 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the owner of the file that shared it first
|
// the owner of the file that shared it first
|
||||||
// leave out if it's the current user
|
if(x == 0 && !share->getUidOwner().isEmpty()){
|
||||||
if(x == 0 && !share->getUidOwner().isEmpty() && !(share->getUidOwner() == _account->credentials()->user())) {
|
|
||||||
_ui->mainOwnerLabel->setText(QString("Shared with you by ").append(share->getOwnerDisplayName()));
|
_ui->mainOwnerLabel->setText(QString("Shared with you by ").append(share->getOwnerDisplayName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>350</width>
|
<width>350</width>
|
||||||
<height>70</height>
|
<height>55</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@@ -17,24 +17,30 @@
|
|||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="mainOwnerLabel">
|
<widget class="QLabel" name="mainOwnerLabel">
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string/>
|
<string/>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="shareeHorizontalLayout" stretch="0,0">
|
<layout class="QHBoxLayout" name="shareeHorizontalLayout">
|
||||||
<property name="spacing">
|
|
||||||
<number>6</number>
|
|
||||||
</property>
|
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
@@ -49,12 +55,6 @@
|
|||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLineEdit" name="shareeLineEdit">
|
<widget class="QLineEdit" name="shareeLineEdit">
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="placeholderText">
|
<property name="placeholderText">
|
||||||
<string>Share with users or groups ...</string>
|
<string>Share with users or groups ...</string>
|
||||||
</property>
|
</property>
|
||||||
|
|||||||
@@ -6,26 +6,35 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>360</width>
|
<width>350</width>
|
||||||
<height>58</height>
|
<height>45</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
<horstretch>1</horstretch>
|
<horstretch>0</horstretch>
|
||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>360</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="autoFillBackground">
|
<property name="autoFillBackground">
|
||||||
<bool>false</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,2,2,2">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="avatar">
|
<widget class="QLabel" name="avatar">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@@ -48,7 +57,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="OCC::ElidedLabel" name="sharedWith">
|
<widget class="OCC::ElidedLabel" name="sharedWith">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
|
<sizepolicy hsizetype="Ignored" vsizetype="Maximum">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
@@ -61,22 +70,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeType">
|
|
||||||
<enum>QSizePolicy::Expanding</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="permissionsEdit">
|
<widget class="QCheckBox" name="permissionsEdit">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@@ -96,12 +89,9 @@
|
|||||||
<iconset resource="../../client.qrc">
|
<iconset resource="../../client.qrc">
|
||||||
<normaloff>:/client/resources/more.svg</normaloff>:/client/resources/more.svg</iconset>
|
<normaloff>:/client/resources/more.svg</normaloff>:/client/resources/more.svg</iconset>
|
||||||
</property>
|
</property>
|
||||||
<property name="autoRaise">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
|||||||
@@ -49,7 +49,6 @@
|
|||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QStringBuilder>
|
#include <QStringBuilder>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QInputDialog>
|
|
||||||
|
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
|
|
||||||
@@ -455,42 +454,7 @@ void SocketApi::command_VERSION(const QString &, SocketListener *listener)
|
|||||||
|
|
||||||
void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listener)
|
void SocketApi::command_SHARE_MENU_TITLE(const QString &, SocketListener *listener)
|
||||||
{
|
{
|
||||||
//listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is Nextcloud").arg(Theme::instance()->appNameGUI()));
|
listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + tr("Share with %1", "parameter is Nextcloud").arg(Theme::instance()->appNameGUI()));
|
||||||
listener->sendMessage(QLatin1String("SHARE_MENU_TITLE:") + Theme::instance()->appNameGUI());
|
|
||||||
}
|
|
||||||
|
|
||||||
void SocketApi::command_EDIT(const QString &localFile, SocketListener *listener)
|
|
||||||
{
|
|
||||||
auto fileData = FileData::get(localFile);
|
|
||||||
if (!fileData.folder) {
|
|
||||||
qCWarning(lcSocketApi) << "Unknown path" << localFile;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto record = fileData.journalRecord();
|
|
||||||
if (!record.isValid())
|
|
||||||
return;
|
|
||||||
|
|
||||||
DirectEditor* editor = getDirectEditorForLocalFile(fileData.localPath);
|
|
||||||
if (!editor)
|
|
||||||
return;
|
|
||||||
|
|
||||||
JsonApiJob *job = new JsonApiJob(fileData.folder->accountState()->account(), QLatin1String("ocs/v2.php/apps/files/api/v1/directEditing/open"), this);
|
|
||||||
|
|
||||||
QUrlQuery params;
|
|
||||||
params.addQueryItem("path", fileData.accountRelativePath);
|
|
||||||
params.addQueryItem("editorId", editor->id());
|
|
||||||
job->addQueryParams(params);
|
|
||||||
job->usePOST();
|
|
||||||
|
|
||||||
QObject::connect(job, &JsonApiJob::jsonReceived, [](const QJsonDocument &json){
|
|
||||||
auto data = json.object().value("ocs").toObject().value("data").toObject();
|
|
||||||
auto url = QUrl(data.value("url").toString());
|
|
||||||
|
|
||||||
if(!url.isEmpty())
|
|
||||||
Utility::openBrowser(url, nullptr);
|
|
||||||
});
|
|
||||||
job->start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't pull the share manager into socketapi unittests
|
// don't pull the share manager into socketapi unittests
|
||||||
@@ -513,8 +477,6 @@ public:
|
|||||||
this, &GetOrCreatePublicLinkShare::linkShareCreated);
|
this, &GetOrCreatePublicLinkShare::linkShareCreated);
|
||||||
connect(&_shareManager, &ShareManager::serverError,
|
connect(&_shareManager, &ShareManager::serverError,
|
||||||
this, &GetOrCreatePublicLinkShare::serverError);
|
this, &GetOrCreatePublicLinkShare::serverError);
|
||||||
connect(&_shareManager, &ShareManager::linkShareRequiresPassword,
|
|
||||||
this, &GetOrCreatePublicLinkShare::passwordRequired);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void run()
|
void run()
|
||||||
@@ -550,24 +512,6 @@ private slots:
|
|||||||
success(share->getLink().toString());
|
success(share->getLink().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void passwordRequired() {
|
|
||||||
bool ok;
|
|
||||||
QString password = QInputDialog::getText(nullptr,
|
|
||||||
tr("Password for share required"),
|
|
||||||
tr("Please enter a password for your link share:"),
|
|
||||||
QLineEdit::Normal,
|
|
||||||
QString(),
|
|
||||||
&ok);
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
// The dialog was canceled so no need to do anything
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to create the link share again with the newly entered password
|
|
||||||
_shareManager.createLinkShare(_localFile, QString(), password);
|
|
||||||
}
|
|
||||||
|
|
||||||
void serverError(int code, const QString &message)
|
void serverError(int code, const QString &message)
|
||||||
{
|
{
|
||||||
qCWarning(lcPublicLink) << "Share fetch/create error" << code << message;
|
qCWarning(lcPublicLink) << "Share fetch/create error" << code << message;
|
||||||
@@ -679,7 +623,7 @@ void SocketApi::command_GET_STRINGS(const QString &argument, SocketListener *lis
|
|||||||
{
|
{
|
||||||
static std::array<std::pair<const char *, QString>, 5> strings { {
|
static std::array<std::pair<const char *, QString>, 5> strings { {
|
||||||
{ "SHARE_MENU_TITLE", tr("Share options") },
|
{ "SHARE_MENU_TITLE", tr("Share options") },
|
||||||
{ "CONTEXT_MENU_TITLE", Theme::instance()->appNameGUI() },
|
{ "CONTEXT_MENU_TITLE", tr("Share via %1").arg(Theme::instance()->appNameGUI())},
|
||||||
{ "COPY_PRIVATE_LINK_MENU_TITLE", tr("Copy private link to clipboard") },
|
{ "COPY_PRIVATE_LINK_MENU_TITLE", tr("Copy private link to clipboard") },
|
||||||
{ "EMAIL_PRIVATE_LINK_MENU_TITLE", tr("Send private link by email …") },
|
{ "EMAIL_PRIVATE_LINK_MENU_TITLE", tr("Send private link by email …") },
|
||||||
} };
|
} };
|
||||||
@@ -773,41 +717,13 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe
|
|||||||
FileData fileData = hasSeveralFiles ? FileData{} : FileData::get(argument);
|
FileData fileData = hasSeveralFiles ? FileData{} : FileData::get(argument);
|
||||||
bool isOnTheServer = fileData.journalRecord().isValid();
|
bool isOnTheServer = fileData.journalRecord().isValid();
|
||||||
auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:");
|
auto flagString = isOnTheServer ? QLatin1String("::") : QLatin1String(":d:");
|
||||||
auto capabilities = fileData.folder->accountState()->account()->capabilities();
|
|
||||||
|
|
||||||
if (fileData.folder && fileData.folder->accountState()->isConnected()) {
|
if (fileData.folder && fileData.folder->accountState()->isConnected()) {
|
||||||
DirectEditor* editor = getDirectEditorForLocalFile(fileData.localPath);
|
|
||||||
if (editor) {
|
|
||||||
//listener->sendMessage(QLatin1String("MENU_ITEM:EDIT") + flagString + tr("Edit via ") + editor->name());
|
|
||||||
listener->sendMessage(QLatin1String("MENU_ITEM:EDIT") + flagString + tr("Edit"));
|
|
||||||
} else {
|
|
||||||
listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser"));
|
|
||||||
}
|
|
||||||
|
|
||||||
sendSharingContextMenuOptions(fileData, listener);
|
sendSharingContextMenuOptions(fileData, listener);
|
||||||
|
listener->sendMessage(QLatin1String("MENU_ITEM:OPEN_PRIVATE_LINK") + flagString + tr("Open in browser"));
|
||||||
}
|
}
|
||||||
listener->sendMessage(QString("GET_MENU_ITEMS:END"));
|
listener->sendMessage(QString("GET_MENU_ITEMS:END"));
|
||||||
}
|
}
|
||||||
|
|
||||||
DirectEditor* SocketApi::getDirectEditorForLocalFile(const QString &localFile)
|
|
||||||
{
|
|
||||||
FileData fileData = FileData::get(localFile);
|
|
||||||
auto capabilities = fileData.folder->accountState()->account()->capabilities();
|
|
||||||
|
|
||||||
if (fileData.folder && fileData.folder->accountState()->isConnected()) {
|
|
||||||
QMimeDatabase db;
|
|
||||||
QMimeType type = db.mimeTypeForFile(localFile);
|
|
||||||
|
|
||||||
DirectEditor* editor = capabilities.getDirectEditorForMimetype(type);
|
|
||||||
if (!editor) {
|
|
||||||
editor = capabilities.getDirectEditorForOptionalMimetype(type);
|
|
||||||
}
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SocketApi::buildRegisterPathMessage(const QString &path)
|
QString SocketApi::buildRegisterPathMessage(const QString &path)
|
||||||
{
|
{
|
||||||
QFileInfo fi(path);
|
QFileInfo fi(path);
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ namespace OCC {
|
|||||||
class SyncFileStatus;
|
class SyncFileStatus;
|
||||||
class Folder;
|
class Folder;
|
||||||
class SocketListener;
|
class SocketListener;
|
||||||
class DirectEditor;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The SocketApi class
|
* @brief The SocketApi class
|
||||||
@@ -124,10 +123,6 @@ private:
|
|||||||
*/
|
*/
|
||||||
Q_INVOKABLE void command_GET_MENU_ITEMS(const QString &argument, SocketListener *listener);
|
Q_INVOKABLE void command_GET_MENU_ITEMS(const QString &argument, SocketListener *listener);
|
||||||
|
|
||||||
/// Direct Editing
|
|
||||||
Q_INVOKABLE void command_EDIT(const QString &localFile, SocketListener *listener);
|
|
||||||
DirectEditor* getDirectEditorForLocalFile(const QString &localFile);
|
|
||||||
|
|
||||||
QString buildRegisterPathMessage(const QString &path);
|
QString buildRegisterPathMessage(const QString &path);
|
||||||
|
|
||||||
QSet<QString> _registeredAliases;
|
QSet<QString> _registeredAliases;
|
||||||
|
|||||||
@@ -12,17 +12,9 @@
|
|||||||
* for more details.
|
* for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "accountmanager.h"
|
|
||||||
#include "systray.h"
|
#include "systray.h"
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "tray/UserModel.h"
|
|
||||||
|
|
||||||
#include <QDesktopServices>
|
|
||||||
#include <QGuiApplication>
|
|
||||||
#include <QQmlComponent>
|
|
||||||
#include <QQmlEngine>
|
|
||||||
#include <QScreen>
|
|
||||||
|
|
||||||
#ifdef USE_FDO_NOTIFICATIONS
|
#ifdef USE_FDO_NOTIFICATIONS
|
||||||
#include <QDBusConnection>
|
#include <QDBusConnection>
|
||||||
@@ -36,78 +28,10 @@
|
|||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
Systray *Systray::_instance = nullptr;
|
|
||||||
|
|
||||||
Systray *Systray::instance()
|
|
||||||
{
|
|
||||||
if (_instance == nullptr) {
|
|
||||||
_instance = new Systray();
|
|
||||||
}
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
Systray::Systray()
|
|
||||||
: _isOpen(false)
|
|
||||||
, _syncIsPaused(false)
|
|
||||||
, _trayComponent(nullptr)
|
|
||||||
, _trayContext(nullptr)
|
|
||||||
{
|
|
||||||
// Create QML tray engine, build component, set C++ backend context used in window.qml
|
|
||||||
// Use pointer instead of engine() helper function until Qt 5.12 is minimum standard
|
|
||||||
_trayEngine = new QQmlEngine;
|
|
||||||
_trayEngine->addImageProvider("avatars", new ImageProvider);
|
|
||||||
_trayEngine->rootContext()->setContextProperty("userModelBackend", UserModel::instance());
|
|
||||||
_trayEngine->rootContext()->setContextProperty("appsMenuModelBackend", UserAppsModel::instance());
|
|
||||||
_trayEngine->rootContext()->setContextProperty("systrayBackend", this);
|
|
||||||
|
|
||||||
_trayComponent = new QQmlComponent(_trayEngine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/Window.qml")));
|
|
||||||
|
|
||||||
connect(UserModel::instance(), &UserModel::newUserSelected,
|
|
||||||
this, &Systray::slotNewUserSelected);
|
|
||||||
|
|
||||||
connect(AccountManager::instance(), &AccountManager::accountAdded,
|
|
||||||
this, &Systray::showWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Systray::create()
|
|
||||||
{
|
|
||||||
if (_trayContext == nullptr) {
|
|
||||||
if (!AccountManager::instance()->accounts().isEmpty()) {
|
|
||||||
_trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel());
|
|
||||||
}
|
|
||||||
_trayContext = _trayEngine->contextForObject(_trayComponent->create());
|
|
||||||
hideWindow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Systray::slotNewUserSelected()
|
|
||||||
{
|
|
||||||
// Change ActivityModel
|
|
||||||
_trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel());
|
|
||||||
|
|
||||||
// Rebuild App list
|
|
||||||
UserAppsModel::instance()->buildAppList();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Systray::isOpen()
|
|
||||||
{
|
|
||||||
return _isOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE void Systray::setOpened()
|
|
||||||
{
|
|
||||||
_isOpen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE void Systray::setClosed()
|
|
||||||
{
|
|
||||||
_isOpen = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Systray::showMessage(const QString &title, const QString &message, MessageIcon icon, int millisecondsTimeoutHint)
|
void Systray::showMessage(const QString &title, const QString &message, MessageIcon icon, int millisecondsTimeoutHint)
|
||||||
{
|
{
|
||||||
#ifdef USE_FDO_NOTIFICATIONS
|
#ifdef USE_FDO_NOTIFICATIONS
|
||||||
if (QDBusInterface(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE).isValid()) {
|
if(QDBusInterface(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE).isValid()) {
|
||||||
QList<QVariant> args = QList<QVariant>() << APPLICATION_NAME << quint32(0) << APPLICATION_ICON_NAME
|
QList<QVariant> args = QList<QVariant>() << APPLICATION_NAME << quint32(0) << APPLICATION_ICON_NAME
|
||||||
<< title << message << QStringList() << QVariantMap() << qint32(-1);
|
<< title << message << QStringList() << QVariantMap() << qint32(-1);
|
||||||
QDBusMessage method = QDBusMessage::createMethodCall(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify");
|
QDBusMessage method = QDBusMessage::createMethodCall(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify");
|
||||||
@@ -130,95 +54,4 @@ void Systray::setToolTip(const QString &tip)
|
|||||||
QSystemTrayIcon::setToolTip(tr("%1: %2").arg(Theme::instance()->appNameGUI(), tip));
|
QSystemTrayIcon::setToolTip(tr("%1: %2").arg(Theme::instance()->appNameGUI(), tip));
|
||||||
}
|
}
|
||||||
|
|
||||||
int Systray::calcTrayWindowX()
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_OSX
|
|
||||||
// macOS handles DPI awareness differently
|
|
||||||
// and menu bar is always at the top, icons starting from the right
|
|
||||||
|
|
||||||
QPoint topLeft = this->geometry().topLeft();
|
|
||||||
QPoint topRight = this->geometry().topRight();
|
|
||||||
int trayIconTopCenterX = (topRight - ((topRight - topLeft) * 0.5)).x();
|
|
||||||
return trayIconTopCenterX - (400 * 0.5);
|
|
||||||
#else
|
|
||||||
QScreen *trayScreen = QGuiApplication::primaryScreen();
|
|
||||||
int screenWidth = trayScreen->geometry().width();
|
|
||||||
int screenHeight = trayScreen->geometry().height();
|
|
||||||
int availableWidth = trayScreen->availableGeometry().width();
|
|
||||||
int availableHeight = trayScreen->availableGeometry().height();
|
|
||||||
QPoint topRightDpiAware = this->geometry().topRight() / trayScreen->devicePixelRatio();
|
|
||||||
QPoint topLeftDpiAware = this->geometry().topLeft() / trayScreen->devicePixelRatio();
|
|
||||||
|
|
||||||
// get coordinates from top center point of tray icon
|
|
||||||
int trayIconTopCenterX = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).x();
|
|
||||||
int trayIconTopCenterY = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).y();
|
|
||||||
|
|
||||||
if (availableHeight < screenHeight) {
|
|
||||||
// taskbar is on top or bottom
|
|
||||||
if (trayIconTopCenterX + (400 * 0.5) > availableWidth) {
|
|
||||||
return availableWidth - 400 - 12;
|
|
||||||
} else {
|
|
||||||
return trayIconTopCenterX - (400 * 0.5);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (trayScreen->availableGeometry().x() > trayScreen->geometry().x()) {
|
|
||||||
// on the left
|
|
||||||
return (screenWidth - availableWidth) + 6;
|
|
||||||
} else {
|
|
||||||
// on the right
|
|
||||||
return screenWidth - 400 - (screenWidth - availableWidth) - 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
int Systray::calcTrayWindowY()
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_OSX
|
|
||||||
// macOS menu bar is always 22 (effective) pixels
|
|
||||||
// don't use availableGeometry() here, because this also excludes the dock
|
|
||||||
return 22+6;
|
|
||||||
#else
|
|
||||||
QScreen *trayScreen = QGuiApplication::primaryScreen();
|
|
||||||
int screenWidth = trayScreen->geometry().width();
|
|
||||||
int screenHeight = trayScreen->geometry().height();
|
|
||||||
int availableHeight = trayScreen->availableGeometry().height();
|
|
||||||
QPoint topRightDpiAware = this->geometry().topRight() / trayScreen->devicePixelRatio();
|
|
||||||
QPoint topLeftDpiAware = this->geometry().topLeft() / trayScreen->devicePixelRatio();
|
|
||||||
|
|
||||||
// get coordinates from top center point of tray icon
|
|
||||||
int trayIconTopCenterX = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).x();
|
|
||||||
int trayIconTopCenterY = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).y();
|
|
||||||
|
|
||||||
if (availableHeight < screenHeight) {
|
|
||||||
// taskbar is on top or bottom
|
|
||||||
if (trayScreen->availableGeometry().y() > trayScreen->geometry().y()) {
|
|
||||||
// on top
|
|
||||||
return (screenHeight - availableHeight) + 6;
|
|
||||||
} else {
|
|
||||||
// on bottom
|
|
||||||
return screenHeight - 510 - (screenHeight - availableHeight) - 6;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// on the left or right
|
|
||||||
return (trayIconTopCenterY - 510 + 12);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Systray::syncIsPaused()
|
|
||||||
{
|
|
||||||
return _syncIsPaused;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Systray::pauseResumeSync()
|
|
||||||
{
|
|
||||||
if (_syncIsPaused) {
|
|
||||||
_syncIsPaused = false;
|
|
||||||
emit resumeSync();
|
|
||||||
} else {
|
|
||||||
_syncIsPaused = true;
|
|
||||||
emit pauseSync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|||||||
@@ -16,10 +16,6 @@
|
|||||||
#define SYSTRAY_H
|
#define SYSTRAY_H
|
||||||
|
|
||||||
#include <QSystemTrayIcon>
|
#include <QSystemTrayIcon>
|
||||||
#include <QQmlContext>
|
|
||||||
|
|
||||||
#include "accountmanager.h"
|
|
||||||
#include "tray/UserModel.h"
|
|
||||||
|
|
||||||
class QIcon;
|
class QIcon;
|
||||||
|
|
||||||
@@ -30,56 +26,16 @@ bool canOsXSendUserNotification();
|
|||||||
void sendOsXUserNotification(const QString &title, const QString &message);
|
void sendOsXUserNotification(const QString &title, const QString &message);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace Ui {
|
|
||||||
class Systray;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The Systray class
|
* @brief The Systray class
|
||||||
* @ingroup gui
|
* @ingroup gui
|
||||||
*/
|
*/
|
||||||
class Systray
|
class Systray : public QSystemTrayIcon
|
||||||
: public QSystemTrayIcon
|
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
static Systray *instance();
|
|
||||||
virtual ~Systray() {};
|
|
||||||
|
|
||||||
void create();
|
|
||||||
void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000);
|
void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000);
|
||||||
void setToolTip(const QString &tip);
|
void setToolTip(const QString &tip);
|
||||||
bool isOpen();
|
|
||||||
|
|
||||||
Q_INVOKABLE void pauseResumeSync();
|
|
||||||
Q_INVOKABLE int calcTrayWindowX();
|
|
||||||
Q_INVOKABLE int calcTrayWindowY();
|
|
||||||
Q_INVOKABLE bool syncIsPaused();
|
|
||||||
Q_INVOKABLE void setOpened();
|
|
||||||
Q_INVOKABLE void setClosed();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void currentUserChanged();
|
|
||||||
void openSettings();
|
|
||||||
void openHelp();
|
|
||||||
void shutdown();
|
|
||||||
void pauseSync();
|
|
||||||
void resumeSync();
|
|
||||||
|
|
||||||
Q_INVOKABLE void hideWindow();
|
|
||||||
Q_INVOKABLE void showWindow();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void slotNewUserSelected();
|
|
||||||
|
|
||||||
private:
|
|
||||||
static Systray *_instance;
|
|
||||||
Systray();
|
|
||||||
bool _isOpen;
|
|
||||||
bool _syncIsPaused;
|
|
||||||
QQmlEngine *_trayEngine;
|
|
||||||
QQmlComponent *_trayComponent;
|
|
||||||
QQmlContext *_trayContext;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
#ifndef NOTIFICATIONHANDLER_H
|
|
||||||
#define NOTIFICATIONHANDLER_H
|
|
||||||
|
|
||||||
#include <QtCore>
|
|
||||||
|
|
||||||
#include "UserModel.h"
|
|
||||||
|
|
||||||
class QJsonDocument;
|
|
||||||
|
|
||||||
namespace OCC {
|
|
||||||
|
|
||||||
class ServerNotificationHandler : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit ServerNotificationHandler(AccountState *accountState, QObject *parent = nullptr);
|
|
||||||
static QMap<int, QByteArray> iconCache;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void newNotificationList(ActivityList);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void slotFetchNotifications();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void slotNotificationsReceived(const QJsonDocument &json, int statusCode);
|
|
||||||
void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode);
|
|
||||||
void slotIconDownloaded(QByteArray iconData);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QPointer<JsonApiJob> _notificationJob;
|
|
||||||
AccountState *_accountState;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // NOTIFICATIONHANDLER_H
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Window 2.2
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtQuick.Layouts 1.2
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
id: userLine
|
|
||||||
height: 60
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: userLineLayout
|
|
||||||
spacing: 0
|
|
||||||
width: 220
|
|
||||||
height: 60
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: accountButton
|
|
||||||
Layout.preferredWidth: (userLineLayout.width * (5/6))
|
|
||||||
Layout.preferredHeight: (userLineLayout.height)
|
|
||||||
display: AbstractButton.IconOnly
|
|
||||||
flat: true
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onContainsMouseChanged: {
|
|
||||||
accountStateIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white")
|
|
||||||
}
|
|
||||||
onClicked: {
|
|
||||||
if (!isCurrentUser) {
|
|
||||||
userModelBackend.switchCurrentUser(id)
|
|
||||||
} else {
|
|
||||||
accountMenu.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: "transparent"
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: accountControlRowLayout
|
|
||||||
height: accountButton.height
|
|
||||||
width: accountButton.width
|
|
||||||
spacing: 0
|
|
||||||
Image {
|
|
||||||
id: accountAvatar
|
|
||||||
Layout.leftMargin: 4
|
|
||||||
verticalAlignment: Qt.AlignCenter
|
|
||||||
cache: false
|
|
||||||
source: ("image://avatars/" + id)
|
|
||||||
Layout.preferredHeight: (userLineLayout.height -16)
|
|
||||||
Layout.preferredWidth: (userLineLayout.height -16)
|
|
||||||
Rectangle {
|
|
||||||
id: accountStateIndicatorBackground
|
|
||||||
width: accountStateIndicator.sourceSize.width + 2
|
|
||||||
height: width
|
|
||||||
anchors.bottom: accountAvatar.bottom
|
|
||||||
anchors.right: accountAvatar.right
|
|
||||||
color: "white"
|
|
||||||
radius: width*0.5
|
|
||||||
}
|
|
||||||
Image {
|
|
||||||
id: accountStateIndicator
|
|
||||||
source: isConnected ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg"
|
|
||||||
cache: false
|
|
||||||
x: accountStateIndicatorBackground.x + 1
|
|
||||||
y: accountStateIndicatorBackground.y + 1
|
|
||||||
sourceSize.width: 16
|
|
||||||
sourceSize.height: 16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: accountLabels
|
|
||||||
spacing: 4
|
|
||||||
Layout.alignment: Qt.AlignLeft
|
|
||||||
Layout.leftMargin: 6
|
|
||||||
Label {
|
|
||||||
id: accountUser
|
|
||||||
width: 128
|
|
||||||
text: name
|
|
||||||
elide: Text.ElideRight
|
|
||||||
color: "black"
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
id: accountServer
|
|
||||||
width: 128
|
|
||||||
text: server
|
|
||||||
elide: Text.ElideRight
|
|
||||||
color: "black"
|
|
||||||
font.pixelSize: 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // accountButton
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: userMoreButton
|
|
||||||
Layout.preferredWidth: (userLineLayout.width * (1/6))
|
|
||||||
Layout.preferredHeight: userLineLayout.height
|
|
||||||
flat: true
|
|
||||||
|
|
||||||
icon.source: "qrc:///client/resources/more.svg"
|
|
||||||
icon.color: "transparent"
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: userMoreButtonMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked:
|
|
||||||
{
|
|
||||||
userMoreButtonMenu.popup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
background:
|
|
||||||
Rectangle {
|
|
||||||
color: userMoreButtonMouseArea.containsMouse ? "grey" : "transparent"
|
|
||||||
opacity: 0.2
|
|
||||||
height: userMoreButton.height - 2
|
|
||||||
y: userMoreButton.y + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Menu {
|
|
||||||
id: userMoreButtonMenu
|
|
||||||
width: 120
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
border.color: "#0082c9"
|
|
||||||
radius: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: isConnected ? qsTr("Log out") : qsTr("Log in")
|
|
||||||
font.pixelSize: 12
|
|
||||||
onClicked: {
|
|
||||||
isConnected ? userModelBackend.logout(index) : userModelBackend.login(index)
|
|
||||||
accountMenu.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: qsTr("Remove Account")
|
|
||||||
font.pixelSize: 12
|
|
||||||
onClicked: {
|
|
||||||
userModelBackend.removeAccount(index)
|
|
||||||
accountMenu.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // MenuItem userLine
|
|
||||||
@@ -1,874 +0,0 @@
|
|||||||
#include "NotificationHandler.h"
|
|
||||||
#include "UserModel.h"
|
|
||||||
|
|
||||||
#include "accountmanager.h"
|
|
||||||
#include "owncloudgui.h"
|
|
||||||
#include "syncengine.h"
|
|
||||||
#include "ocsjob.h"
|
|
||||||
#include "configfile.h"
|
|
||||||
#include "notificationconfirmjob.h"
|
|
||||||
|
|
||||||
#include <QDesktopServices>
|
|
||||||
#include <QIcon>
|
|
||||||
#include <QMessageBox>
|
|
||||||
#include <QSvgRenderer>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QPushButton>
|
|
||||||
|
|
||||||
// time span in milliseconds which has to be between two
|
|
||||||
// refreshes of the notifications
|
|
||||||
#define NOTIFICATION_REQUEST_FREE_PERIOD 15000
|
|
||||||
|
|
||||||
namespace OCC {
|
|
||||||
|
|
||||||
User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
, _account(account)
|
|
||||||
, _isCurrentUser(isCurrent)
|
|
||||||
, _activityModel(new ActivityListModel(_account.data()))
|
|
||||||
, _notificationRequestsRunning(0)
|
|
||||||
{
|
|
||||||
connect(ProgressDispatcher::instance(), &ProgressDispatcher::progressInfo,
|
|
||||||
this, &User::slotProgressInfo);
|
|
||||||
connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted,
|
|
||||||
this, &User::slotItemCompleted);
|
|
||||||
connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError,
|
|
||||||
this, &User::slotAddError);
|
|
||||||
|
|
||||||
connect(&_notificationCheckTimer, &QTimer::timeout,
|
|
||||||
this, &User::slotRefresh);
|
|
||||||
|
|
||||||
connect(_account.data(), &AccountState::stateChanged,
|
|
||||||
[=]() { if (isConnected()) {slotRefresh();} });
|
|
||||||
connect(_account.data(), &AccountState::hasFetchedNavigationApps,
|
|
||||||
this, &User::slotRebuildNavigationAppList);
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::slotBuildNotificationDisplay(const ActivityList &list)
|
|
||||||
{
|
|
||||||
// Whether a new notification was added to the list
|
|
||||||
bool newNotificationShown = false;
|
|
||||||
|
|
||||||
_activityModel->clearNotifications();
|
|
||||||
|
|
||||||
foreach (auto activity, list) {
|
|
||||||
if (_blacklistedNotifications.contains(activity)) {
|
|
||||||
qCInfo(lcActivity) << "Activity in blacklist, skip";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle gui logs. In order to NOT annoy the user with every fetching of the
|
|
||||||
// notifications the notification id is stored in a Set. Only if an id
|
|
||||||
// is not in the set, it qualifies for guiLog.
|
|
||||||
// Important: The _guiLoggedNotifications set must be wiped regularly which
|
|
||||||
// will repeat the gui log.
|
|
||||||
|
|
||||||
// after one hour, clear the gui log notification store
|
|
||||||
if (_guiLogTimer.elapsed() > 60 * 60 * 1000) {
|
|
||||||
_guiLoggedNotifications.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_guiLoggedNotifications.contains(activity._id)) {
|
|
||||||
newNotificationShown = true;
|
|
||||||
_guiLoggedNotifications.insert(activity._id);
|
|
||||||
|
|
||||||
// Assemble a tray notification for the NEW notification
|
|
||||||
ConfigFile cfg;
|
|
||||||
if (cfg.optionalServerNotifications()) {
|
|
||||||
if (AccountManager::instance()->accounts().count() == 1) {
|
|
||||||
emit guiLog(activity._subject, "");
|
|
||||||
} else {
|
|
||||||
emit guiLog(activity._subject, activity._accName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_activityModel->addNotificationToActivityList(activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// restart the gui log timer now that we show a new notification
|
|
||||||
if (newNotificationShown) {
|
|
||||||
_guiLogTimer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::setNotificationRefreshInterval(std::chrono::milliseconds interval)
|
|
||||||
{
|
|
||||||
qCDebug(lcActivity) << "Starting Notification refresh timer with " << interval.count() / 1000 << " sec interval";
|
|
||||||
_notificationCheckTimer.start(interval.count());
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::slotRefreshImmediately() {
|
|
||||||
if (_account.data() && _account.data()->isConnected()) {
|
|
||||||
this->slotRefreshActivities();
|
|
||||||
}
|
|
||||||
this->slotRefreshNotifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::slotRefresh()
|
|
||||||
{
|
|
||||||
// QElapsedTimer isn't actually constructed as invalid.
|
|
||||||
if (!_timeSinceLastCheck.contains(_account.data())) {
|
|
||||||
_timeSinceLastCheck[_account.data()].invalidate();
|
|
||||||
}
|
|
||||||
QElapsedTimer &timer = _timeSinceLastCheck[_account.data()];
|
|
||||||
|
|
||||||
// Fetch Activities only if visible and if last check is longer than 15 secs ago
|
|
||||||
if (timer.isValid() && timer.elapsed() < NOTIFICATION_REQUEST_FREE_PERIOD) {
|
|
||||||
qCDebug(lcActivity) << "Do not check as last check is only secs ago: " << timer.elapsed() / 1000;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_account.data() && _account.data()->isConnected()) {
|
|
||||||
if (!timer.isValid()) {
|
|
||||||
this->slotRefreshActivities();
|
|
||||||
}
|
|
||||||
this->slotRefreshNotifications();
|
|
||||||
timer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::slotRefreshActivities()
|
|
||||||
{
|
|
||||||
_activityModel->slotRefreshActivity();
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::slotRefreshNotifications()
|
|
||||||
{
|
|
||||||
// start a server notification handler if no notification requests
|
|
||||||
// are running
|
|
||||||
if (_notificationRequestsRunning == 0) {
|
|
||||||
ServerNotificationHandler *snh = new ServerNotificationHandler(_account.data());
|
|
||||||
connect(snh, &ServerNotificationHandler::newNotificationList,
|
|
||||||
this, &User::slotBuildNotificationDisplay);
|
|
||||||
|
|
||||||
snh->slotFetchNotifications();
|
|
||||||
} else {
|
|
||||||
qCWarning(lcActivity) << "Notification request counter not zero.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::slotRebuildNavigationAppList()
|
|
||||||
{
|
|
||||||
// Rebuild App list
|
|
||||||
UserAppsModel::instance()->buildAppList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::slotNotificationRequestFinished(int statusCode)
|
|
||||||
{
|
|
||||||
int row = sender()->property("activityRow").toInt();
|
|
||||||
|
|
||||||
// the ocs API returns stat code 100 or 200 inside the xml if it succeeded.
|
|
||||||
if (statusCode != OCS_SUCCESS_STATUS_CODE && statusCode != OCS_SUCCESS_STATUS_CODE_V2) {
|
|
||||||
qCWarning(lcActivity) << "Notification Request to Server failed, leave notification visible.";
|
|
||||||
} else {
|
|
||||||
// to do use the model to rebuild the list or remove the item
|
|
||||||
qCWarning(lcActivity) << "Notification Request to Server successed, rebuilding list.";
|
|
||||||
_activityModel->removeActivityFromActivityList(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::slotEndNotificationRequest(int replyCode)
|
|
||||||
{
|
|
||||||
_notificationRequestsRunning--;
|
|
||||||
slotNotificationRequestFinished(replyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row)
|
|
||||||
{
|
|
||||||
qCInfo(lcActivity) << "Server Notification Request " << verb << link << "on account" << accountName;
|
|
||||||
|
|
||||||
const QStringList validVerbs = QStringList() << "GET"
|
|
||||||
<< "PUT"
|
|
||||||
<< "POST"
|
|
||||||
<< "DELETE";
|
|
||||||
|
|
||||||
if (validVerbs.contains(verb)) {
|
|
||||||
AccountStatePtr acc = AccountManager::instance()->account(accountName);
|
|
||||||
if (acc) {
|
|
||||||
NotificationConfirmJob *job = new NotificationConfirmJob(acc->account());
|
|
||||||
QUrl l(link);
|
|
||||||
job->setLinkAndVerb(l, verb);
|
|
||||||
job->setProperty("activityRow", QVariant::fromValue(row));
|
|
||||||
connect(job, &AbstractNetworkJob::networkError,
|
|
||||||
this, &User::slotNotifyNetworkError);
|
|
||||||
connect(job, &NotificationConfirmJob::jobFinished,
|
|
||||||
this, &User::slotNotifyServerFinished);
|
|
||||||
job->start();
|
|
||||||
|
|
||||||
// count the number of running notification requests. If this member var
|
|
||||||
// is larger than zero, no new fetching of notifications is started
|
|
||||||
_notificationRequestsRunning++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qCWarning(lcActivity) << "Notification Links: Invalid verb:" << verb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::slotNotifyNetworkError(QNetworkReply *reply)
|
|
||||||
{
|
|
||||||
NotificationConfirmJob *job = qobject_cast<NotificationConfirmJob *>(sender());
|
|
||||||
if (!job) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int resultCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
||||||
|
|
||||||
slotEndNotificationRequest(resultCode);
|
|
||||||
qCWarning(lcActivity) << "Server notify job failed with code " << resultCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::slotNotifyServerFinished(const QString &reply, int replyCode)
|
|
||||||
{
|
|
||||||
NotificationConfirmJob *job = qobject_cast<NotificationConfirmJob *>(sender());
|
|
||||||
if (!job) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
slotEndNotificationRequest(replyCode);
|
|
||||||
qCInfo(lcActivity) << "Server Notification reply code" << replyCode << reply;
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::slotProgressInfo(const QString &folder, const ProgressInfo &progress)
|
|
||||||
{
|
|
||||||
if (progress.status() == ProgressInfo::Reconcile) {
|
|
||||||
// Wipe all non-persistent entries - as well as the persistent ones
|
|
||||||
// in cases where a local discovery was done.
|
|
||||||
auto f = FolderMan::instance()->folder(folder);
|
|
||||||
if (!f)
|
|
||||||
return;
|
|
||||||
const auto &engine = f->syncEngine();
|
|
||||||
const auto style = engine.lastLocalDiscoveryStyle();
|
|
||||||
foreach (Activity activity, _activityModel->errorsList()) {
|
|
||||||
if (activity._folder != folder) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (style == LocalDiscoveryStyle::FilesystemOnly) {
|
|
||||||
_activityModel->removeActivityFromActivityList(activity);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activity._status == SyncFileItem::Conflict && !QFileInfo(f->path() + activity._file).exists()) {
|
|
||||||
_activityModel->removeActivityFromActivityList(activity);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activity._status == SyncFileItem::FileLocked && !QFileInfo(f->path() + activity._file).exists()) {
|
|
||||||
_activityModel->removeActivityFromActivityList(activity);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (activity._status == SyncFileItem::FileIgnored && !QFileInfo(f->path() + activity._file).exists()) {
|
|
||||||
_activityModel->removeActivityFromActivityList(activity);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!QFileInfo(f->path() + activity._file).exists()) {
|
|
||||||
_activityModel->removeActivityFromActivityList(activity);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto path = QFileInfo(activity._file).dir().path().toUtf8();
|
|
||||||
if (path == ".")
|
|
||||||
path.clear();
|
|
||||||
|
|
||||||
if (engine.shouldDiscoverLocally(path))
|
|
||||||
_activityModel->removeActivityFromActivityList(activity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress.status() == ProgressInfo::Done) {
|
|
||||||
// We keep track very well of pending conflicts.
|
|
||||||
// Inform other components about them.
|
|
||||||
QStringList conflicts;
|
|
||||||
foreach (Activity activity, _activityModel->errorsList()) {
|
|
||||||
if (activity._folder == folder
|
|
||||||
&& activity._status == SyncFileItem::Conflict) {
|
|
||||||
conflicts.append(activity._file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emit ProgressDispatcher::instance()->folderConflicts(folder, conflicts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::slotAddError(const QString &folderAlias, const QString &message, ErrorCategory category)
|
|
||||||
{
|
|
||||||
auto folderInstance = FolderMan::instance()->folder(folderAlias);
|
|
||||||
if (!folderInstance)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (folderInstance->accountState() == _account.data()) {
|
|
||||||
qCWarning(lcActivity) << "Item " << folderInstance->shortGuiLocalPath() << " retrieved resulted in " << message;
|
|
||||||
|
|
||||||
Activity activity;
|
|
||||||
activity._type = Activity::SyncResultType;
|
|
||||||
activity._status = SyncResult::Error;
|
|
||||||
activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate);
|
|
||||||
activity._subject = message;
|
|
||||||
activity._message = folderInstance->shortGuiLocalPath();
|
|
||||||
activity._link = folderInstance->shortGuiLocalPath();
|
|
||||||
activity._accName = folderInstance->accountState()->account()->displayName();
|
|
||||||
activity._folder = folderAlias;
|
|
||||||
|
|
||||||
|
|
||||||
if (category == ErrorCategory::InsufficientRemoteStorage) {
|
|
||||||
ActivityLink link;
|
|
||||||
link._label = tr("Retry all uploads");
|
|
||||||
link._link = folderInstance->path();
|
|
||||||
link._verb = "";
|
|
||||||
link._isPrimary = true;
|
|
||||||
activity._links.append(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add 'other errors' to activity list
|
|
||||||
_activityModel->addErrorToActivityList(activity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
|
|
||||||
{
|
|
||||||
auto folderInstance = FolderMan::instance()->folder(folder);
|
|
||||||
|
|
||||||
if (!folderInstance)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// check if we are adding it to the right account and if it is useful information (protocol errors)
|
|
||||||
if (folderInstance->accountState() == _account.data()) {
|
|
||||||
qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in " << item->_errorString;
|
|
||||||
|
|
||||||
Activity activity;
|
|
||||||
activity._type = Activity::SyncFileItemType; //client activity
|
|
||||||
activity._status = item->_status;
|
|
||||||
activity._dateTime = QDateTime::currentDateTime();
|
|
||||||
activity._message = item->_originalFile;
|
|
||||||
activity._link = folderInstance->accountState()->account()->url();
|
|
||||||
activity._accName = folderInstance->accountState()->account()->displayName();
|
|
||||||
activity._file = item->_file;
|
|
||||||
activity._folder = folder;
|
|
||||||
activity._fileAction = "";
|
|
||||||
|
|
||||||
if (item->_instruction == CSYNC_INSTRUCTION_REMOVE) {
|
|
||||||
activity._fileAction = "file_deleted";
|
|
||||||
} else if (item->_instruction == CSYNC_INSTRUCTION_NEW) {
|
|
||||||
activity._fileAction = "file_created";
|
|
||||||
} else if (item->_instruction == CSYNC_INSTRUCTION_RENAME) {
|
|
||||||
activity._fileAction = "file_renamed";
|
|
||||||
} else {
|
|
||||||
activity._fileAction = "file_changed";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (item->_status == SyncFileItem::NoStatus || item->_status == SyncFileItem::Success) {
|
|
||||||
qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully.";
|
|
||||||
|
|
||||||
if (activity._fileAction == "file_renamed") {
|
|
||||||
activity._message.prepend(tr("You renamed") + " ");
|
|
||||||
} else if (activity._fileAction == "file_deleted") {
|
|
||||||
activity._message.prepend(tr("You deleted") + " ");
|
|
||||||
} else if (activity._fileAction == "file_created") {
|
|
||||||
activity._message.prepend(tr("You created") + " ");
|
|
||||||
} else {
|
|
||||||
activity._message.prepend(tr("You changed") + " ");
|
|
||||||
}
|
|
||||||
|
|
||||||
_activityModel->addSyncFileItemToActivityList(activity);
|
|
||||||
} else {
|
|
||||||
qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in error " << item->_errorString;
|
|
||||||
activity._subject = item->_errorString;
|
|
||||||
|
|
||||||
if (item->_status == SyncFileItem::Status::FileIgnored) {
|
|
||||||
_activityModel->addIgnoredFileToList(activity);
|
|
||||||
} else {
|
|
||||||
// add 'protocol error' to activity list
|
|
||||||
_activityModel->addErrorToActivityList(activity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountPtr User::account() const
|
|
||||||
{
|
|
||||||
return _account->account();
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::setCurrentUser(const bool &isCurrent)
|
|
||||||
{
|
|
||||||
_isCurrentUser = isCurrent;
|
|
||||||
}
|
|
||||||
|
|
||||||
Folder *User::getFolder()
|
|
||||||
{
|
|
||||||
foreach (Folder *folder, FolderMan::instance()->map()) {
|
|
||||||
if (folder->accountState() == _account.data()) {
|
|
||||||
return folder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ActivityListModel *User::getActivityModel()
|
|
||||||
{
|
|
||||||
return _activityModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::openLocalFolder()
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
QString path = "file:///" + this->getFolder()->path();
|
|
||||||
#else
|
|
||||||
QString path = "file://" + this->getFolder()->path();
|
|
||||||
#endif
|
|
||||||
QDesktopServices::openUrl(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::login() const
|
|
||||||
{
|
|
||||||
_account->account()->resetRejectedCertificates();
|
|
||||||
_account->signIn();
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::logout() const
|
|
||||||
{
|
|
||||||
_account->signOutByUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString User::name() const
|
|
||||||
{
|
|
||||||
// If davDisplayName is empty (can be several reasons, simplest is missing login at startup), fall back to username
|
|
||||||
QString name = _account->account()->davDisplayName();
|
|
||||||
if (name == "") {
|
|
||||||
name = _account->account()->credentials()->user();
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString User::server(bool shortened) const
|
|
||||||
{
|
|
||||||
QString serverUrl = _account->account()->url().toString();
|
|
||||||
if (shortened) {
|
|
||||||
serverUrl.replace(QLatin1String("https://"), QLatin1String(""));
|
|
||||||
serverUrl.replace(QLatin1String("http://"), QLatin1String(""));
|
|
||||||
}
|
|
||||||
return serverUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage User::avatar(bool whiteBg) const
|
|
||||||
{
|
|
||||||
QImage img = AvatarJob::makeCircularAvatar(_account->account()->avatar());
|
|
||||||
if (img.isNull()) {
|
|
||||||
QImage image(128, 128, QImage::Format_ARGB32);
|
|
||||||
image.fill(Qt::GlobalColor::transparent);
|
|
||||||
QPainter painter(&image);
|
|
||||||
|
|
||||||
QSvgRenderer renderer(QString(whiteBg ? ":/client/theme/black/user.svg" : ":/client/theme/white/user.svg"));
|
|
||||||
renderer.render(&painter);
|
|
||||||
|
|
||||||
return image;
|
|
||||||
} else {
|
|
||||||
return img;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool User::serverHasTalk() const
|
|
||||||
{
|
|
||||||
return _account->hasTalk();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool User::hasActivities() const
|
|
||||||
{
|
|
||||||
return _account->account()->capabilities().hasActivities();
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountAppList User::appList() const
|
|
||||||
{
|
|
||||||
return _account->appList();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool User::isCurrentUser() const
|
|
||||||
{
|
|
||||||
return _isCurrentUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool User::isConnected() const
|
|
||||||
{
|
|
||||||
return (_account->connectionStatus() == AccountState::ConnectionStatus::Connected);
|
|
||||||
}
|
|
||||||
|
|
||||||
void User::removeAccount() const
|
|
||||||
{
|
|
||||||
AccountManager::instance()->deleteAccount(_account.data());
|
|
||||||
AccountManager::instance()->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
UserModel *UserModel::_instance = nullptr;
|
|
||||||
|
|
||||||
UserModel *UserModel::instance()
|
|
||||||
{
|
|
||||||
if (_instance == nullptr) {
|
|
||||||
_instance = new UserModel();
|
|
||||||
}
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
UserModel::UserModel(QObject *parent)
|
|
||||||
: QAbstractListModel(parent)
|
|
||||||
, _currentUserId()
|
|
||||||
{
|
|
||||||
// TODO: Remember selected user from last quit via settings file
|
|
||||||
if (AccountManager::instance()->accounts().size() > 0) {
|
|
||||||
buildUserList();
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(AccountManager::instance(), &AccountManager::accountAdded,
|
|
||||||
this, &UserModel::buildUserList);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UserModel::buildUserList()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < AccountManager::instance()->accounts().size(); i++) {
|
|
||||||
auto user = AccountManager::instance()->accounts().at(i);
|
|
||||||
addUser(user);
|
|
||||||
}
|
|
||||||
if (_init) {
|
|
||||||
_users.first()->setCurrentUser(true);
|
|
||||||
_init = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE int UserModel::numUsers()
|
|
||||||
{
|
|
||||||
return _users.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE int UserModel::currentUserId()
|
|
||||||
{
|
|
||||||
return _currentUserId;
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE bool UserModel::isUserConnected(const int &id)
|
|
||||||
{
|
|
||||||
if (!_users.isEmpty()) {
|
|
||||||
return _users[id]->isConnected();
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE QImage UserModel::currentUserAvatar()
|
|
||||||
{
|
|
||||||
if (_users.count() >= 1) {
|
|
||||||
return _users[_currentUserId]->avatar();
|
|
||||||
} else {
|
|
||||||
QImage image(128, 128, QImage::Format_ARGB32);
|
|
||||||
image.fill(Qt::GlobalColor::transparent);
|
|
||||||
QPainter painter(&image);
|
|
||||||
QSvgRenderer renderer(QString(":/client/theme/white/user.svg"));
|
|
||||||
renderer.render(&painter);
|
|
||||||
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage UserModel::avatarById(const int &id)
|
|
||||||
{
|
|
||||||
return _users[id]->avatar(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE QString UserModel::currentUserName()
|
|
||||||
{
|
|
||||||
if (_users.count() >= 1) {
|
|
||||||
return _users[_currentUserId]->name();
|
|
||||||
} else {
|
|
||||||
return QString("No users");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE QString UserModel::currentUserServer()
|
|
||||||
{
|
|
||||||
if (_users.count() >= 1) {
|
|
||||||
return _users[_currentUserId]->server();
|
|
||||||
} else {
|
|
||||||
return QString("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE bool UserModel::currentServerHasTalk()
|
|
||||||
{
|
|
||||||
if (_users.count() >= 1) {
|
|
||||||
return _users[_currentUserId]->serverHasTalk();
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent)
|
|
||||||
{
|
|
||||||
bool containsUser = false;
|
|
||||||
for (int i = 0; i < _users.size(); i++) {
|
|
||||||
if (_users[i]->account() == user->account()) {
|
|
||||||
containsUser = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!containsUser) {
|
|
||||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
|
||||||
_users << new User(user, isCurrent);
|
|
||||||
if (isCurrent) {
|
|
||||||
_currentUserId = _users.indexOf(_users.last());
|
|
||||||
}
|
|
||||||
endInsertRows();
|
|
||||||
ConfigFile cfg;
|
|
||||||
_users.last()->setNotificationRefreshInterval(cfg.notificationRefreshInterval());
|
|
||||||
emit newUserSelected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int UserModel::currentUserIndex()
|
|
||||||
{
|
|
||||||
return _currentUserId;
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE void UserModel::openCurrentAccountLocalFolder()
|
|
||||||
{
|
|
||||||
_users[_currentUserId]->openLocalFolder();
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE void UserModel::openCurrentAccountTalk()
|
|
||||||
{
|
|
||||||
QString url = _users[_currentUserId]->server(false) + "/apps/spreed";
|
|
||||||
if (!(url.contains("http://") || url.contains("https://"))) {
|
|
||||||
url = "https://" + _users[_currentUserId]->server(false) + "/apps/spreed";
|
|
||||||
}
|
|
||||||
QDesktopServices::openUrl(QUrl(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE void UserModel::openCurrentAccountServer()
|
|
||||||
{
|
|
||||||
// Don't open this URL when the QML appMenu pops up on click (see Window.qml)
|
|
||||||
if(appList().count() > 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
QString url = _users[_currentUserId]->server(false);
|
|
||||||
if (!(url.contains("http://") || url.contains("https://"))) {
|
|
||||||
url = "https://" + _users[_currentUserId]->server(false);
|
|
||||||
}
|
|
||||||
QDesktopServices::openUrl(QUrl(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE void UserModel::switchCurrentUser(const int &id)
|
|
||||||
{
|
|
||||||
_users[_currentUserId]->setCurrentUser(false);
|
|
||||||
_users[id]->setCurrentUser(true);
|
|
||||||
_currentUserId = id;
|
|
||||||
emit refreshCurrentUserGui();
|
|
||||||
emit newUserSelected();
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE void UserModel::login(const int &id)
|
|
||||||
{
|
|
||||||
_users[id]->login();
|
|
||||||
emit refreshCurrentUserGui();
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE void UserModel::logout(const int &id)
|
|
||||||
{
|
|
||||||
_users[id]->logout();
|
|
||||||
emit refreshCurrentUserGui();
|
|
||||||
}
|
|
||||||
|
|
||||||
Q_INVOKABLE void UserModel::removeAccount(const int &id)
|
|
||||||
{
|
|
||||||
QMessageBox messageBox(QMessageBox::Question,
|
|
||||||
tr("Confirm Account Removal"),
|
|
||||||
tr("<p>Do you really want to remove the connection to the account <i>%1</i>?</p>"
|
|
||||||
"<p><b>Note:</b> This will <b>not</b> delete any files.</p>")
|
|
||||||
.arg(_users[id]->name()),
|
|
||||||
QMessageBox::NoButton);
|
|
||||||
QPushButton *yesButton =
|
|
||||||
messageBox.addButton(tr("Remove connection"), QMessageBox::YesRole);
|
|
||||||
messageBox.addButton(tr("Cancel"), QMessageBox::NoRole);
|
|
||||||
|
|
||||||
messageBox.exec();
|
|
||||||
if (messageBox.clickedButton() != yesButton) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_users[id]->isCurrentUser() && _users.count() > 1) {
|
|
||||||
id == 0 ? switchCurrentUser(1) : switchCurrentUser(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
_users[id]->logout();
|
|
||||||
_users[id]->removeAccount();
|
|
||||||
|
|
||||||
beginRemoveRows(QModelIndex(), id, id);
|
|
||||||
_users.removeAt(id);
|
|
||||||
endRemoveRows();
|
|
||||||
|
|
||||||
emit refreshCurrentUserGui();
|
|
||||||
}
|
|
||||||
|
|
||||||
int UserModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
Q_UNUSED(parent);
|
|
||||||
return _users.count();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant UserModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (index.row() < 0 || index.row() >= _users.count()) {
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == NameRole) {
|
|
||||||
return _users[index.row()]->name();
|
|
||||||
} else if (role == ServerRole) {
|
|
||||||
return _users[index.row()]->server();
|
|
||||||
} else if (role == AvatarRole) {
|
|
||||||
return _users[index.row()]->avatar();
|
|
||||||
} else if (role == IsCurrentUserRole) {
|
|
||||||
return _users[index.row()]->isCurrentUser();
|
|
||||||
} else if (role == IsConnectedRole) {
|
|
||||||
return _users[index.row()]->isConnected();
|
|
||||||
} else if (role == IdRole) {
|
|
||||||
return index.row();
|
|
||||||
}
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> UserModel::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> roles;
|
|
||||||
roles[NameRole] = "name";
|
|
||||||
roles[ServerRole] = "server";
|
|
||||||
roles[AvatarRole] = "avatar";
|
|
||||||
roles[IsCurrentUserRole] = "isCurrentUser";
|
|
||||||
roles[IsConnectedRole] = "isConnected";
|
|
||||||
roles[IdRole] = "id";
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
ActivityListModel *UserModel::currentActivityModel()
|
|
||||||
{
|
|
||||||
return _users[currentUserIndex()]->getActivityModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UserModel::currentUserHasActivities()
|
|
||||||
{
|
|
||||||
return _users[currentUserIndex()]->hasActivities();
|
|
||||||
}
|
|
||||||
|
|
||||||
void UserModel::fetchCurrentActivityModel()
|
|
||||||
{
|
|
||||||
_users[currentUserId()]->slotRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
AccountAppList UserModel::appList() const
|
|
||||||
{
|
|
||||||
if (_users.count() > 0) {
|
|
||||||
return _users[_currentUserId]->appList();
|
|
||||||
} else {
|
|
||||||
return AccountAppList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
ImageProvider::ImageProvider()
|
|
||||||
: QQuickImageProvider(QQuickImageProvider::Image)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage ImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
|
|
||||||
{
|
|
||||||
Q_UNUSED(size)
|
|
||||||
Q_UNUSED(requestedSize)
|
|
||||||
|
|
||||||
if (id == "currentUser") {
|
|
||||||
return UserModel::instance()->currentUserAvatar();
|
|
||||||
} else {
|
|
||||||
int uid = id.toInt();
|
|
||||||
return UserModel::instance()->avatarById(uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
UserAppsModel *UserAppsModel::_instance = nullptr;
|
|
||||||
|
|
||||||
UserAppsModel *UserAppsModel::instance()
|
|
||||||
{
|
|
||||||
if (_instance == nullptr) {
|
|
||||||
_instance = new UserAppsModel();
|
|
||||||
}
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAppsModel::UserAppsModel(QObject *parent)
|
|
||||||
: QAbstractListModel(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void UserAppsModel::buildAppList()
|
|
||||||
{
|
|
||||||
if (rowCount() > 0) {
|
|
||||||
beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
|
|
||||||
_apps.clear();
|
|
||||||
endRemoveRows();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(UserModel::instance()->appList().count() > 0) {
|
|
||||||
foreach(AccountApp *app, UserModel::instance()->appList()) {
|
|
||||||
// Filter out Talk because we have a dedicated button for it
|
|
||||||
if(app->id() == QLatin1String("spreed"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
|
||||||
_apps << app;
|
|
||||||
endInsertRows();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void UserAppsModel::openAppUrl(const QUrl &url)
|
|
||||||
{
|
|
||||||
QDesktopServices::openUrl(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
int UserAppsModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
Q_UNUSED(parent);
|
|
||||||
return _apps.count();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant UserAppsModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (index.row() < 0 || index.row() >= _apps.count()) {
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == NameRole) {
|
|
||||||
return _apps[index.row()]->name();
|
|
||||||
} else if (role == UrlRole) {
|
|
||||||
return _apps[index.row()]->url();
|
|
||||||
} else if (role == IconUrlRole) {
|
|
||||||
return _apps[index.row()]->iconUrl().toString();
|
|
||||||
}
|
|
||||||
return QVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> UserAppsModel::roleNames() const
|
|
||||||
{
|
|
||||||
QHash<int, QByteArray> roles;
|
|
||||||
roles[NameRole] = "appName";
|
|
||||||
roles[UrlRole] = "appUrl";
|
|
||||||
roles[IconUrlRole] = "appIconUrl";
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
#ifndef USERMODEL_H
|
|
||||||
#define USERMODEL_H
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
#include <QImage>
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QStringList>
|
|
||||||
#include <QQuickImageProvider>
|
|
||||||
|
|
||||||
#include "ActivityListModel.h"
|
|
||||||
#include "accountmanager.h"
|
|
||||||
#include "folderman.h"
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
namespace OCC {
|
|
||||||
|
|
||||||
class User : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
User(AccountStatePtr &account, const bool &isCurrent = false, QObject* parent = 0);
|
|
||||||
|
|
||||||
AccountPtr account() const;
|
|
||||||
|
|
||||||
bool isConnected() const;
|
|
||||||
bool isCurrentUser() const;
|
|
||||||
void setCurrentUser(const bool &isCurrent);
|
|
||||||
Folder *getFolder();
|
|
||||||
ActivityListModel *getActivityModel();
|
|
||||||
void openLocalFolder();
|
|
||||||
QString name() const;
|
|
||||||
QString server(bool shortened = true) const;
|
|
||||||
bool serverHasTalk() const;
|
|
||||||
bool hasActivities() const;
|
|
||||||
AccountAppList appList() const;
|
|
||||||
QImage avatar(bool whiteBg = false) const;
|
|
||||||
QString id() const;
|
|
||||||
void login() const;
|
|
||||||
void logout() const;
|
|
||||||
void removeAccount() const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void guiLog(const QString &, const QString &);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item);
|
|
||||||
void slotProgressInfo(const QString &folder, const ProgressInfo &progress);
|
|
||||||
void slotAddError(const QString &folderAlias, const QString &message, ErrorCategory category);
|
|
||||||
void slotNotificationRequestFinished(int statusCode);
|
|
||||||
void slotNotifyNetworkError(QNetworkReply *reply);
|
|
||||||
void slotEndNotificationRequest(int replyCode);
|
|
||||||
void slotNotifyServerFinished(const QString &reply, int replyCode);
|
|
||||||
void slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row);
|
|
||||||
void slotBuildNotificationDisplay(const ActivityList &list);
|
|
||||||
void slotRefreshNotifications();
|
|
||||||
void slotRefreshActivities();
|
|
||||||
void slotRefresh();
|
|
||||||
void slotRefreshImmediately();
|
|
||||||
void setNotificationRefreshInterval(std::chrono::milliseconds interval);
|
|
||||||
void slotRebuildNavigationAppList();
|
|
||||||
|
|
||||||
private:
|
|
||||||
AccountStatePtr _account;
|
|
||||||
bool _isCurrentUser;
|
|
||||||
ActivityListModel *_activityModel;
|
|
||||||
ActivityList _blacklistedNotifications;
|
|
||||||
|
|
||||||
QTimer _notificationCheckTimer;
|
|
||||||
QHash<AccountState *, QElapsedTimer> _timeSinceLastCheck;
|
|
||||||
|
|
||||||
QElapsedTimer _guiLogTimer;
|
|
||||||
QSet<int> _guiLoggedNotifications;
|
|
||||||
|
|
||||||
// number of currently running notification requests. If non zero,
|
|
||||||
// no query for notifications is started.
|
|
||||||
int _notificationRequestsRunning;
|
|
||||||
};
|
|
||||||
|
|
||||||
class UserModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
static UserModel *instance();
|
|
||||||
virtual ~UserModel() {};
|
|
||||||
|
|
||||||
void addUser(AccountStatePtr &user, const bool &isCurrent = false);
|
|
||||||
int currentUserIndex();
|
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
|
||||||
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
|
||||||
|
|
||||||
QImage avatarById(const int &id);
|
|
||||||
|
|
||||||
Q_INVOKABLE void fetchCurrentActivityModel();
|
|
||||||
Q_INVOKABLE void openCurrentAccountLocalFolder();
|
|
||||||
Q_INVOKABLE void openCurrentAccountTalk();
|
|
||||||
Q_INVOKABLE void openCurrentAccountServer();
|
|
||||||
Q_INVOKABLE QImage currentUserAvatar();
|
|
||||||
Q_INVOKABLE int numUsers();
|
|
||||||
Q_INVOKABLE QString currentUserName();
|
|
||||||
Q_INVOKABLE QString currentUserServer();
|
|
||||||
Q_INVOKABLE bool currentUserHasActivities();
|
|
||||||
Q_INVOKABLE bool currentServerHasTalk();
|
|
||||||
Q_INVOKABLE int currentUserId();
|
|
||||||
Q_INVOKABLE bool isUserConnected(const int &id);
|
|
||||||
Q_INVOKABLE void switchCurrentUser(const int &id);
|
|
||||||
Q_INVOKABLE void login(const int &id);
|
|
||||||
Q_INVOKABLE void logout(const int &id);
|
|
||||||
Q_INVOKABLE void removeAccount(const int &id);
|
|
||||||
|
|
||||||
ActivityListModel *currentActivityModel();
|
|
||||||
|
|
||||||
enum UserRoles {
|
|
||||||
NameRole = Qt::UserRole + 1,
|
|
||||||
ServerRole,
|
|
||||||
AvatarRole,
|
|
||||||
IsCurrentUserRole,
|
|
||||||
IsConnectedRole,
|
|
||||||
IdRole
|
|
||||||
};
|
|
||||||
|
|
||||||
AccountAppList appList() const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
Q_INVOKABLE void addAccount();
|
|
||||||
Q_INVOKABLE void refreshCurrentUserGui();
|
|
||||||
Q_INVOKABLE void newUserSelected();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static UserModel *_instance;
|
|
||||||
UserModel(QObject *parent = 0);
|
|
||||||
QList<User*> _users;
|
|
||||||
int _currentUserId;
|
|
||||||
bool _init = true;
|
|
||||||
|
|
||||||
void buildUserList();
|
|
||||||
};
|
|
||||||
|
|
||||||
class ImageProvider : public QQuickImageProvider
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ImageProvider();
|
|
||||||
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
class UserAppsModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
static UserAppsModel *instance();
|
|
||||||
virtual ~UserAppsModel() {};
|
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
|
||||||
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
|
|
||||||
|
|
||||||
enum UserAppsRoles {
|
|
||||||
NameRole = Qt::UserRole + 1,
|
|
||||||
UrlRole,
|
|
||||||
IconUrlRole
|
|
||||||
};
|
|
||||||
|
|
||||||
void buildAppList();
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void openAppUrl(const QUrl &url);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static UserAppsModel *_instance;
|
|
||||||
UserAppsModel(QObject *parent = 0);
|
|
||||||
|
|
||||||
AccountAppList _apps;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
#endif // USERMODEL_H
|
|
||||||
@@ -1,612 +0,0 @@
|
|||||||
import QtQml 2.1
|
|
||||||
import QtQml.Models 2.1
|
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Window 2.2
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtQuick.Layouts 1.2
|
|
||||||
import QtGraphicalEffects 1.0
|
|
||||||
|
|
||||||
Window {
|
|
||||||
|
|
||||||
id: trayWindow
|
|
||||||
visible: true
|
|
||||||
width: 400
|
|
||||||
height: 510
|
|
||||||
color: "transparent"
|
|
||||||
flags: Qt.FramelessWindowHint
|
|
||||||
|
|
||||||
onActiveChanged: {
|
|
||||||
if(!active) {
|
|
||||||
trayWindow.hide();
|
|
||||||
systrayBackend.setClosed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
|
||||||
currentAccountAvatar.source = ""
|
|
||||||
currentAccountAvatar.source = "image://avatars/currentUser"
|
|
||||||
currentAccountUser.text = userModelBackend.currentUserName();
|
|
||||||
currentAccountServer.text = userModelBackend.currentUserServer();
|
|
||||||
trayWindowTalkButton.visible = userModelBackend.currentServerHasTalk() ? true : false;
|
|
||||||
currentAccountStateIndicator.source = ""
|
|
||||||
currentAccountStateIndicator.source = userModelBackend.isUserConnected(userModelBackend.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg"
|
|
||||||
|
|
||||||
userLineInstantiator.active = false;
|
|
||||||
userLineInstantiator.active = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: userModelBackend
|
|
||||||
onRefreshCurrentUserGui: {
|
|
||||||
currentAccountAvatar.source = ""
|
|
||||||
currentAccountAvatar.source = "image://avatars/currentUser"
|
|
||||||
currentAccountUser.text = userModelBackend.currentUserName();
|
|
||||||
currentAccountServer.text = userModelBackend.currentUserServer();
|
|
||||||
currentAccountStateIndicator.source = ""
|
|
||||||
currentAccountStateIndicator.source = userModelBackend.isUserConnected(userModelBackend.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg"
|
|
||||||
}
|
|
||||||
onNewUserSelected: {
|
|
||||||
accountMenu.close();
|
|
||||||
trayWindowTalkButton.visible = userModelBackend.currentServerHasTalk() ? true : false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: systrayBackend
|
|
||||||
onShowWindow: {
|
|
||||||
accountMenu.close();
|
|
||||||
trayWindow.show();
|
|
||||||
trayWindow.raise();
|
|
||||||
trayWindow.requestActivate();
|
|
||||||
trayWindow.setX( systrayBackend.calcTrayWindowX());
|
|
||||||
trayWindow.setY( systrayBackend.calcTrayWindowY());
|
|
||||||
systrayBackend.setOpened();
|
|
||||||
userModelBackend.fetchCurrentActivityModel();
|
|
||||||
}
|
|
||||||
onHideWindow: {
|
|
||||||
trayWindow.hide();
|
|
||||||
systrayBackend.setClosed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: trayWindowBackground
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: 10
|
|
||||||
border.color: "#0082c9"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: trayWindowHeaderBackground
|
|
||||||
anchors.left: trayWindowBackground.left
|
|
||||||
anchors.top: trayWindowBackground.top
|
|
||||||
height: 60
|
|
||||||
width: parent.width
|
|
||||||
radius: 9
|
|
||||||
color: "#0082c9"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.left: trayWindowHeaderBackground.left
|
|
||||||
anchors.bottom: trayWindowHeaderBackground.bottom
|
|
||||||
height: 30
|
|
||||||
width: parent.width
|
|
||||||
color: "#0082c9"
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: trayWindowHeaderLayout
|
|
||||||
spacing: 0
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: currentAccountButton
|
|
||||||
Layout.preferredWidth: 220
|
|
||||||
Layout.preferredHeight: (trayWindowHeaderBackground.height)
|
|
||||||
display: AbstractButton.IconOnly
|
|
||||||
flat: true
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: accountBtnMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onContainsMouseChanged: {
|
|
||||||
currentAccountStateIndicatorBackground.color = (containsMouse ? "#009dd9" : "#0082c9")
|
|
||||||
}
|
|
||||||
onClicked:
|
|
||||||
{
|
|
||||||
syncPauseButton.text = systrayBackend.syncIsPaused() ? qsTr("Resume sync for all") : qsTr("Pause sync for all")
|
|
||||||
accountMenu.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
Menu {
|
|
||||||
id: accountMenu
|
|
||||||
x: (currentAccountButton.x + 2)
|
|
||||||
y: (currentAccountButton.y + currentAccountButton.height + 2)
|
|
||||||
width: (currentAccountButton.width - 2)
|
|
||||||
closePolicy: "CloseOnPressOutside"
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
border.color: "#0082c9"
|
|
||||||
radius: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
onClosed: {
|
|
||||||
userLineInstantiator.active = false;
|
|
||||||
userLineInstantiator.active = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Instantiator {
|
|
||||||
id: userLineInstantiator
|
|
||||||
model: userModelBackend
|
|
||||||
delegate: UserLine {}
|
|
||||||
onObjectAdded: accountMenu.insertItem(index, object)
|
|
||||||
onObjectRemoved: accountMenu.removeItem(object)
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
id: addAccountButton
|
|
||||||
height: 50
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
width: addAccountButton.width
|
|
||||||
height: addAccountButton.height
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Image {
|
|
||||||
Layout.leftMargin: 14
|
|
||||||
verticalAlignment: Qt.AlignCenter
|
|
||||||
source: "qrc:///client/theme/black/add.svg"
|
|
||||||
sourceSize.width: openLocalFolderButton.icon.width
|
|
||||||
sourceSize.height: openLocalFolderButton.icon.height
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
Layout.leftMargin: 14
|
|
||||||
text: qsTr("Add account")
|
|
||||||
color: "black"
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onClicked: userModelBackend.addAccount()
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuSeparator { id: accountMenuSeparator }
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
id: syncPauseButton
|
|
||||||
font.pixelSize: 12
|
|
||||||
onClicked: systrayBackend.pauseResumeSync()
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: qsTr("Open settings")
|
|
||||||
font.pixelSize: 12
|
|
||||||
onClicked: systrayBackend.openSettings()
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: qsTr("Help")
|
|
||||||
font.pixelSize: 12
|
|
||||||
onClicked: systrayBackend.openHelp()
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: qsTr("Quit Nextcloud")
|
|
||||||
font.pixelSize: 12
|
|
||||||
onClicked: systrayBackend.shutdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background:
|
|
||||||
Item {
|
|
||||||
id: leftHoverContainer
|
|
||||||
height: currentAccountButton.height
|
|
||||||
width: currentAccountButton.width
|
|
||||||
Rectangle {
|
|
||||||
width: currentAccountButton.width / 2
|
|
||||||
height: currentAccountButton.height / 2
|
|
||||||
color: "transparent"
|
|
||||||
clip: true
|
|
||||||
Rectangle {
|
|
||||||
width: currentAccountButton.width
|
|
||||||
height: currentAccountButton.height
|
|
||||||
radius: 10
|
|
||||||
color: "white"
|
|
||||||
opacity: 0.2
|
|
||||||
visible: accountBtnMouseArea.containsMouse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
width: currentAccountButton.width / 2
|
|
||||||
height: currentAccountButton.height / 2
|
|
||||||
anchors.bottom: leftHoverContainer.bottom
|
|
||||||
color: "white"
|
|
||||||
opacity: 0.2
|
|
||||||
visible: accountBtnMouseArea.containsMouse
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
width: currentAccountButton.width / 2
|
|
||||||
height: currentAccountButton.height / 2
|
|
||||||
anchors.right: leftHoverContainer.right
|
|
||||||
color: "white"
|
|
||||||
opacity: 0.2
|
|
||||||
visible: accountBtnMouseArea.containsMouse
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
width: currentAccountButton.width / 2
|
|
||||||
height: currentAccountButton.height / 2
|
|
||||||
anchors.right: leftHoverContainer.right
|
|
||||||
anchors.bottom: leftHoverContainer.bottom
|
|
||||||
color: "white"
|
|
||||||
opacity: 0.2
|
|
||||||
visible: accountBtnMouseArea.containsMouse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: accountControlRowLayout
|
|
||||||
height: currentAccountButton.height
|
|
||||||
width: currentAccountButton.width
|
|
||||||
spacing: 0
|
|
||||||
Image {
|
|
||||||
id: currentAccountAvatar
|
|
||||||
Layout.leftMargin: 8
|
|
||||||
verticalAlignment: Qt.AlignCenter
|
|
||||||
cache: false
|
|
||||||
source: "image://avatars/currentUser"
|
|
||||||
Layout.preferredHeight: (trayWindowHeaderBackground.height -16)
|
|
||||||
Layout.preferredWidth: (trayWindowHeaderBackground.height -16)
|
|
||||||
Rectangle {
|
|
||||||
id: currentAccountStateIndicatorBackground
|
|
||||||
width: currentAccountStateIndicator.sourceSize.width + 2
|
|
||||||
height: width
|
|
||||||
anchors.bottom: currentAccountAvatar.bottom
|
|
||||||
anchors.right: currentAccountAvatar.right
|
|
||||||
color: "#0082c9"
|
|
||||||
radius: width*0.5
|
|
||||||
}
|
|
||||||
Image {
|
|
||||||
id: currentAccountStateIndicator
|
|
||||||
source: userModelBackend.isUserConnected(userModelBackend.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg"
|
|
||||||
cache: false
|
|
||||||
x: currentAccountStateIndicatorBackground.x + 1
|
|
||||||
y: currentAccountStateIndicatorBackground.y + 1
|
|
||||||
sourceSize.width: 16
|
|
||||||
sourceSize.height: 16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: accountLabels
|
|
||||||
spacing: 4
|
|
||||||
Layout.alignment: Qt.AlignLeft
|
|
||||||
Layout.leftMargin: 6
|
|
||||||
Label {
|
|
||||||
id: currentAccountUser
|
|
||||||
width: 128
|
|
||||||
text: userModelBackend.currentUserName()
|
|
||||||
elide: Text.ElideRight
|
|
||||||
color: "white"
|
|
||||||
font.pixelSize: 12
|
|
||||||
font.bold: true
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
id: currentAccountServer
|
|
||||||
width: 128
|
|
||||||
text: userModelBackend.currentUserServer()
|
|
||||||
elide: Text.ElideRight
|
|
||||||
color: "white"
|
|
||||||
font.pixelSize: 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
verticalAlignment: Qt.AlignCenter
|
|
||||||
Layout.margins: 8
|
|
||||||
source: "qrc:///client/theme/white/caret-down.svg"
|
|
||||||
sourceSize.width: 20
|
|
||||||
sourceSize.height: 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: trayWindowHeaderSpacer
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: openLocalFolderButton
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
display: AbstractButton.IconOnly
|
|
||||||
Layout.preferredWidth: (trayWindowHeaderBackground.height)
|
|
||||||
Layout.preferredHeight: (trayWindowHeaderBackground.height)
|
|
||||||
flat: true
|
|
||||||
|
|
||||||
icon.source: "qrc:///client/theme/white/folder.svg"
|
|
||||||
icon.color: "transparent"
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: folderBtnMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked:
|
|
||||||
{
|
|
||||||
userModelBackend.openCurrentAccountLocalFolder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background:
|
|
||||||
Rectangle {
|
|
||||||
color: folderBtnMouseArea.containsMouse ? "white" : "transparent"
|
|
||||||
opacity: 0.2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: trayWindowTalkButton
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
display: AbstractButton.IconOnly
|
|
||||||
Layout.preferredWidth: (trayWindowHeaderBackground.height)
|
|
||||||
Layout.preferredHeight: (trayWindowHeaderBackground.height)
|
|
||||||
flat: true
|
|
||||||
visible: userModelBackend.currentServerHasTalk() ? true : false
|
|
||||||
|
|
||||||
icon.source: "qrc:///client/theme/white/talk-app.svg"
|
|
||||||
icon.color: "transparent"
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: talkBtnMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked:
|
|
||||||
{
|
|
||||||
userModelBackend.openCurrentAccountTalk();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background:
|
|
||||||
Rectangle {
|
|
||||||
color: talkBtnMouseArea.containsMouse ? "white" : "transparent"
|
|
||||||
opacity: 0.2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: trayWindowAppsButton
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
display: AbstractButton.IconOnly
|
|
||||||
Layout.preferredWidth: (trayWindowHeaderBackground.height)
|
|
||||||
Layout.preferredHeight: (trayWindowHeaderBackground.height)
|
|
||||||
flat: true
|
|
||||||
|
|
||||||
icon.source: "qrc:///client/theme/white/more-apps.svg"
|
|
||||||
icon.color: "transparent"
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: appsBtnMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
onClicked:
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
// The count() property was introduced in QtQuick.Controls 2.3 (Qt 5.10)
|
|
||||||
// so we handle this with userModelBackend.openCurrentAccountServer()
|
|
||||||
//
|
|
||||||
// See UserModel::openCurrentAccountServer() to disable this workaround
|
|
||||||
// in the future for Qt >= 5.10
|
|
||||||
|
|
||||||
if(appsMenu.count() > 0) {
|
|
||||||
appsMenu.popup();
|
|
||||||
} else {
|
|
||||||
userModelBackend.openCurrentAccountServer();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
appsMenu.open();
|
|
||||||
userModelBackend.openCurrentAccountServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
Menu {
|
|
||||||
id: appsMenu
|
|
||||||
y: (trayWindowAppsButton.y + trayWindowAppsButton.height + 2)
|
|
||||||
width: (trayWindowAppsButton.width * 3)
|
|
||||||
closePolicy: "CloseOnPressOutside"
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
border.color: "#0082c9"
|
|
||||||
radius: 2
|
|
||||||
}
|
|
||||||
|
|
||||||
Instantiator {
|
|
||||||
id: appsMenuInstantiator
|
|
||||||
model: appsMenuModelBackend
|
|
||||||
onObjectAdded: appsMenu.insertItem(index, object)
|
|
||||||
onObjectRemoved: appsMenu.removeItem(object)
|
|
||||||
delegate: MenuItem {
|
|
||||||
text: appName
|
|
||||||
font.pixelSize: 12
|
|
||||||
icon.source: appIconUrl
|
|
||||||
onTriggered: appsMenuModelBackend.openAppUrl(appUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background:
|
|
||||||
Item {
|
|
||||||
id: rightHoverContainer
|
|
||||||
height: trayWindowAppsButton.height
|
|
||||||
width: trayWindowAppsButton.width
|
|
||||||
Rectangle {
|
|
||||||
width: trayWindowAppsButton.width / 2
|
|
||||||
height: trayWindowAppsButton.height / 2
|
|
||||||
color: "white"
|
|
||||||
opacity: 0.2
|
|
||||||
visible: appsBtnMouseArea.containsMouse
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
width: trayWindowAppsButton.width / 2
|
|
||||||
height: trayWindowAppsButton.height / 2
|
|
||||||
anchors.bottom: rightHoverContainer.bottom
|
|
||||||
color: "white"
|
|
||||||
opacity: 0.2
|
|
||||||
visible: appsBtnMouseArea.containsMouse
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
width: trayWindowAppsButton.width / 2
|
|
||||||
height: trayWindowAppsButton.height / 2
|
|
||||||
anchors.bottom: rightHoverContainer.bottom
|
|
||||||
anchors.right: rightHoverContainer.right
|
|
||||||
color: "white"
|
|
||||||
opacity: 0.2
|
|
||||||
visible: appsBtnMouseArea.containsMouse
|
|
||||||
}
|
|
||||||
Rectangle {
|
|
||||||
id: rightHoverContainerClipper
|
|
||||||
anchors.right: rightHoverContainer.right
|
|
||||||
width: trayWindowAppsButton.width / 2
|
|
||||||
height: trayWindowAppsButton.height / 2
|
|
||||||
color: "transparent"
|
|
||||||
clip: true
|
|
||||||
Rectangle {
|
|
||||||
width: trayWindowAppsButton.width
|
|
||||||
height: trayWindowAppsButton.height
|
|
||||||
anchors.right: rightHoverContainerClipper.right
|
|
||||||
radius: 10
|
|
||||||
color: "white"
|
|
||||||
opacity: 0.2
|
|
||||||
visible: appsBtnMouseArea.containsMouse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // Rectangle trayWindowHeaderBackground
|
|
||||||
|
|
||||||
ListView {
|
|
||||||
id: activityListView
|
|
||||||
anchors.top: trayWindowHeaderBackground.bottom
|
|
||||||
width: trayWindowBackground.width
|
|
||||||
height: trayWindowBackground.height - trayWindowHeaderBackground.height
|
|
||||||
clip: true
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
|
||||||
id: listViewScrollbar
|
|
||||||
}
|
|
||||||
|
|
||||||
model: activityModel
|
|
||||||
|
|
||||||
delegate: RowLayout {
|
|
||||||
id: activityItem
|
|
||||||
width: activityListView.width
|
|
||||||
height: trayWindowHeaderLayout.height
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Image {
|
|
||||||
id: activityIcon
|
|
||||||
Layout.leftMargin: 8
|
|
||||||
Layout.rightMargin: 8
|
|
||||||
Layout.preferredWidth: activityButton1.icon.width
|
|
||||||
Layout.preferredHeight: activityButton1.icon.height
|
|
||||||
verticalAlignment: Qt.AlignCenter
|
|
||||||
cache: true
|
|
||||||
source: icon
|
|
||||||
sourceSize.height: 64
|
|
||||||
sourceSize.width: 64
|
|
||||||
}
|
|
||||||
Column {
|
|
||||||
id: activityTextColumn
|
|
||||||
spacing: 4
|
|
||||||
Layout.alignment: Qt.AlignLeft
|
|
||||||
Text {
|
|
||||||
id: activityTextTitle
|
|
||||||
text: (type === "Activity" || type === "Notification") ? subject : message
|
|
||||||
width: 240 + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.pixelSize: 12
|
|
||||||
color: activityTextTitleColor
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: activityTextInfo
|
|
||||||
text: (type === "Activity" || type === "File" || type === "Sync") ? displaypath : message
|
|
||||||
height: (text === "") ? 0 : activityTextTitle.height
|
|
||||||
width: 240 + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.pixelSize: 10
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: activityTextDateTime
|
|
||||||
text: dateTime
|
|
||||||
height: (text === "") ? 0 : activityTextTitle.height
|
|
||||||
width: 240 + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8
|
|
||||||
elide: Text.ElideRight
|
|
||||||
font.pixelSize: 10
|
|
||||||
color: "#808080"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Item {
|
|
||||||
id: activityItemFiller
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
id: activityButton1
|
|
||||||
Layout.preferredWidth: (path === "") ? 0 : activityItem.height
|
|
||||||
Layout.preferredHeight: activityItem.height
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
flat: true
|
|
||||||
hoverEnabled: false
|
|
||||||
visible: (path === "") ? false : true
|
|
||||||
display: AbstractButton.IconOnly
|
|
||||||
icon.source: "qrc:///client/resources/files.svg"
|
|
||||||
icon.color: "transparent"
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
Qt.openUrlExternally(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
id: activityButton2
|
|
||||||
Layout.preferredWidth: (link === "") ? 0 : activityItem.height
|
|
||||||
Layout.preferredHeight: activityItem.height
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
flat: true
|
|
||||||
hoverEnabled: false
|
|
||||||
visible: (link === "") ? false : true
|
|
||||||
display: AbstractButton.IconOnly
|
|
||||||
icon.source: "qrc:///client/resources/public.svg"
|
|
||||||
icon.color: "transparent"
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
Qt.openUrlExternally(link)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*add: Transition {
|
|
||||||
NumberAnimation { properties: "y"; from: -60; duration: 100; easing.type: Easing.Linear }
|
|
||||||
}
|
|
||||||
|
|
||||||
remove: Transition {
|
|
||||||
NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 100 }
|
|
||||||
}
|
|
||||||
|
|
||||||
removeDisplaced: Transition {
|
|
||||||
SequentialAnimation {
|
|
||||||
PauseAnimation { duration: 100}
|
|
||||||
NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
displaced: Transition {
|
|
||||||
NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear }
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
} // Rectangle trayWindowBackground
|
|
||||||
}
|
|
||||||
@@ -84,7 +84,6 @@ OwncloudSetupPage::OwncloudSetupPage(QWidget *parent)
|
|||||||
_ui.slideShow->addSlide(Theme::hidpiFileName(":/client/theme/colored/wizard-files.png"), tr("Secure collaboration & file exchange"));
|
_ui.slideShow->addSlide(Theme::hidpiFileName(":/client/theme/colored/wizard-files.png"), tr("Secure collaboration & file exchange"));
|
||||||
_ui.slideShow->addSlide(Theme::hidpiFileName(":/client/theme/colored/wizard-groupware.png"), tr("Easy-to-use web mail, calendaring & contacts"));
|
_ui.slideShow->addSlide(Theme::hidpiFileName(":/client/theme/colored/wizard-groupware.png"), tr("Easy-to-use web mail, calendaring & contacts"));
|
||||||
_ui.slideShow->addSlide(Theme::hidpiFileName(":/client/theme/colored/wizard-talk.png"), tr("Screensharing, online meetings & web conferences"));
|
_ui.slideShow->addSlide(Theme::hidpiFileName(":/client/theme/colored/wizard-talk.png"), tr("Screensharing, online meetings & web conferences"));
|
||||||
|
|
||||||
connect(_ui.slideShow, &SlideShow::clicked, _ui.slideShow, &SlideShow::stopShow);
|
connect(_ui.slideShow, &SlideShow::clicked, _ui.slideShow, &SlideShow::stopShow);
|
||||||
connect(_ui.nextButton, &QPushButton::clicked, _ui.slideShow, &SlideShow::nextSlide);
|
connect(_ui.nextButton, &QPushButton::clicked, _ui.slideShow, &SlideShow::nextSlide);
|
||||||
connect(_ui.prevButton, &QPushButton::clicked, _ui.slideShow, &SlideShow::prevSlide);
|
connect(_ui.prevButton, &QPushButton::clicked, _ui.slideShow, &SlideShow::prevSlide);
|
||||||
|
|||||||
@@ -35,9 +35,6 @@
|
|||||||
#include <QSslKey>
|
#include <QSslKey>
|
||||||
#include <QAuthenticator>
|
#include <QAuthenticator>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QJsonArray>
|
|
||||||
|
|
||||||
#include <keychain.h>
|
#include <keychain.h>
|
||||||
#include "creds/abstractcredentials.h"
|
#include "creds/abstractcredentials.h"
|
||||||
@@ -603,49 +600,4 @@ void Account::deleteAppPassword(){
|
|||||||
job->start();
|
job->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Account::fetchDirectEditors(const QUrl &directEditingURL, const QString &directEditingETag)
|
|
||||||
{
|
|
||||||
if(directEditingURL.isEmpty() || directEditingETag.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Check for the directEditing capability
|
|
||||||
if (!directEditingURL.isEmpty() &&
|
|
||||||
(directEditingETag.isEmpty() || directEditingETag != _lastDirectEditingETag)) {
|
|
||||||
// Fetch the available editors and their mime types
|
|
||||||
JsonApiJob *job = new JsonApiJob(sharedFromThis(), QLatin1String("ocs/v2.php/apps/files/api/v1/directEditing"), this);
|
|
||||||
QObject::connect(job, &JsonApiJob::jsonReceived, this, &Account::slotDirectEditingRecieved);
|
|
||||||
job->start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Account::slotDirectEditingRecieved(const QJsonDocument &json)
|
|
||||||
{
|
|
||||||
auto data = json.object().value("ocs").toObject().value("data").toObject();
|
|
||||||
auto editors = data.value("editors").toObject();
|
|
||||||
|
|
||||||
foreach (auto editorKey, editors.keys()) {
|
|
||||||
auto editor = editors.value(editorKey).toObject();
|
|
||||||
|
|
||||||
const QString id = editor.value("id").toString();
|
|
||||||
const QString name = editor.value("name").toString();
|
|
||||||
|
|
||||||
if(!id.isEmpty() && !name.isEmpty()) {
|
|
||||||
auto mimeTypes = editor.value("mimetypes").toArray();
|
|
||||||
auto optionalMimeTypes = editor.value("optionalMimetypes").toArray();
|
|
||||||
|
|
||||||
DirectEditor *directEditor = new DirectEditor(id, name);
|
|
||||||
|
|
||||||
foreach(auto mimeType, mimeTypes) {
|
|
||||||
directEditor->addMimetype(mimeType.toString().toLatin1());
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(auto optionalMimeType, optionalMimeTypes) {
|
|
||||||
directEditor->addOptionalMimetype(optionalMimeType.toString().toLatin1());
|
|
||||||
}
|
|
||||||
|
|
||||||
_capabilities.addDirectEditor(directEditor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|||||||
@@ -246,10 +246,6 @@ public:
|
|||||||
void writeAppPasswordOnce(QString appPassword);
|
void writeAppPasswordOnce(QString appPassword);
|
||||||
void deleteAppPassword();
|
void deleteAppPassword();
|
||||||
|
|
||||||
/// Direct Editing
|
|
||||||
// Check for the directEditing capability
|
|
||||||
void fetchDirectEditors(const QUrl &directEditingURL, const QString &directEditingETag);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/// Used when forgetting credentials
|
/// Used when forgetting credentials
|
||||||
void clearQNAMCache();
|
void clearQNAMCache();
|
||||||
@@ -282,7 +278,6 @@ signals:
|
|||||||
protected Q_SLOTS:
|
protected Q_SLOTS:
|
||||||
void slotCredentialsFetched();
|
void slotCredentialsFetched();
|
||||||
void slotCredentialsAsked();
|
void slotCredentialsAsked();
|
||||||
void slotDirectEditingRecieved(const QJsonDocument &json);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Account(QObject *parent = nullptr);
|
Account(QObject *parent = nullptr);
|
||||||
@@ -329,9 +324,6 @@ private:
|
|||||||
|
|
||||||
friend class AccountManager;
|
friend class AccountManager;
|
||||||
|
|
||||||
// Direct Editing
|
|
||||||
QString _lastDirectEditingETag;
|
|
||||||
|
|
||||||
/* IMPORTANT - remove later - FIXME MS@2019-12-07 -->
|
/* IMPORTANT - remove later - FIXME MS@2019-12-07 -->
|
||||||
* TODO: For "Log out" & "Remove account": Remove client CA certs and KEY!
|
* TODO: For "Log out" & "Remove account": Remove client CA certs and KEY!
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -103,8 +103,7 @@ bool Capabilities::isValid() const
|
|||||||
return !_capabilities.isEmpty();
|
return !_capabilities.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Capabilities::hasActivities() const
|
bool Capabilities::hasActivities() const {
|
||||||
{
|
|
||||||
return _capabilities.contains("activity");
|
return _capabilities.contains("activity");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,85 +175,4 @@ bool Capabilities::uploadConflictFiles() const
|
|||||||
|
|
||||||
return _capabilities["uploadConflictFiles"].toBool();
|
return _capabilities["uploadConflictFiles"].toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
// Direct Editing
|
|
||||||
void Capabilities::addDirectEditor(DirectEditor* directEditor)
|
|
||||||
{
|
|
||||||
if(directEditor)
|
|
||||||
_directEditors.append(directEditor);
|
|
||||||
}
|
|
||||||
|
|
||||||
DirectEditor* Capabilities::getDirectEditorForMimetype(const QMimeType &mimeType)
|
|
||||||
{
|
|
||||||
foreach(DirectEditor* editor, _directEditors) {
|
|
||||||
if(editor->hasMimetype(mimeType))
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
DirectEditor* Capabilities::getDirectEditorForOptionalMimetype(const QMimeType &mimeType)
|
|
||||||
{
|
|
||||||
foreach(DirectEditor* editor, _directEditors) {
|
|
||||||
if(editor->hasOptionalMimetype(mimeType))
|
|
||||||
return editor;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
DirectEditor::DirectEditor(const QString &id, const QString &name, QObject* parent)
|
|
||||||
: QObject(parent)
|
|
||||||
, _id(id)
|
|
||||||
, _name(name)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QString DirectEditor::id() const
|
|
||||||
{
|
|
||||||
return _id;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString DirectEditor::name() const
|
|
||||||
{
|
|
||||||
return _name;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DirectEditor::addMimetype(const QByteArray &mimeType)
|
|
||||||
{
|
|
||||||
_mimeTypes.append(mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DirectEditor::addOptionalMimetype(const QByteArray &mimeType)
|
|
||||||
{
|
|
||||||
_optionalMimeTypes.append(mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QByteArray> DirectEditor::mimeTypes() const
|
|
||||||
{
|
|
||||||
return _mimeTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QByteArray> DirectEditor::optionalMimeTypes() const
|
|
||||||
{
|
|
||||||
return _optionalMimeTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DirectEditor::hasMimetype(const QMimeType &mimeType)
|
|
||||||
{
|
|
||||||
return _mimeTypes.contains(mimeType.name().toLatin1());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DirectEditor::hasOptionalMimetype(const QMimeType &mimeType)
|
|
||||||
{
|
|
||||||
return _optionalMimeTypes.contains(mimeType.name().toLatin1());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,12 +20,9 @@
|
|||||||
|
|
||||||
#include <QVariantMap>
|
#include <QVariantMap>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QMimeDatabase>
|
|
||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
class DirectEditor;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The Capabilities class represents the capabilities of an ownCloud
|
* @brief The Capabilities class represents the capabilities of an ownCloud
|
||||||
* server
|
* server
|
||||||
@@ -130,47 +127,9 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool uploadConflictFiles() const;
|
bool uploadConflictFiles() const;
|
||||||
|
|
||||||
// Direct Editing
|
|
||||||
void addDirectEditor(DirectEditor* directEditor);
|
|
||||||
DirectEditor* getDirectEditorForMimetype(const QMimeType &mimeType);
|
|
||||||
DirectEditor* getDirectEditorForOptionalMimetype(const QMimeType &mimeType);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QVariantMap _capabilities;
|
QVariantMap _capabilities;
|
||||||
|
|
||||||
QList<DirectEditor*> _directEditors;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
class OWNCLOUDSYNC_EXPORT DirectEditor : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
DirectEditor(const QString &id, const QString &name, QObject* parent = 0);
|
|
||||||
|
|
||||||
void addMimetype(const QByteArray &mimeType);
|
|
||||||
void addOptionalMimetype(const QByteArray &mimeType);
|
|
||||||
|
|
||||||
bool hasMimetype(const QMimeType &mimeType);
|
|
||||||
bool hasOptionalMimetype(const QMimeType &mimeType);
|
|
||||||
|
|
||||||
QString id() const;
|
|
||||||
QString name() const;
|
|
||||||
|
|
||||||
QList<QByteArray> mimeTypes() const;
|
|
||||||
QList<QByteArray> optionalMimeTypes() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString _id;
|
|
||||||
QString _name;
|
|
||||||
|
|
||||||
QList<QByteArray> _mimeTypes;
|
|
||||||
QList<QByteArray> _optionalMimeTypes;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif //CAPABILITIES_H
|
#endif //CAPABILITIES_H
|
||||||
|
|||||||
@@ -384,14 +384,7 @@ void DiscoverySingleDirectoryJob::directoryListingIteratedSlot(QString file, con
|
|||||||
propertyMapToFileStat(map, file_stat.get());
|
propertyMapToFileStat(map, file_stat.get());
|
||||||
if (file_stat->type == ItemTypeDirectory)
|
if (file_stat->type == ItemTypeDirectory)
|
||||||
file_stat->size = 0;
|
file_stat->size = 0;
|
||||||
if (file_stat->remotePerm.hasPermission(RemotePermissions::IsShared) && file_stat->etag.isEmpty()) {
|
if (file_stat->type == ItemTypeSkip
|
||||||
/* Handle broken shared file error gracefully instead of stopping sync in the desktop client.
|
|
||||||
DO not set _error */
|
|
||||||
qCWarning(lcDiscovery)
|
|
||||||
<< "Missing path to a share :" << file << file_stat->path << file_stat->type << file_stat->size
|
|
||||||
<< file_stat->modtime << file_stat->remotePerm.toString()
|
|
||||||
<< file_stat->etag << file_stat->file_id;
|
|
||||||
} else if (file_stat->type == ItemTypeSkip
|
|
||||||
|| file_stat->size == -1
|
|| file_stat->size == -1
|
||||||
|| file_stat->remotePerm.isNull()
|
|| file_stat->remotePerm.isNull()
|
||||||
|| file_stat->etag.isEmpty()
|
|| file_stat->etag.isEmpty()
|
||||||
|
|||||||
@@ -801,7 +801,7 @@ void JsonApiJob::start()
|
|||||||
auto query = _additionalParams;
|
auto query = _additionalParams;
|
||||||
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
|
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
|
||||||
QUrl url = Utility::concatUrlPath(account()->url(), path(), query);
|
QUrl url = Utility::concatUrlPath(account()->url(), path(), query);
|
||||||
sendRequest(_usePOST ? "POST" : "GET", url, _request);
|
sendRequest("GET", url, _request);
|
||||||
AbstractNetworkJob::start();
|
AbstractNetworkJob::start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||