Compare commits
257 Commits
v3.4.0-rc1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b174d6182 | ||
|
|
bfed1f952c | ||
|
|
e5cae81f25 | ||
|
|
ae44dd5978 | ||
|
|
5ce8c9bf50 | ||
|
|
0df0d47c5e | ||
|
|
c21f02bead | ||
|
|
0776df65c3 | ||
|
|
375c7b585c | ||
|
|
0ea7e8d3a6 | ||
|
|
ceb13aa53c | ||
|
|
bb8ca05162 | ||
|
|
a2524763c8 | ||
|
|
fddd9dd884 | ||
|
|
ce5494b4ec | ||
|
|
a66bed5d0f | ||
|
|
566a5e5465 | ||
|
|
c78855b4c2 | ||
|
|
7a1eae45d3 | ||
|
|
e94802a338 | ||
|
|
c69cecceda | ||
|
|
7e43f29cd1 | ||
|
|
368d226357 | ||
|
|
9c98846c23 | ||
|
|
fe799e983b | ||
|
|
004e5e58a3 | ||
|
|
29d823571c | ||
|
|
308dd8e790 | ||
|
|
fbc89a3149 | ||
|
|
20a4ed6e7e | ||
|
|
b4d6c90c62 | ||
|
|
41a194952e | ||
|
|
624213956e | ||
|
|
d0364e697a | ||
|
|
1a3e42451c | ||
|
|
40021e6943 | ||
|
|
580148335f | ||
|
|
4e8a5d7aee | ||
|
|
45b2647cc9 | ||
|
|
aad028097b | ||
|
|
12610baeba | ||
|
|
f4bf4da44d | ||
|
|
99457e8c38 | ||
|
|
f21d064b4f | ||
|
|
94ec5396da | ||
|
|
610c2f9084 | ||
|
|
075f33a272 | ||
|
|
2a27882307 | ||
|
|
8b26251199 | ||
|
|
1e6d3d2c6b | ||
|
|
fd8186ff0e | ||
|
|
82aed71d09 | ||
|
|
c08a291742 | ||
|
|
a10f84b5c1 | ||
|
|
40d8434c8e | ||
|
|
5cec3c3ca1 | ||
|
|
907282cf89 | ||
|
|
3aaaaaf2d0 | ||
|
|
5ea53cdf80 | ||
|
|
5e651878ff | ||
|
|
961cea92ca | ||
|
|
040e740375 | ||
|
|
18be0e88dc | ||
|
|
c2af167933 | ||
|
|
39d1ca3a3c | ||
|
|
39e2292acd | ||
|
|
3d7fc166b4 | ||
|
|
4a8433d297 | ||
|
|
f2d075f3ca | ||
|
|
c872514571 | ||
|
|
66ba89a37d | ||
|
|
dbe8dc303f | ||
|
|
2967b3acca | ||
|
|
1d8478bc06 | ||
|
|
27cf2592de | ||
|
|
2e0ba8208b | ||
|
|
225753a8c0 | ||
|
|
190d278fd4 | ||
|
|
5b0e2d8ed0 | ||
|
|
6f5f3e769f | ||
|
|
b7be10f712 | ||
|
|
fbe35538b6 | ||
|
|
39faeffe09 | ||
|
|
e89dfe493e | ||
|
|
c88f57b42d | ||
|
|
9d7568aa4a | ||
|
|
ab66711513 | ||
|
|
e48879555b | ||
|
|
05f4a331bf | ||
|
|
a7c1f58f99 | ||
|
|
f23c7007a5 | ||
|
|
044cf7ee13 | ||
|
|
a1d42b4177 | ||
|
|
8ad04f5a5e | ||
|
|
e25140d72e | ||
|
|
9688ba24ca | ||
|
|
237e5a942f | ||
|
|
888cd27540 | ||
|
|
77d7a0b7ed | ||
|
|
263a10cb3a | ||
|
|
3cf67bb60e | ||
|
|
ef938a5d54 | ||
|
|
62b0a9b9f1 | ||
|
|
d790bafcad | ||
|
|
85020c1c95 | ||
|
|
5c7cce6dfe | ||
|
|
72f32fc4f4 | ||
|
|
a8f8b6d36c | ||
|
|
fdba105a0d | ||
|
|
9d4d11df61 | ||
|
|
493da5f964 | ||
|
|
9b5a16e57e | ||
|
|
f1e17c6672 | ||
|
|
3909f62fa9 | ||
|
|
df40bee75e | ||
|
|
55e6301fa6 | ||
|
|
d549a16ee8 | ||
|
|
2ba0df3d3c | ||
|
|
8c091a2daa | ||
|
|
fc366933e4 | ||
|
|
e62ce9e6e3 | ||
|
|
728f5fe2cd | ||
|
|
49a81a7a2c | ||
|
|
cedf079762 | ||
|
|
d61983643a | ||
|
|
a5275e37d1 | ||
|
|
3e82466d5e | ||
|
|
384c4bb3f8 | ||
|
|
4c447b06a2 | ||
|
|
b66ba8ba0a | ||
|
|
6381acf85b | ||
|
|
3758cdf818 | ||
|
|
3b4b2c7a50 | ||
|
|
2482ca7ea7 | ||
|
|
e092909b81 | ||
|
|
24f1d2ce89 | ||
|
|
f581f71058 | ||
|
|
958a9a3aed | ||
|
|
3f3b752e44 | ||
|
|
1244e96681 | ||
|
|
b021460290 | ||
|
|
9a201a8963 | ||
|
|
5b696e5cf3 | ||
|
|
3c62c9ddb8 | ||
|
|
284a5eff89 | ||
|
|
a0b7ba6d9b | ||
|
|
e1d5a2a4c1 | ||
|
|
8ead035016 | ||
|
|
7e19462312 | ||
|
|
881697d3d7 | ||
|
|
66d981aa64 | ||
|
|
f2219ee717 | ||
|
|
833e3bbcf6 | ||
|
|
8696670909 | ||
|
|
791073bd28 | ||
|
|
41c86d6459 | ||
|
|
b1977dfb18 | ||
|
|
76c8d7287e | ||
|
|
389424c54b | ||
|
|
c9f63b5744 | ||
|
|
9523c036d6 | ||
|
|
2223c6e498 | ||
|
|
6b5a4bbb13 | ||
|
|
77bf892809 | ||
|
|
7e41cf07d4 | ||
|
|
b053cf9e2a | ||
|
|
6ceb45bac4 | ||
|
|
450af67698 | ||
|
|
312f5960df | ||
|
|
064f137f26 | ||
|
|
ec431ffd95 | ||
|
|
ec28618413 | ||
|
|
25785841a3 | ||
|
|
14374bc1be | ||
|
|
aa9c4eec8a | ||
|
|
f1a7de70e2 | ||
|
|
25f868971b | ||
|
|
17a5cf572d | ||
|
|
7dfd8181fa | ||
|
|
da56bb8d9f | ||
|
|
7ef4deb76c | ||
|
|
c53159cb0c | ||
|
|
c12704d726 | ||
|
|
cbed4786be | ||
|
|
fd60e60541 | ||
|
|
fb833ed311 | ||
|
|
1440c53ed6 | ||
|
|
bd42c35e80 | ||
|
|
a5c82670c9 | ||
|
|
3c966a77df | ||
|
|
1a9aade28e | ||
|
|
34c4c28879 | ||
|
|
a272b34809 | ||
|
|
05b8d1e40d | ||
|
|
18ef471332 | ||
|
|
e14502606c | ||
|
|
59953d857b | ||
|
|
436eced9fb | ||
|
|
f56985938d | ||
|
|
56f4198b28 | ||
|
|
6b22081f61 | ||
|
|
a5fa53c460 | ||
|
|
426e0af8cd | ||
|
|
e2f1854b1e | ||
|
|
c194605c35 | ||
|
|
112be18635 | ||
|
|
802c7ac906 | ||
|
|
f575cc1860 | ||
|
|
b03bf1c1f0 | ||
|
|
9bebda057a | ||
|
|
83a8058b51 | ||
|
|
072e9d44bd | ||
|
|
a3013de6ea | ||
|
|
9eed62a854 | ||
|
|
79282a8df9 | ||
|
|
ec64246dc7 | ||
|
|
c89d2abf5a | ||
|
|
b3914f627d | ||
|
|
998236dcc5 | ||
|
|
d9626bf311 | ||
|
|
684d70985e | ||
|
|
e92842d837 | ||
|
|
12c6d6e3bd | ||
|
|
1d704d9352 | ||
|
|
38ac585e7c | ||
|
|
892d289f38 | ||
|
|
5294c5135c | ||
|
|
8e6896ba03 | ||
|
|
b2e86c2ea3 | ||
|
|
8cc58dd8b0 | ||
|
|
ca1620ef42 | ||
|
|
f1d834df8e | ||
|
|
502ffc62ef | ||
|
|
73db636361 | ||
|
|
b222785dc2 | ||
|
|
5454004ef9 | ||
|
|
ed9671c2a6 | ||
|
|
911e35bc50 | ||
|
|
898949d1bc | ||
|
|
ef8fe58245 | ||
|
|
9e792369b2 | ||
|
|
c76a77e431 | ||
|
|
2308c9da49 | ||
|
|
c59f88ca82 | ||
|
|
3edfcff1a0 | ||
|
|
d84673376d | ||
|
|
69def04ec2 | ||
|
|
3e1a46f2de | ||
|
|
113ba716e6 | ||
|
|
703037cbfb | ||
|
|
07a8e8c91d | ||
|
|
df745ef39c | ||
|
|
2665c8fc16 | ||
|
|
cb34fec596 | ||
|
|
d8560dcb19 | ||
|
|
0e5f1d9a30 | ||
|
|
ad814f175e |
@@ -9,7 +9,7 @@ steps:
|
||||
path: /drone/build
|
||||
commands:
|
||||
- cd /drone/build
|
||||
- cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DCMAKE_BUILD_TYPE=Debug -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DECM_ENABLE_SANITIZERS=address -DCMAKE_CXX_FLAGS=-Werror ../src
|
||||
- cmake -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DCMAKE_BUILD_TYPE=Debug -DQUICK_COMPILER=ON -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DECM_ENABLE_SANITIZERS=address -DCMAKE_CXX_FLAGS=-Werror ../src
|
||||
- name: compile
|
||||
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-4
|
||||
volumes:
|
||||
@@ -53,7 +53,7 @@ steps:
|
||||
path: /drone/build
|
||||
commands:
|
||||
- cd /drone/build
|
||||
- cmake -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 -DCMAKE_BUILD_TYPE=Debug -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DECM_ENABLE_SANITIZERS=address -DCMAKE_CXX_FLAGS=-Werror ../src
|
||||
- cmake -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 -DCMAKE_BUILD_TYPE=Debug -DQUICK_COMPILER=ON -DBUILD_UPDATER=ON -DBUILD_TESTING=1 -DECM_ENABLE_SANITIZERS=address -DCMAKE_CXX_FLAGS=-Werror ../src
|
||||
- name: compile
|
||||
image: ghcr.io/nextcloud/continuous-integration-client:client-5.15-4
|
||||
volumes:
|
||||
|
||||
1
.gitignore
vendored
@@ -184,6 +184,7 @@ compile_commands.json
|
||||
convert.exe
|
||||
.dir-locals.el
|
||||
*-icon.png
|
||||
*-icon-win-folder.png
|
||||
*-sidebar.png
|
||||
*-w10startmenu.png
|
||||
theme.qrc
|
||||
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[cy_GB]=@APPLICATION_ICON_NAME@
|
||||
Name[cy_GB]=@APPLICATION_NAME@ cleient cydweddu bwrdd gwaith
|
||||
Comment[cy_GB]=@APPLICATION_NAME@ cleient cydweddu bwrdd gwaith
|
||||
GenericName[cy_GB]=Cydweddu Ffolder
|
||||
|
||||
@@ -21,7 +21,7 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
|
||||
# Translations
|
||||
Icon[de_DE]=@APPLICATION_ICON_NAME@
|
||||
Name[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
|
||||
Comment[de_DE]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
|
||||
GenericName[de_DE]=Ordnersynchronisierung
|
||||
Icon[de]=@APPLICATION_ICON_NAME@
|
||||
Name[de]=@APPLICATION_NAME@ Desktop
|
||||
Comment[de]=@APPLICATION_NAME@ Client zur Desktop-Synchronisierung
|
||||
GenericName[de]=Ordner-Synchronisation
|
||||
|
||||
24
.tx/nextcloud.client-desktop/id_translation
Normal file
@@ -0,0 +1,24 @@
|
||||
[Desktop Entry]
|
||||
Categories=Utility;X-SuSE-SyncUtility;
|
||||
Type=Application
|
||||
Exec=@APPLICATION_EXECUTABLE@
|
||||
Name=@APPLICATION_NAME@ Desktop
|
||||
Comment=@APPLICATION_NAME@ desktop synchronization client
|
||||
GenericName=Folder Sync
|
||||
Icon=@APPLICATION_ICON_NAME@
|
||||
Keywords=@APPLICATION_NAME@;syncing;file;sharing;
|
||||
X-GNOME-Autostart-Delay=3
|
||||
MimeType=application/vnd.@APPLICATION_EXECUTABLE@;
|
||||
Actions=Quit;
|
||||
|
||||
# Translations
|
||||
|
||||
|
||||
[Desktop Action Quit]
|
||||
Exec=@APPLICATION_EXECUTABLE@ --quit
|
||||
Name=Quit @APPLICATION_NAME@
|
||||
Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
|
||||
# Translations
|
||||
GenericName[id]=Sinkronisasi Folder
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[ko]=@APPLICATION_ICON_NAME@
|
||||
Name[ko]=@APPLICATION_NAME@ 데스크탑
|
||||
Comment[ko]=@APPLICATION_NAME@ 데스크톱 동기화 클라이언트
|
||||
GenericName[ko]=폴더 동기화
|
||||
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[nb_NO]=@APPLICATION_ICON_NAME@
|
||||
Name[nb_NO]=@APPLICATION_NAME@ skrivebord
|
||||
Comment[nb_NO]=@APPLICATION_NAME@ klient for synkroinisering
|
||||
GenericName[nb_NO]=Mappe synkroinisering
|
||||
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[oc]=@APPLICATION_ICON_NAME@
|
||||
Name[oc]=@APPLICATION_NAME@ Burèu
|
||||
Comment[oc]=@APPLICATION_NAME@ client de sincronizacion
|
||||
GenericName[oc]=Sincro. dossièr
|
||||
|
||||
@@ -22,5 +22,6 @@ Icon=@APPLICATION_EXECUTABLE@
|
||||
|
||||
# Translations
|
||||
Icon[sv]=@APPLICATION_ICON_NAME@
|
||||
Name[sv]=@APPLICATION_NAME@ Skrivbord
|
||||
Comment[sv]=@APPLICATION_NAME@ desktopssynkroniseringsklient
|
||||
GenericName[sv]=Mappsynkronisering
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
cmake_policy(SET CMP0071 NEW) # Enable use of QtQuick compiler/generated code
|
||||
|
||||
project(client)
|
||||
|
||||
@@ -96,26 +97,18 @@ endif()
|
||||
message(STATUS "GIT_SHA1 ${GIT_SHA1}")
|
||||
|
||||
set(SYSCONFDIR ${SYSCONF_INSTALL_DIR})
|
||||
set(SHAREDIR ${CMAKE_INSTALL_DATADIR})
|
||||
set(SHAREDIR ${CMAKE_INSTALL_FULL_DATADIR})
|
||||
|
||||
#####
|
||||
## handle BUILD_OWNCLOUD_OSX_BUNDLE
|
||||
# BUILD_OWNCLOUD_OSX_BUNDLE was not initialized OR set to true on OSX
|
||||
if(APPLE AND (NOT DEFINED BUILD_OWNCLOUD_OSX_BUNDLE OR BUILD_OWNCLOUD_OSX_BUNDLE))
|
||||
set(BUILD_OWNCLOUD_OSX_BUNDLE ON)
|
||||
# Build MacOS app bundle if wished
|
||||
if(APPLE AND BUILD_OWNCLOUD_OSX_BUNDLE)
|
||||
message(STATUS "Build MacOS app bundle")
|
||||
set(OWNCLOUD_OSX_BUNDLE "${APPLICATION_NAME}.app")
|
||||
set(LIB_INSTALL_DIR "${APPLICATION_NAME}.app/Contents/MacOS")
|
||||
set(BIN_INSTALL_DIR "${APPLICATION_NAME}.app/Contents/MacOS")
|
||||
|
||||
# BUILD_OWNCLOUD_OSX_BUNDLE was disabled on OSX
|
||||
elseif(APPLE AND NOT BUILD_OWNCLOUD_OSX_BUNDLE)
|
||||
message(FATAL_ERROR "Building in non-bundle mode on OSX is currently not supported. Comment this error out if you want to work on/test it.")
|
||||
|
||||
# any other platform
|
||||
else()
|
||||
set(BUILD_OWNCLOUD_OSX_BUNDLE OFF)
|
||||
endif()
|
||||
#####
|
||||
|
||||
|
||||
option(QUICK_COMPILER "Use QtQuick compiler to improve performance" OFF)
|
||||
|
||||
# this option removes Http authentication, keychain, shibboleth etc and is intended for
|
||||
# external authentication mechanisms
|
||||
@@ -130,11 +123,8 @@ if(NO_MSG_HANDLER)
|
||||
add_definitions(-DNO_MSG_HANDLER=1)
|
||||
endif()
|
||||
|
||||
# this option builds the updater
|
||||
option(BUILD_UPDATER "BUILD_UPDATER" OFF)
|
||||
if(BUILD_UPDATER)
|
||||
message("Compiling with updater")
|
||||
add_definitions(-DBUILD_UPDATER=1)
|
||||
else()
|
||||
message("Compiling without updater")
|
||||
endif()
|
||||
@@ -193,6 +183,7 @@ if(BUILD_CLIENT)
|
||||
pkg_check_modules(CLOUDPROVIDERS cloudproviders IMPORTED_TARGET)
|
||||
|
||||
if(CLOUDPROVIDERS_FOUND)
|
||||
pkg_check_modules(DBUS-1 REQUIRED dbus-1 IMPORTED_TARGET)
|
||||
pkg_check_modules(GIO REQUIRED gio-2.0 IMPORTED_TARGET)
|
||||
pkg_check_modules(GLIB2 REQUIRED glib-2.0 IMPORTED_TARGET)
|
||||
endif()
|
||||
|
||||
@@ -30,12 +30,14 @@ option( WITH_CRASHREPORTER "Build crashreporter" OFF )
|
||||
#set( CRASHREPORTER_ICON ":/owncloud-icon.png" )
|
||||
|
||||
## Updater options
|
||||
option( BUILD_UPDATER "Build updater" OFF )
|
||||
option( BUILD_UPDATER "Build updater" ON )
|
||||
|
||||
option( WITH_PROVIDERS "Build with providers list" ON )
|
||||
|
||||
option( ENFORCE_VIRTUAL_FILES_SYNC_FOLDER "Enforce use of virtual files sync folder when available" OFF )
|
||||
|
||||
option( DO_NOT_USE_PROXY "Do not use system wide proxy, instead always do a direct connection to server" OFF )
|
||||
|
||||
## Theming options
|
||||
set(NEXTCLOUD_BACKGROUND_COLOR "#0082c9" CACHE STRING "Default Nextcloud background color")
|
||||
set( APPLICATION_WIZARD_HEADER_BACKGROUND_COLOR ${NEXTCLOUD_BACKGROUND_COLOR} CACHE STRING "Hex color of the wizard header background")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
set( MIRALL_VERSION_MAJOR 3 )
|
||||
set( MIRALL_VERSION_MINOR 3 )
|
||||
set( MIRALL_VERSION_PATCH 81 )
|
||||
set( MIRALL_VERSION_MINOR 4 )
|
||||
set( MIRALL_VERSION_PATCH 50 )
|
||||
set( MIRALL_VERSION_YEAR 2021 )
|
||||
set( MIRALL_SOVERSION 0 )
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ OBS_PROJECT_BETA=home:ivaradi:beta
|
||||
OBS_PACKAGE=nextcloud-desktop
|
||||
|
||||
if test "${DRONE_TARGET_BRANCH}" = "stable-2.6"; then
|
||||
UBUNTU_DISTRIBUTIONS="bionic focal hirsute impish"
|
||||
UBUNTU_DISTRIBUTIONS="bionic focal impish jammy"
|
||||
DEBIAN_DISTRIBUTIONS="buster stretch testing"
|
||||
else
|
||||
UBUNTU_DISTRIBUTIONS="focal hirsute impish"
|
||||
UBUNTU_DISTRIBUTIONS="focal impish jammy"
|
||||
DEBIAN_DISTRIBUTIONS="testing"
|
||||
fi
|
||||
|
||||
|
||||
@@ -695,7 +695,12 @@
|
||||
<key>PROJECT_SETTINGS</key>
|
||||
<dict>
|
||||
<key>ADVANCED_OPTIONS</key>
|
||||
<dict/>
|
||||
<dict>
|
||||
<key>installer-script.options:hostArchitectures</key>
|
||||
<array>
|
||||
<string>x86_64,arm64</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>BUILD_FORMAT</key>
|
||||
<integer>0</integer>
|
||||
<key>BUILD_PATH</key>
|
||||
|
||||
@@ -26,6 +26,8 @@ install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/make-msi.bat
|
||||
Platform.wxi
|
||||
Nextcloud.wxs
|
||||
RegistryCleanup.vbs
|
||||
RegistryCleanupCustomAction.wxs
|
||||
gui/banner.bmp
|
||||
gui/dialog.bmp
|
||||
DESTINATION msi/)
|
||||
|
||||
@@ -76,12 +76,16 @@
|
||||
|
||||
<!-- Uninstall: Remove sync folders from Explorer's Navigation Pane, only effective for the current user (home users) -->
|
||||
<Custom Action="RemoveNavigationPaneEntries" After="RemoveFiles">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
|
||||
|
||||
<!-- Uninstall: Cleanup the Registry -->
|
||||
<Custom Action="RegistryCleanupCustomAction" After="RemoveFiles">(NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>
|
||||
|
||||
<!-- Schedule Reboot for the Shell Extensions (in silent installation mode only, or if SCHEDULE_REBOOT argument is set-->
|
||||
<ScheduleReboot After="InstallFinalize">(SCHEDULE_REBOOT=1) OR NOT (UILevel=2)</ScheduleReboot>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<!-- "Add or Remove" Programs Entries -->
|
||||
<Property Id="APPNAME">$(var.AppName)</Property>
|
||||
<Property Id="ARPPRODUCTICON">$(var.AppIcon)</Property>
|
||||
<Property Id="ARPHELPLINK">$(var.AppHelpLink)</Property>
|
||||
<Property Id="ARPURLINFOABOUT">$(var.AppInfoLink)</Property>
|
||||
|
||||
54
admin/win/msi/RegistryCleanup.vbs
Normal file
@@ -0,0 +1,54 @@
|
||||
On Error goto 0
|
||||
|
||||
Const HKEY_LOCAL_MACHINE = &H80000002
|
||||
|
||||
Const strObjRegistry = "winmgmts:\\.\root\default:StdRegProv"
|
||||
|
||||
Function RegistryDeleteKeyRecursive(regRoot, strKeyPath)
|
||||
Set objRegistry = GetObject(strObjRegistry)
|
||||
objRegistry.EnumKey regRoot, strKeyPath, arrSubkeys
|
||||
If IsArray(arrSubkeys) Then
|
||||
For Each strSubkey In arrSubkeys
|
||||
RegistryDeleteKeyRecursive regRoot, strKeyPath & "\" & strSubkey
|
||||
Next
|
||||
End If
|
||||
objRegistry.DeleteKey regRoot, strKeyPath
|
||||
End Function
|
||||
|
||||
Function RegistryListSubkeys(regRoot, strKeyPath)
|
||||
Set objRegistry = GetObject(strObjRegistry)
|
||||
objRegistry.EnumKey regRoot, strKeyPath, arrSubkeys
|
||||
RegistryListSubkeys = arrSubkeys
|
||||
End Function
|
||||
|
||||
Function GetUserSID()
|
||||
Dim objWshNetwork, objUserAccount
|
||||
|
||||
Set objWshNetwork = CreateObject("WScript.Network")
|
||||
|
||||
Set objUserAccount = GetObject("winmgmts://" & objWshNetwork.UserDomain & "/root/cimv2").Get("Win32_UserAccount.Domain='" & objWshNetwork.ComputerName & "',Name='" & objWshNetwork.UserName & "'")
|
||||
GetUserSID = objUserAccount.SID
|
||||
End Function
|
||||
|
||||
Function RegistryCleanupSyncRootManager()
|
||||
strSyncRootManagerKeyPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SyncRootManager"
|
||||
|
||||
arrSubKeys = RegistryListSubkeys(HKEY_LOCAL_MACHINE, strSyncRootManagerKeyPath)
|
||||
|
||||
If IsArray(arrSubkeys) Then
|
||||
arrSubkeys=Filter(arrSubkeys, Session.Property("APPNAME"))
|
||||
End If
|
||||
If IsArray(arrSubkeys) Then
|
||||
arrSubkeys=Filter(arrSubkeys, GetUserSID())
|
||||
End If
|
||||
|
||||
If IsArray(arrSubkeys) Then
|
||||
For Each strSubkey In arrSubkeys
|
||||
RegistryDeleteKeyRecursive HKEY_LOCAL_MACHINE, strSyncRootManagerKeyPath & "\" & strSubkey
|
||||
Next
|
||||
End If
|
||||
End Function
|
||||
|
||||
Function RegistryCleanup()
|
||||
RegistryCleanupSyncRootManager()
|
||||
End Function
|
||||
7
admin/win/msi/RegistryCleanupCustomAction.wxs
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Fragment>
|
||||
<Binary Id="RegistryCleanup" SourceFile="RegistryCleanup.vbs"/>
|
||||
<CustomAction Id='RegistryCleanupCustomAction' BinaryKey="RegistryCleanup" VBScriptCall="RegistryCleanup" Return="ignore" Execute="immediate"/>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -17,10 +17,10 @@ Rem Generate collect.wxs
|
||||
if %ERRORLEVEL% neq 0 exit %ERRORLEVEL%
|
||||
|
||||
Rem Compile en-US (https://www.firegiant.com/wix/tutorial/transforms/morphing-installers/)
|
||||
"%WIX%\bin\candle.exe" -dcodepage=1252 -dPlatform=%BuildArch% -arch %BuildArch% -dHarvestAppDir="%HarvestAppDir%" -ext WixUtilExtension NCMsiHelper.wxs WinShellExt.wxs collect.wxs Nextcloud.wxs
|
||||
"%WIX%\bin\candle.exe" -dcodepage=1252 -dPlatform=%BuildArch% -arch %BuildArch% -dHarvestAppDir="%HarvestAppDir%" -ext WixUtilExtension NCMsiHelper.wxs WinShellExt.wxs collect.wxs Nextcloud.wxs RegistryCleanupCustomAction.wxs
|
||||
if %ERRORLEVEL% neq 0 exit %ERRORLEVEL%
|
||||
|
||||
Rem Link MSI package
|
||||
"%WIX%\bin\light.exe" -sw1076 -ext WixUIExtension -ext WixUtilExtension -cultures:en-us NCMsiHelper.wixobj WinShellExt.wixobj collect.wixobj Nextcloud.wixobj -out "@MSI_INSTALLER_FILENAME@"
|
||||
"%WIX%\bin\light.exe" -sw1076 -ext WixUIExtension -ext WixUtilExtension -cultures:en-us NCMsiHelper.wixobj WinShellExt.wixobj collect.wixobj Nextcloud.wixobj RegistryCleanupCustomAction.wixobj -out "@MSI_INSTALLER_FILENAME@"
|
||||
|
||||
exit %ERRORLEVEL%
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
# target does not have the ``WIN32_EXECUTABLE`` property set.
|
||||
# * One of the tools png2ico (See :find-module:`FindPng2Ico`) or
|
||||
# icotool (see :find-module:`FindIcoTool`) is required.
|
||||
# * Supported sizes: 16, 24, 32, 48, 64, 128, 256, 512 and 1024.
|
||||
# * Supported sizes: 16, 20, 24, 32, 40, 48, 64, 128, 256, 512 and 1024.
|
||||
#
|
||||
# Mac OS X notes
|
||||
# * The executable target must have the ``MACOSX_BUNDLE`` property set.
|
||||
@@ -102,9 +102,12 @@ include(CMakeParseArguments)
|
||||
|
||||
function(ecm_add_app_icon appsources)
|
||||
set(options)
|
||||
set(oneValueArgs OUTFILE_BASENAME)
|
||||
set(multiValueArgs ICONS SIDEBAR_ICONS)
|
||||
set(oneValueArgs OUTFILE_BASENAME ICON_INDEX)
|
||||
set(multiValueArgs ICONS SIDEBAR_ICONS RC_DEPENDENCIES)
|
||||
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
if (NOT ARG_ICON_INDEX)
|
||||
set(ARG_ICON_INDEX 1)
|
||||
endif()
|
||||
|
||||
if(NOT ARG_ICONS)
|
||||
message(FATAL_ERROR "No ICONS argument given to ecm_add_app_icon")
|
||||
@@ -138,8 +141,11 @@ function(ecm_add_app_icon appsources)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
|
||||
_ecm_add_app_icon_categorize_icons("${ARG_ICONS}" "icons" "16;24;32;48;64;128;256;512;1024")
|
||||
if (WIN32)
|
||||
_ecm_add_app_icon_categorize_icons("${ARG_ICONS}" "icons" "16;20;24;32;40;48;64;128;256;512;1024")
|
||||
else()
|
||||
_ecm_add_app_icon_categorize_icons("${ARG_ICONS}" "icons" "16;24;32;48;64;128;256;512;1024")
|
||||
endif()
|
||||
if(ARG_SIDEBAR_ICONS)
|
||||
_ecm_add_app_icon_categorize_icons("${ARG_SIDEBAR_ICONS}" "sidebar_icons" "16;32;64;128;256")
|
||||
endif()
|
||||
@@ -168,8 +174,10 @@ function(ecm_add_app_icon appsources)
|
||||
|
||||
|
||||
set(windows_icons ${icons_at_16px}
|
||||
${icons_at_20px}
|
||||
${icons_at_24px}
|
||||
${icons_at_32px}
|
||||
${icons_at_40px}
|
||||
${icons_at_48px}
|
||||
${icons_at_64px}
|
||||
${icons_at_128px}
|
||||
@@ -204,12 +212,12 @@ function(ecm_add_app_icon appsources)
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
# this bit's a little hacky to make the dependency stuff work
|
||||
file(WRITE "${_outfilename}.rc.in" "IDI_ICON1 ICON DISCARDABLE \"${_outfilename}.ico\"\n")
|
||||
file(WRITE "${_outfilename}.rc.in" "IDI_ICON${ARG_ICON_INDEX} ICON DISCARDABLE \"${_outfilename}.ico\"\n")
|
||||
add_custom_command(
|
||||
OUTPUT "${_outfilename}.rc"
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
ARGS -E copy "${_outfilename}.rc.in" "${_outfilename}.rc"
|
||||
DEPENDS "${_outfilename}.ico"
|
||||
DEPENDS ${ARG_RC_DEPENDENCIES} "${_outfilename}.ico"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
)
|
||||
endfunction()
|
||||
@@ -226,7 +234,7 @@ function(ecm_add_app_icon appsources)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(size 16 24 32 48 64 128 ${maxSize})
|
||||
foreach(size 16 20 24 32 40 48 64 128 ${maxSize})
|
||||
if(NOT icons_at_${size}px)
|
||||
continue()
|
||||
endif()
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#cmakedefine APPLICATION_FORBID_BAD_SSL "@APPLICATION_FORBID_BAD_SSL@"
|
||||
#define APPLICATION_DOTVIRTUALFILE_SUFFIX "." APPLICATION_VIRTUALFILE_SUFFIX
|
||||
#cmakedefine01 ENFORCE_VIRTUAL_FILES_SYNC_FOLDER
|
||||
#cmakedefine DO_NOT_USE_PROXY "@DO_NOT_USE_PROXY@"
|
||||
|
||||
#cmakedefine ZLIB_FOUND @ZLIB_FOUND@
|
||||
|
||||
@@ -41,4 +42,6 @@
|
||||
|
||||
#cmakedefine01 GUI_TESTING
|
||||
|
||||
#cmakedefine BUILD_UPDATER "@BUILD_UPDATER@"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,7 +21,7 @@ result, the Nextcloud Client runs on Linux, Windows, and MacOS.
|
||||
The Synchronization Process
|
||||
---------------------------
|
||||
|
||||
The process of synchronization keeps files in two separate repositories the
|
||||
The process of synchronization keeps files in two separate repositories the
|
||||
same. When synchronized:
|
||||
|
||||
- If a file is added to one repository it is copied to the other synchronized repository.
|
||||
@@ -93,7 +93,7 @@ traverses the file tree and compares the modification time of each file with an
|
||||
expected value stored in its database. If the value is not the same, the client
|
||||
determines that the file has been modified in the local repository.
|
||||
|
||||
.. note:: On the local side, the modification time is a good attribute to use for
|
||||
.. note:: On the local side, the modification time is a good attribute to use for
|
||||
detecting changes, because
|
||||
the value does not depend on time shifts and such.
|
||||
|
||||
@@ -122,7 +122,7 @@ Conflict files are always created on the client and never on the server.
|
||||
|
||||
In ownCloud 10.0 we implemented a checksum feature which checks the file integrity on upload and download by computing a checksum after the file transfer finishes.
|
||||
The client queries the server capabilities after login to decide which checksum algorithm to use.
|
||||
Currently, SHA1 is hard-coded in the official server release and can't be changed by the end-user.
|
||||
Currently, SHA1 is hard-coded in the official server release and can't be changed by the end-user.
|
||||
Note that the server additionally also supports MD5 and Adler-32, but the desktop client will always use the checksum algorithm announced in the capabilities:
|
||||
|
||||
::
|
||||
@@ -202,14 +202,14 @@ Conflict files are always created on the client and never on the server.
|
||||
Upload
|
||||
~~~~~~
|
||||
|
||||
A checksum is calculated with the previously negotiated algorithm by the client and sent along with the file in an HTTP Header.
|
||||
A checksum is calculated with the previously negotiated algorithm by the client and sent along with the file in an HTTP Header.
|
||||
```OC-Checksum: [algorithm]:[checksum]```
|
||||
|
||||
.. image:: ./images/checksums/client-activity.png
|
||||
|
||||
During file upload, the server computes SHA1, MD5, and Adler-32 checksums and compares one of them to the checksum supplied by the client.
|
||||
During file upload, the server computes SHA1, MD5, and Adler-32 checksums and compares one of them to the checksum supplied by the client.
|
||||
|
||||
On mismatch, the server returns HTTP Status code 400 (Bad Request) thus signaling the client that the upload failed.
|
||||
On mismatch, the server returns HTTP Status code 400 (Bad Request) thus signaling the client that the upload failed.
|
||||
The server then discards the upload, and the client blacklists the file:
|
||||
|
||||
.. image:: ./images/checksums/testing-checksums.png
|
||||
@@ -223,29 +223,29 @@ Conflict files are always created on the client and never on the server.
|
||||
client.</s:message>
|
||||
</d:error>
|
||||
|
||||
The client retries the upload using exponential back-off.
|
||||
The client retries the upload using exponential back-off.
|
||||
On success (matching checksum) the computed checksums are stored by the server in ``oc_filecache`` alongside the file.
|
||||
|
||||
Chunked Upload
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Mostly same as above.
|
||||
The checksum of the full file is sent with every chunk of the file.
|
||||
Mostly same as above.
|
||||
The checksum of the full file is sent with every chunk of the file.
|
||||
But the server only compares the checksum after receiving the checksum sent with the last chunk.
|
||||
|
||||
Download
|
||||
~~~~~~~~
|
||||
|
||||
The server sends the checksum in an HTTP header with the file. (same format as above).
|
||||
If no checksum is found in ``oc_filecache`` (freshly mounted external storage) it is computed and stored in ``oc_filecache`` on the first download.
|
||||
The checksum is then provided on all subsequent downloads but not on the first.
|
||||
If no checksum is found in ``oc_filecache`` (freshly mounted external storage) it is computed and stored in ``oc_filecache`` on the first download.
|
||||
The checksum is then provided on all subsequent downloads but not on the first.
|
||||
|
||||
.. _ignored-files-label:
|
||||
|
||||
Ignored Files
|
||||
-------------
|
||||
|
||||
The Nextcloud Client supports the ability to exclude or ignore certain files from the synchronization process.
|
||||
The Nextcloud Client supports the ability to exclude or ignore certain files from the synchronization process.
|
||||
Some system wide file patterns that are used to exclude or ignore files are included with the client by default and the Nextcloud Client provides the ability to add custom patterns.
|
||||
|
||||
By default, the Nextcloud Client ignores the following files:
|
||||
@@ -262,18 +262,18 @@ By default, the Nextcloud Client ignores the following files:
|
||||
If a pattern selected using a checkbox in the `ignoredFilesEditor-label` (or if
|
||||
a line in the exclude file starts with the character ``]`` directly followed by
|
||||
the file pattern), files matching the pattern are considered *fleeting meta
|
||||
data*.
|
||||
data*.
|
||||
|
||||
These files are ignored and *removed* by the client if found in the
|
||||
synchronized folder.
|
||||
synchronized folder.
|
||||
This is suitable for meta files created by some applications that have no sustainable meaning.
|
||||
|
||||
If a pattern ends with the forward slash (``/``) character, only directories are matched.
|
||||
If a pattern ends with the forward slash (``/``) character, only directories are matched.
|
||||
The pattern is only applied for directory components of filenames selected using the checkbox.
|
||||
|
||||
To match filenames against the exclude patterns, the UNIX standard C library
|
||||
function ``fnmatch`` is used.
|
||||
This process checks the filename against the specified pattern using standard shell wildcard pattern matching.
|
||||
function ``fnmatch`` is used.
|
||||
This process checks the filename against the specified pattern using standard shell wildcard pattern matching.
|
||||
For more information, please refer to `The opengroup website
|
||||
<http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13_01>`_.
|
||||
|
||||
@@ -369,7 +369,7 @@ is renamed or moved.
|
||||
Example:
|
||||
|
||||
<oc:id>00000020oc5cfy6qqizm</oc:id>
|
||||
|
||||
|
||||
End-to-end Encryption
|
||||
---------------------
|
||||
|
||||
@@ -440,13 +440,3 @@ Files that must be removed from the local storage only, need to be dehydrated vi
|
||||
|
||||
.. note::
|
||||
* End-to-end Encryption works with Virtual Files (VFS) but only on a per-folder level. Folders with E2EE can be made available offline in their entirety, but the individual files in them can not be retrieved on demand. This is mainly due to two technical reasons. First, the Windows VFS API is not designed for handling encrypted files. Second, while the VFS is designed to deal mostly with large files, E2EE is mostly recommended for use with small files as encrypting and decrypting large files puts large demands on the computer infrastructure.
|
||||
|
||||
|
||||
User Status
|
||||
-----------
|
||||
|
||||
Starting from 3.2.0, user status is displayed in the Nextcloud desktop client's tray window. The icon and a text message are displayed as long as those are set in the user's account menu in the Web UI (server's website). At the moment, setting the status from the desktop client is not available.
|
||||
The status is updated almost immediately after it is set in the Web UI. Default user status is always "Online" if no other status is available from the server-side.
|
||||
|
||||
.. image:: images/status_feature_example.png
|
||||
:alt: User Status feature in the tray window
|
||||
|
||||
@@ -185,6 +185,8 @@ Then, in Terminal:
|
||||
.. code-block:: bash
|
||||
|
||||
% echo 'export CMAKE_INSTALL_PREFIX=~/Builds' >> ~/.nextcloud_build_variables
|
||||
# If you want to build a macOS app bundle for distribution
|
||||
% echo 'export BUILD_OWNCLOUD_OSX_BUNDLE=ON' >> ~/.nextcloud_build_variables
|
||||
|
||||
Replace ``~/Builds`` with a different directory if you'd like the build to end up elsewhere.
|
||||
|
||||
|
||||
@@ -48,9 +48,9 @@ copyright = u'2013-2021, The Nextcloud developers'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '3.3'
|
||||
version = '3.4'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '3.3.81'
|
||||
release = '3.4.50'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 21 KiB |
BIN
doc/images/open-share-dialog.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
doc/images/set-user-status-menu.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
doc/images/set-user-status.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
doc/images/share-dialog-view-profile.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
doc/images/sync-state-paused.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
doc/images/sync-state-synced.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
doc/images/sync-state-syncing.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
doc/images/sync-state-warnings.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
doc/images/unified-search-events.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
doc/images/unified-search-files.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
doc/images/unified-search-talk.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
doc/images/user-account-options.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
@@ -8,7 +8,9 @@ There are clients for Linux, macOs, and Microsoft Windows.
|
||||
|
||||
The currently supported server releases are the latest three stable versions
|
||||
at time of publication. It means that the |version| release series is supporting
|
||||
server major version 20, 21 and 22.
|
||||
stable server major versions.
|
||||
See https://github.com/nextcloud/server/wiki/Maintenance-and-Release-Schedule for
|
||||
supported major versions.
|
||||
|
||||
Installation on Mac OS X and Windows is the same as for any software
|
||||
application: download the program and then double-click it to launch the
|
||||
@@ -31,7 +33,7 @@ download page.
|
||||
System Requirements
|
||||
----------------------------------
|
||||
|
||||
- Windows 10+
|
||||
- Windows 8.1+
|
||||
- macOS 10.12+ (64-bit only)
|
||||
- Linux
|
||||
- FreeBSD
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
===========
|
||||
Visual Tour
|
||||
===========
|
||||
|
||||
@@ -11,23 +12,7 @@ as an icon in the system tray (Windows, KDE), status bar
|
||||
(macOS), or notification area (Ubuntu).
|
||||
|
||||
.. image:: images/icon.png
|
||||
|
||||
Main dialog
|
||||
~~~~~~~~~~~
|
||||
|
||||
.. index:: activity, recent changes, sync activity, main dialog, adding account, account, add account, remove account
|
||||
|
||||
The main dialog, which can be invoked from the tray icon in the
|
||||
taskbar, will show files information about the activities of the sync
|
||||
client and Nextcloud. If there are any synchronization issues, they
|
||||
will show up here. The dialog also gives information about other
|
||||
activities or notifications like Talk mentions or file changes. It
|
||||
does also show the status of the user.
|
||||
|
||||
.. image:: images/main_dialog.png
|
||||
|
||||
When clicking on the avatar a menu opens where it is possible to add a
|
||||
new account or removing an existing account.
|
||||
:alt: desktop client icon
|
||||
|
||||
Menu
|
||||
----
|
||||
@@ -45,8 +30,9 @@ A right click on the icon provides the following menu:
|
||||
.. NOTE::
|
||||
This menu is not available on macOS.
|
||||
|
||||
|
||||
Settings
|
||||
--------
|
||||
~~~~~~~~
|
||||
|
||||
Account Settings
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -218,3 +204,98 @@ while syncing for a three times. These are listed in the activity view.
|
||||
There also is a button to retry the sync for another three times.
|
||||
|
||||
For more detailed information see :ref:`ignored-files-label`.
|
||||
|
||||
Main dialog
|
||||
-----------
|
||||
|
||||
.. index:: activity, recent changes, sync activity, main dialog, adding account, account, add account, remove account, sync state, user status, unified search, share dialog
|
||||
|
||||
Sync State
|
||||
~~~~~~~~~~
|
||||
|
||||
The main dialog, which can be invoked from the tray icon in the
|
||||
taskbar, will show files information about the activities of the sync
|
||||
client and Nextcloud.
|
||||
|
||||
|
||||
.. image:: images/sync-state-paused.png
|
||||
:alt: sync state paused
|
||||
|
||||
.. image:: images/sync-state-syncing.png
|
||||
:alt: sync state syncing
|
||||
|
||||
.. image:: images/sync-state-synced.png
|
||||
:alt: sync state synced
|
||||
|
||||
|
||||
If there are any synchronization issues, they will show up here:
|
||||
|
||||
.. image:: images/sync-state-warnings.png
|
||||
:alt: sync state warnings
|
||||
|
||||
For more information on how to solve these issues see :doc:`troubleshooting`.
|
||||
|
||||
When clicking on the avatar a menu opens where it is
|
||||
possible to add a new account or removing an existing account.
|
||||
|
||||
.. image:: images/user-account-options.png
|
||||
:alt: user account options
|
||||
|
||||
|
||||
User Status
|
||||
~~~~~~~~~~~
|
||||
|
||||
User status is displayed in the Nextcloud desktop client's tray window.
|
||||
Default user status is always "Online" if no other status is available from the server-side.
|
||||
|
||||
.. image:: images/status_feature_example.png
|
||||
:alt: User Status feature in the tray window
|
||||
|
||||
When clicking on ``Set status`` you can edit the emoji, message and the timer to clear your user status:
|
||||
|
||||
.. image:: images/set-user-status.png
|
||||
:alt: set user status menu option
|
||||
|
||||
|
|
||||
|
||||
.. image:: images/set-user-status-menu.png
|
||||
:alt: changing the user status
|
||||
|
||||
Activities list
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The dialog also gives information about other activities or
|
||||
notifications like Talk mentions or file changes.
|
||||
It does also show the status of the user.
|
||||
|
||||
.. image:: images/main_dialog.png
|
||||
:alt: main dialog activities list
|
||||
|
||||
Unified search
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
With the unified search you can find everything you have in your server - files,
|
||||
Talk messages, calendar appointments:
|
||||
|
||||
.. image:: images/unified-search-files.png
|
||||
:alt: unified search files search result
|
||||
|
||||
.. image:: images/unified-search-talk.png
|
||||
:alt: unified search Talk conversations search result
|
||||
|
||||
.. image:: images/unified-search-events.png
|
||||
:alt: unified search calendar appointments search result
|
||||
|
||||
|
||||
Share dialog: Talk options and View Profile
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can now share a file directly in a conversation in Talk and view the sharee user profile:
|
||||
|
||||
.. image:: images/open-share-dialog.png
|
||||
:alt: open share dialog option
|
||||
|
||||
|
|
||||
|
||||
.. image:: images/share-dialog-view-profile.png
|
||||
:alt: shared dialog sharing options
|
||||
@@ -23,5 +23,10 @@
|
||||
<file>src/gui/tray/UnifiedSearchResultListItem.qml</file>
|
||||
<file>src/gui/tray/UnifiedSearchResultNothingFound.qml</file>
|
||||
<file>src/gui/tray/UnifiedSearchResultSectionItem.qml</file>
|
||||
<file>src/gui/tray/CustomButton.qml</file>
|
||||
<file>src/gui/tray/CustomTextButton.qml</file>
|
||||
<file>src/gui/tray/ActivityItemContextMenu.qml</file>
|
||||
<file>src/gui/tray/ActivityItemActions.qml</file>
|
||||
<file>src/gui/tray/ActivityItemContent.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
if(APPLE)
|
||||
set(OC_OEM_SHARE_ICNS "${CMAKE_BINARY_DIR}/src/gui/${APPLICATION_ICON_NAME}.icns")
|
||||
set(OC_OEM_SHARE_ICNS "${CMAKE_BINARY_DIR}/src/gui/${APPLICATION_ICON_NAME}.icns")
|
||||
|
||||
# The bundle identifier and application group need to have compatible values with the client
|
||||
# to be able to open a Mach port across the extension's sandbox boundary.
|
||||
# Pass the info through the xcodebuild command line and make sure that the project uses
|
||||
# those user-defined settings to build the plist.
|
||||
add_custom_target( mac_overlayplugin ALL
|
||||
xcodebuild -project ${CMAKE_SOURCE_DIR}/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj
|
||||
-target FinderSyncExt -configuration Release "SYMROOT=${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"OC_OEM_SHARE_ICNS=${OC_OEM_SHARE_ICNS}"
|
||||
"OC_APPLICATION_NAME=${APPLICATION_NAME}"
|
||||
"OC_APPLICATION_REV_DOMAIN=${APPLICATION_REV_DOMAIN}"
|
||||
"OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX=${SOCKETAPI_TEAM_IDENTIFIER_PREFIX}"
|
||||
# The bundle identifier and application group need to have compatible values with the client
|
||||
# to be able to open a Mach port across the extension's sandbox boundary.
|
||||
# Pass the info through the xcodebuild command line and make sure that the project uses
|
||||
# those user-defined settings to build the plist.
|
||||
add_custom_target( mac_overlayplugin ALL
|
||||
xcodebuild ARCHS=${CMAKE_OSX_ARCHITECTURES} ONLY_ACTIVE_ARCH=NO
|
||||
-project ${CMAKE_SOURCE_DIR}/shell_integration/MacOSX/OwnCloudFinderSync/OwnCloudFinderSync.xcodeproj
|
||||
-target FinderSyncExt -configuration Release "SYMROOT=${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"OC_OEM_SHARE_ICNS=${OC_OEM_SHARE_ICNS}"
|
||||
"OC_APPLICATION_NAME=${APPLICATION_NAME}"
|
||||
"OC_APPLICATION_REV_DOMAIN=${APPLICATION_REV_DOMAIN}"
|
||||
"OC_SOCKETAPI_TEAM_IDENTIFIER_PREFIX=${SOCKETAPI_TEAM_IDENTIFIER_PREFIX}"
|
||||
COMMENT building Mac Overlay icons
|
||||
VERBATIM)
|
||||
add_dependencies(mac_overlayplugin nextcloud) # for the ownCloud.icns to be generated
|
||||
add_dependencies(mac_overlayplugin nextcloud) # for the ownCloud.icns to be generated
|
||||
|
||||
INSTALL(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Release/FinderSyncExt.appex
|
||||
DESTINATION ${OWNCLOUD_OSX_BUNDLE}/Contents/PlugIns
|
||||
USE_SOURCE_PERMISSIONS)
|
||||
endif(APPLE)
|
||||
if (BUILD_OWNCLOUD_OSX_BUNDLE)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Release/FinderSyncExt.appex
|
||||
DESTINATION ${OWNCLOUD_OSX_BUNDLE}/Contents/PlugIns
|
||||
USE_SOURCE_PERMISSIONS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
280
src/3rdparty/kirigami/wheelhandler.cpp
vendored
Normal file
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "wheelhandler.h"
|
||||
#include <QWheelEvent>
|
||||
#include <QQuickItem>
|
||||
#include <QDebug>
|
||||
|
||||
class GlobalWheelFilterSingleton
|
||||
{
|
||||
public:
|
||||
GlobalWheelFilter self;
|
||||
};
|
||||
|
||||
Q_GLOBAL_STATIC(GlobalWheelFilterSingleton, privateGlobalWheelFilterSelf)
|
||||
|
||||
GlobalWheelFilter::GlobalWheelFilter(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
GlobalWheelFilter::~GlobalWheelFilter() = default;
|
||||
|
||||
GlobalWheelFilter *GlobalWheelFilter::self()
|
||||
{
|
||||
return &privateGlobalWheelFilterSelf()->self;
|
||||
}
|
||||
|
||||
void GlobalWheelFilter::setItemHandlerAssociation(QQuickItem *item, WheelHandler *handler)
|
||||
{
|
||||
if (!m_handlersForItem.contains(handler->target())) {
|
||||
handler->target()->installEventFilter(this);
|
||||
}
|
||||
m_handlersForItem.insert(item, handler);
|
||||
|
||||
connect(item, &QObject::destroyed, this, [this](QObject *obj) {
|
||||
auto item = static_cast<QQuickItem *>(obj);
|
||||
m_handlersForItem.remove(item);
|
||||
});
|
||||
|
||||
connect(handler, &QObject::destroyed, this, [this](QObject *obj) {
|
||||
auto handler = static_cast<WheelHandler *>(obj);
|
||||
removeItemHandlerAssociation(handler->target(), handler);
|
||||
});
|
||||
}
|
||||
|
||||
void GlobalWheelFilter::removeItemHandlerAssociation(QQuickItem *item, WheelHandler *handler)
|
||||
{
|
||||
if (!item || !handler) {
|
||||
return;
|
||||
}
|
||||
m_handlersForItem.remove(item, handler);
|
||||
if (!m_handlersForItem.contains(item)) {
|
||||
item->removeEventFilter(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool GlobalWheelFilter::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::Wheel) {
|
||||
auto item = qobject_cast<QQuickItem *>(watched);
|
||||
if (!item || !item->isEnabled()) {
|
||||
return QObject::eventFilter(watched, event);
|
||||
}
|
||||
auto we = static_cast<QWheelEvent *>(event);
|
||||
m_wheelEvent.initializeFromEvent(we);
|
||||
|
||||
bool shouldBlock = false;
|
||||
bool shouldScrollFlickable = false;
|
||||
|
||||
for (auto *handler : m_handlersForItem.values(item)) {
|
||||
if (handler->m_blockTargetWheel) {
|
||||
shouldBlock = true;
|
||||
}
|
||||
if (handler->m_scrollFlickableTarget) {
|
||||
shouldScrollFlickable = true;
|
||||
}
|
||||
emit handler->wheel(&m_wheelEvent);
|
||||
}
|
||||
|
||||
if (shouldScrollFlickable && !m_wheelEvent.isAccepted()) {
|
||||
manageWheel(item, we);
|
||||
}
|
||||
|
||||
if (shouldBlock) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return QObject::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
void GlobalWheelFilter::manageWheel(QQuickItem *target, QWheelEvent *event)
|
||||
{
|
||||
// Duck typing: accept everyhint that has all the properties we need
|
||||
if (target->metaObject()->indexOfProperty("contentX") == -1
|
||||
|| target->metaObject()->indexOfProperty("contentY") == -1
|
||||
|| target->metaObject()->indexOfProperty("contentWidth") == -1
|
||||
|| target->metaObject()->indexOfProperty("contentHeight") == -1
|
||||
|| target->metaObject()->indexOfProperty("topMargin") == -1
|
||||
|| target->metaObject()->indexOfProperty("bottomMargin") == -1
|
||||
|| target->metaObject()->indexOfProperty("leftMargin") == -1
|
||||
|| target->metaObject()->indexOfProperty("rightMargin") == -1
|
||||
|| target->metaObject()->indexOfProperty("originX") == -1
|
||||
|| target->metaObject()->indexOfProperty("originY") == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
qreal contentWidth = target->property("contentWidth").toReal();
|
||||
qreal contentHeight = target->property("contentHeight").toReal();
|
||||
qreal contentX = target->property("contentX").toReal();
|
||||
qreal contentY = target->property("contentY").toReal();
|
||||
qreal topMargin = target->property("topMargin").toReal();
|
||||
qreal bottomMargin = target->property("bottomMargin").toReal();
|
||||
qreal leftMargin = target->property("leftMaring").toReal();
|
||||
qreal rightMargin = target->property("rightMargin").toReal();
|
||||
qreal originX = target->property("originX").toReal();
|
||||
qreal originY = target->property("originY").toReal();
|
||||
|
||||
// Scroll Y
|
||||
if (contentHeight > target->height()) {
|
||||
|
||||
int y = event->pixelDelta().y() != 0 ? event->pixelDelta().y() : event->angleDelta().y() / 8;
|
||||
|
||||
//if we don't have a pixeldelta, apply the configured mouse wheel lines
|
||||
if (!event->pixelDelta().y()) {
|
||||
y *= 3; // Magic copied value from Kirigami::Settings
|
||||
}
|
||||
|
||||
// Scroll one page regardless of delta:
|
||||
if ((event->modifiers() & Qt::ControlModifier) || (event->modifiers() & Qt::ShiftModifier)) {
|
||||
if (y > 0) {
|
||||
y = target->height();
|
||||
} else if (y < 0) {
|
||||
y = -target->height();
|
||||
}
|
||||
}
|
||||
|
||||
qreal minYExtent = topMargin - originY;
|
||||
qreal maxYExtent = target->height() - (contentHeight + bottomMargin + originY);
|
||||
|
||||
target->setProperty("contentY", qMin(-maxYExtent, qMax(-minYExtent, contentY - y)));
|
||||
}
|
||||
|
||||
//Scroll X
|
||||
if (contentWidth > target->width()) {
|
||||
|
||||
int x = event->pixelDelta().x() != 0 ? event->pixelDelta().x() : event->angleDelta().x() / 8;
|
||||
|
||||
// Special case: when can't scroll vertically, scroll horizontally with vertical wheel as well
|
||||
if (x == 0 && contentHeight <= target->height()) {
|
||||
x = event->pixelDelta().y() != 0 ? event->pixelDelta().y() : event->angleDelta().y() / 8;
|
||||
}
|
||||
|
||||
//if we don't have a pixeldelta, apply the configured mouse wheel lines
|
||||
if (!event->pixelDelta().x()) {
|
||||
x *= 3; // Magic copied value from Kirigami::Settings
|
||||
}
|
||||
|
||||
// Scroll one page regardless of delta:
|
||||
if ((event->modifiers() & Qt::ControlModifier) || (event->modifiers() & Qt::ShiftModifier)) {
|
||||
if (x > 0) {
|
||||
x = target->width();
|
||||
} else if (x < 0) {
|
||||
x = -target->width();
|
||||
}
|
||||
}
|
||||
|
||||
qreal minXExtent = leftMargin - originX;
|
||||
qreal maxXExtent = target->width() - (contentWidth + rightMargin + originX);
|
||||
|
||||
target->setProperty("contentX", qMin(-maxXExtent, qMax(-minXExtent, contentX - x)));
|
||||
}
|
||||
|
||||
//this is just for making the scrollbar
|
||||
target->metaObject()->invokeMethod(target, "flick", Q_ARG(double, 0), Q_ARG(double, 1));
|
||||
target->metaObject()->invokeMethod(target, "cancelFlick");
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////
|
||||
KirigamiWheelEvent::KirigamiWheelEvent(QObject *parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
KirigamiWheelEvent::~KirigamiWheelEvent() = default;
|
||||
|
||||
void KirigamiWheelEvent::initializeFromEvent(QWheelEvent *event)
|
||||
{
|
||||
m_x = event->position().x();
|
||||
m_y = event->position().y();
|
||||
m_angleDelta = event->angleDelta();
|
||||
m_pixelDelta = event->pixelDelta();
|
||||
m_buttons = event->buttons();
|
||||
m_modifiers = event->modifiers();
|
||||
m_accepted = false;
|
||||
m_inverted = event->inverted();
|
||||
}
|
||||
|
||||
qreal KirigamiWheelEvent::x() const
|
||||
{
|
||||
return m_x;
|
||||
}
|
||||
|
||||
qreal KirigamiWheelEvent::y() const
|
||||
{
|
||||
return m_y;
|
||||
}
|
||||
|
||||
QPointF KirigamiWheelEvent::angleDelta() const
|
||||
{
|
||||
return m_angleDelta;
|
||||
}
|
||||
|
||||
QPointF KirigamiWheelEvent::pixelDelta() const
|
||||
{
|
||||
return m_pixelDelta;
|
||||
}
|
||||
|
||||
int KirigamiWheelEvent::buttons() const
|
||||
{
|
||||
return m_buttons;
|
||||
}
|
||||
|
||||
int KirigamiWheelEvent::modifiers() const
|
||||
{
|
||||
return m_modifiers;
|
||||
}
|
||||
|
||||
bool KirigamiWheelEvent::inverted() const
|
||||
{
|
||||
return m_inverted;
|
||||
}
|
||||
|
||||
bool KirigamiWheelEvent::isAccepted()
|
||||
{
|
||||
return m_accepted;
|
||||
}
|
||||
|
||||
void KirigamiWheelEvent::setAccepted(bool accepted)
|
||||
{
|
||||
m_accepted = accepted;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
WheelHandler::WheelHandler(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
WheelHandler::~WheelHandler() = default;
|
||||
|
||||
QQuickItem *WheelHandler::target() const
|
||||
{
|
||||
return m_target;
|
||||
}
|
||||
|
||||
void WheelHandler::setTarget(QQuickItem *target)
|
||||
{
|
||||
if (m_target == target) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_target) {
|
||||
GlobalWheelFilter::self()->removeItemHandlerAssociation(m_target, this);
|
||||
}
|
||||
|
||||
m_target = target;
|
||||
|
||||
GlobalWheelFilter::self()->setItemHandlerAssociation(target, this);
|
||||
|
||||
emit targetChanged();
|
||||
}
|
||||
|
||||
|
||||
#include "moc_wheelhandler.cpp"
|
||||
213
src/3rdparty/kirigami/wheelhandler.h
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtQml>
|
||||
#include <QPoint>
|
||||
#include <QQuickItem>
|
||||
#include <QObject>
|
||||
|
||||
class QWheelEvent;
|
||||
|
||||
class WheelHandler;
|
||||
|
||||
/**
|
||||
* Describes the mouse wheel event
|
||||
*/
|
||||
class KirigamiWheelEvent : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* x: real
|
||||
*
|
||||
* X coordinate of the mouse pointer
|
||||
*/
|
||||
Q_PROPERTY(qreal x READ x CONSTANT)
|
||||
|
||||
/**
|
||||
* y: real
|
||||
*
|
||||
* Y coordinate of the mouse pointer
|
||||
*/
|
||||
Q_PROPERTY(qreal y READ y CONSTANT)
|
||||
|
||||
/**
|
||||
* angleDelta: point
|
||||
*
|
||||
* The distance the wheel is rotated in degrees.
|
||||
* The x and y coordinates indicate the horizontal and vertical wheels respectively.
|
||||
* A positive value indicates it was rotated up/right, negative, bottom/left
|
||||
* This value is more likely to be set in traditional mice.
|
||||
*/
|
||||
Q_PROPERTY(QPointF angleDelta READ angleDelta CONSTANT)
|
||||
|
||||
/**
|
||||
* pixelDelta: point
|
||||
*
|
||||
* provides the delta in screen pixels available on high resolution trackpads
|
||||
*/
|
||||
Q_PROPERTY(QPointF pixelDelta READ pixelDelta CONSTANT)
|
||||
|
||||
/**
|
||||
* buttons: int
|
||||
*
|
||||
* it contains an OR combination of the buttons that were pressed during the wheel, they can be:
|
||||
* Qt.LeftButton, Qt.MiddleButton, Qt.RightButton
|
||||
*/
|
||||
Q_PROPERTY(int buttons READ buttons CONSTANT)
|
||||
|
||||
/**
|
||||
* modifiers: int
|
||||
*
|
||||
* Keyboard mobifiers that were pressed during the wheel event, such as:
|
||||
* Qt.NoModifier (default, no modifiers)
|
||||
* Qt.ControlModifier
|
||||
* Qt.ShiftModifier
|
||||
* ...
|
||||
*/
|
||||
Q_PROPERTY(int modifiers READ modifiers CONSTANT)
|
||||
|
||||
/**
|
||||
* inverted: bool
|
||||
*
|
||||
* Whether the delta values are inverted
|
||||
* On some platformsthe returned delta are inverted, so positive values would mean bottom/left
|
||||
*/
|
||||
Q_PROPERTY(bool inverted READ inverted CONSTANT)
|
||||
|
||||
/**
|
||||
* accepted: bool
|
||||
*
|
||||
* If set, the event shouldn't be managed anymore,
|
||||
* for instance it can be used to block the handler to manage the scroll of a view on some scenarios
|
||||
* @code
|
||||
* // This handler handles automatically the scroll of
|
||||
* // flickableItem, unless Ctrl is pressed, in this case the
|
||||
* // app has custom code to handle Ctrl+wheel zooming
|
||||
* Kirigami.WheelHandler {
|
||||
* target: flickableItem
|
||||
* blockTargetWheel: true
|
||||
* scrollFlickableTarget: true
|
||||
* onWheel: {
|
||||
* if (wheel.modifiers & Qt.ControlModifier) {
|
||||
* wheel.accepted = true;
|
||||
* // Handle scaling of the view
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
*/
|
||||
Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted)
|
||||
|
||||
public:
|
||||
KirigamiWheelEvent(QObject *parent = nullptr);
|
||||
~KirigamiWheelEvent() override;
|
||||
|
||||
void initializeFromEvent(QWheelEvent *event);
|
||||
|
||||
qreal x() const;
|
||||
qreal y() const;
|
||||
QPointF angleDelta() const;
|
||||
QPointF pixelDelta() const;
|
||||
int buttons() const;
|
||||
int modifiers() const;
|
||||
bool inverted() const;
|
||||
bool isAccepted();
|
||||
void setAccepted(bool accepted);
|
||||
|
||||
private:
|
||||
qreal m_x = 0;
|
||||
qreal m_y = 0;
|
||||
QPointF m_angleDelta;
|
||||
QPointF m_pixelDelta;
|
||||
Qt::MouseButtons m_buttons = Qt::NoButton;
|
||||
Qt::KeyboardModifiers m_modifiers = Qt::NoModifier;
|
||||
bool m_inverted = false;
|
||||
bool m_accepted = false;
|
||||
};
|
||||
|
||||
class GlobalWheelFilter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GlobalWheelFilter(QObject *parent = nullptr);
|
||||
~GlobalWheelFilter() override;
|
||||
|
||||
static GlobalWheelFilter *self();
|
||||
|
||||
void setItemHandlerAssociation(QQuickItem *item, WheelHandler *handler);
|
||||
void removeItemHandlerAssociation(QQuickItem *item, WheelHandler *handler);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
private:
|
||||
void manageWheel(QQuickItem *target, QWheelEvent *wheel);
|
||||
|
||||
QMultiHash<QQuickItem *, WheelHandler *> m_handlersForItem;
|
||||
KirigamiWheelEvent m_wheelEvent;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This class intercepts the mouse wheel events of its target, and gives them to the user code as a signal, which can be used for custom mouse wheel management code.
|
||||
* The handler can block completely the wheel events from its target, and if it's a Flickable, it can automatically handle scrolling on it
|
||||
*/
|
||||
class WheelHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* target: Item
|
||||
*
|
||||
* The target we want to manage wheel events.
|
||||
* We will receive wheel() signals every time the user moves
|
||||
* the mouse wheel (or scrolls with the touchpad) on top
|
||||
* of that item.
|
||||
*/
|
||||
Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged)
|
||||
|
||||
/**
|
||||
* blockTargetWheel: bool
|
||||
*
|
||||
* If true, the target won't receive any wheel event at all (default true)
|
||||
*/
|
||||
Q_PROPERTY(bool blockTargetWheel MEMBER m_blockTargetWheel NOTIFY blockTargetWheelChanged)
|
||||
|
||||
/**
|
||||
* scrollFlickableTarget: bool
|
||||
* If this property is true and the target is a Flickable, wheel events will cause the Flickable to scroll (default true)
|
||||
*/
|
||||
Q_PROPERTY(bool scrollFlickableTarget MEMBER m_scrollFlickableTarget NOTIFY scrollFlickableTargetChanged)
|
||||
|
||||
public:
|
||||
explicit WheelHandler(QObject *parent = nullptr);
|
||||
~WheelHandler() override;
|
||||
|
||||
QQuickItem *target() const;
|
||||
void setTarget(QQuickItem *target);
|
||||
|
||||
Q_SIGNALS:
|
||||
void targetChanged();
|
||||
void blockTargetWheelChanged();
|
||||
void scrollFlickableTargetChanged();
|
||||
void wheel(KirigamiWheelEvent *wheel);
|
||||
|
||||
private:
|
||||
QPointer<QQuickItem> m_target;
|
||||
bool m_blockTargetWheel = true;
|
||||
bool m_scrollFlickableTarget = true;
|
||||
KirigamiWheelEvent m_wheelEvent;
|
||||
|
||||
friend class GlobalWheelFilter;
|
||||
};
|
||||
|
||||
|
||||
@@ -337,7 +337,7 @@ ComputeChecksum *ValidateChecksumHeader::prepareStart(const QByteArray &checksum
|
||||
|
||||
if (!parseChecksumHeader(checksumHeader, &_expectedChecksumType, &_expectedChecksum)) {
|
||||
qCWarning(lcChecksums) << "Checksum header malformed:" << checksumHeader;
|
||||
emit validationFailed(tr("The checksum header is malformed."));
|
||||
emit validationFailed(tr("The checksum header is malformed."), _calculatedChecksumType, _calculatedChecksum, ChecksumHeaderMalformed);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -360,15 +360,30 @@ void ValidateChecksumHeader::start(std::unique_ptr<QIODevice> device, const QByt
|
||||
calculator->start(std::move(device));
|
||||
}
|
||||
|
||||
QByteArray ValidateChecksumHeader::calculatedChecksumType() const
|
||||
{
|
||||
return _calculatedChecksumType;
|
||||
}
|
||||
|
||||
QByteArray ValidateChecksumHeader::calculatedChecksum() const
|
||||
{
|
||||
return _calculatedChecksum;
|
||||
}
|
||||
|
||||
void ValidateChecksumHeader::slotChecksumCalculated(const QByteArray &checksumType,
|
||||
const QByteArray &checksum)
|
||||
{
|
||||
_calculatedChecksumType = checksumType;
|
||||
_calculatedChecksum = checksum;
|
||||
|
||||
if (checksumType != _expectedChecksumType) {
|
||||
emit validationFailed(tr("The checksum header contained an unknown checksum type \"%1\"").arg(QString::fromLatin1(_expectedChecksumType)));
|
||||
emit validationFailed(tr("The checksum header contained an unknown checksum type \"%1\"").arg(QString::fromLatin1(_expectedChecksumType)),
|
||||
_calculatedChecksumType, _calculatedChecksum, ChecksumTypeUnknown);
|
||||
return;
|
||||
}
|
||||
if (checksum != _expectedChecksum) {
|
||||
emit validationFailed(tr(R"(The downloaded file does not match the checksum, it will be resumed. "%1" != "%2")").arg(QString::fromUtf8(_expectedChecksum), QString::fromUtf8(checksum)));
|
||||
emit validationFailed(tr(R"(The downloaded file does not match the checksum, it will be resumed. "%1" != "%2")").arg(QString::fromUtf8(_expectedChecksum), QString::fromUtf8(checksum)),
|
||||
_calculatedChecksumType, _calculatedChecksum, ChecksumMismatch);
|
||||
return;
|
||||
}
|
||||
emit validated(checksumType, checksum);
|
||||
|
||||
@@ -140,6 +140,14 @@ class OCSYNC_EXPORT ValidateChecksumHeader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum FailureReason {
|
||||
Success,
|
||||
ChecksumHeaderMalformed,
|
||||
ChecksumTypeUnknown,
|
||||
ChecksumMismatch,
|
||||
};
|
||||
Q_ENUM(FailureReason)
|
||||
|
||||
explicit ValidateChecksumHeader(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
@@ -161,9 +169,13 @@ public:
|
||||
*/
|
||||
void start(std::unique_ptr<QIODevice> device, const QByteArray &checksumHeader);
|
||||
|
||||
QByteArray calculatedChecksumType() const;
|
||||
QByteArray calculatedChecksum() const;
|
||||
|
||||
signals:
|
||||
void validated(const QByteArray &checksumType, const QByteArray &checksum);
|
||||
void validationFailed(const QString &errMsg);
|
||||
void validationFailed(const QString &errMsg, const QByteArray &calculatedChecksumType,
|
||||
const QByteArray &calculatedChecksum, const ValidateChecksumHeader::FailureReason reason);
|
||||
|
||||
private slots:
|
||||
void slotChecksumCalculated(const QByteArray &checksumType, const QByteArray &checksum);
|
||||
@@ -173,6 +185,9 @@ private:
|
||||
|
||||
QByteArray _expectedChecksumType;
|
||||
QByteArray _expectedChecksum;
|
||||
|
||||
QByteArray _calculatedChecksumType;
|
||||
QByteArray _calculatedChecksum;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1001,43 +1001,22 @@ qint64 SyncJournalDb::keyValueStoreGetInt(const QString &key, qint64 defaultValu
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const auto query = _queryManager.get(PreparedSqlQueryManager::GetKeyValueStoreQuery, QByteArrayLiteral("SELECT value FROM key_value_store WHERE key = ?1;"), _db);
|
||||
const auto query = _queryManager.get(PreparedSqlQueryManager::GetKeyValueStoreQuery, QByteArrayLiteral("SELECT value FROM key_value_store WHERE key=?1"), _db);
|
||||
if (!query) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
query->bindValue(1, key);
|
||||
query->exec();
|
||||
auto result = query->next();
|
||||
|
||||
if (!query->next().hasData) {
|
||||
if (!result.ok || !result.hasData) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return query->int64Value(0);
|
||||
}
|
||||
|
||||
QVariant SyncJournalDb::keyValueStoreGet(const QString &key, QVariant defaultValue)
|
||||
{
|
||||
QMutexLocker locker(&_mutex);
|
||||
if (!checkConnect()) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const auto query = _queryManager.get(PreparedSqlQueryManager::GetKeyValueStoreQuery, QByteArrayLiteral("SELECT value FROM key_value_store WHERE key = ?1;"), _db);
|
||||
if (!query) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
query->bindValue(1, key);
|
||||
query->exec();
|
||||
|
||||
if (!query->next().hasData) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return query->stringValue(0);
|
||||
}
|
||||
|
||||
void SyncJournalDb::keyValueStoreDelete(const QString &key)
|
||||
{
|
||||
const auto query = _queryManager.get(PreparedSqlQueryManager::DeleteKeyValueStoreQuery, QByteArrayLiteral("DELETE FROM key_value_store WHERE key=?1;"), _db);
|
||||
|
||||
@@ -70,7 +70,6 @@ public:
|
||||
|
||||
void keyValueStoreSet(const QString &key, QVariant value);
|
||||
qint64 keyValueStoreGetInt(const QString &key, qint64 defaultValue);
|
||||
QVariant keyValueStoreGet(const QString &key, QVariant defaultValue = {});
|
||||
void keyValueStoreDelete(const QString &key);
|
||||
|
||||
bool deleteFileRecord(const QString &filename, bool recursively = false);
|
||||
|
||||
@@ -109,6 +109,11 @@ void Utility::setupFavLink(const QString &folder)
|
||||
setupFavLink_private(folder);
|
||||
}
|
||||
|
||||
void Utility::removeFavLink(const QString &folder)
|
||||
{
|
||||
removeFavLink_private(folder);
|
||||
}
|
||||
|
||||
QString Utility::octetsToString(qint64 octets)
|
||||
{
|
||||
#define THE_FACTOR 1024
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace Utility {
|
||||
OCSYNC_EXPORT void usleep(int usec);
|
||||
OCSYNC_EXPORT QString formatFingerprint(const QByteArray &, bool colonSeparated = true);
|
||||
OCSYNC_EXPORT void setupFavLink(const QString &folder);
|
||||
OCSYNC_EXPORT void removeFavLink(const QString &folder);
|
||||
OCSYNC_EXPORT bool writeRandomFile(const QString &fname, int size = -1);
|
||||
OCSYNC_EXPORT QString octetsToString(qint64 octets);
|
||||
OCSYNC_EXPORT QByteArray userAgentString();
|
||||
@@ -241,6 +242,11 @@ namespace Utility {
|
||||
*/
|
||||
OCSYNC_EXPORT bool isPathWindowsDrivePartitionRoot(const QString &path);
|
||||
|
||||
/**
|
||||
* @brief Retrieves current logged-in user name from the OS
|
||||
*/
|
||||
OCSYNC_EXPORT QString getCurrentUserName();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
OCSYNC_EXPORT bool registryKeyExists(HKEY hRootKey, const QString &subKey);
|
||||
OCSYNC_EXPORT QVariant registryGetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName);
|
||||
|
||||
@@ -41,6 +41,11 @@ static void setupFavLink_private(const QString &folder)
|
||||
CFRelease(urlRef);
|
||||
}
|
||||
|
||||
static void removeFavLink_private(const QString &folder)
|
||||
{
|
||||
Q_UNUSED(folder)
|
||||
}
|
||||
|
||||
bool hasLaunchOnStartup_private(const QString &)
|
||||
{
|
||||
// this is quite some duplicate code with setLaunchOnStartup, at some point we should fix this FIXME.
|
||||
@@ -131,4 +136,9 @@ static bool hasDarkSystray_private()
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
QString Utility::getCurrentUserName()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
@@ -37,6 +37,11 @@ static void setupFavLink_private(const QString &folder)
|
||||
}
|
||||
}
|
||||
|
||||
static void removeFavLink_private(const QString &folder)
|
||||
{
|
||||
Q_UNUSED(folder)
|
||||
}
|
||||
|
||||
// returns the autostart directory the linux way
|
||||
// and respects the XDG_CONFIG_HOME env variable
|
||||
QString getUserAutostartDir_private()
|
||||
@@ -103,4 +108,9 @@ static inline bool hasDarkSystray_private()
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Utility::getCurrentUserName()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
|
||||
#include "asserts.h"
|
||||
#include "utility.h"
|
||||
#include "gui/configgui.h"
|
||||
|
||||
#include <comdef.h>
|
||||
#include <Lmcons.h>
|
||||
#include <shlguid.h>
|
||||
#include <shlobj.h>
|
||||
#include <string>
|
||||
@@ -47,7 +49,14 @@ static void setupFavLink_private(const QString &folder)
|
||||
desktopIni.open(QFile::WriteOnly);
|
||||
desktopIni.write("[.ShellClassInfo]\r\nIconResource=");
|
||||
desktopIni.write(QDir::toNativeSeparators(qApp->applicationFilePath()).toUtf8());
|
||||
desktopIni.write(",0\r\n");
|
||||
#ifdef APPLICATION_FOLDER_ICON_INDEX
|
||||
const auto iconIndex = APPLICATION_FOLDER_ICON_INDEX;
|
||||
#else
|
||||
const auto iconIndex = "0";
|
||||
#endif
|
||||
desktopIni.write(",");
|
||||
desktopIni.write(iconIndex);
|
||||
desktopIni.write("\r\n");
|
||||
desktopIni.close();
|
||||
|
||||
// Set the folder as system and Desktop.ini as hidden+system for explorer to pick it.
|
||||
@@ -74,6 +83,40 @@ static void setupFavLink_private(const QString &folder)
|
||||
qCWarning(lcUtility) << "linking" << folder << "to" << linkName << "failed!";
|
||||
}
|
||||
|
||||
static void removeFavLink_private(const QString &folder)
|
||||
{
|
||||
const QDir folderDir(folder);
|
||||
|
||||
// #1 Remove the Desktop.ini to reset the folder icon
|
||||
if (!QFile::remove(folderDir.absoluteFilePath(QLatin1String("Desktop.ini")))) {
|
||||
qCWarning(lcUtility) << "Remove Desktop.ini from" << folder
|
||||
<< " has failed. Make sure it exists and is not locked by another process.";
|
||||
}
|
||||
|
||||
// #2 Remove the system file attribute
|
||||
const auto folderAttrs = GetFileAttributesW(folder.toStdWString().c_str());
|
||||
if (!SetFileAttributesW(folder.toStdWString().c_str(), folderAttrs & ~FILE_ATTRIBUTE_SYSTEM)) {
|
||||
qCWarning(lcUtility) << "Remove system file attribute failed for:" << folder;
|
||||
}
|
||||
|
||||
// #3 Remove the link to this folder
|
||||
PWSTR path;
|
||||
if (!SHGetKnownFolderPath(FOLDERID_Links, 0, nullptr, &path) == S_OK) {
|
||||
qCWarning(lcUtility) << "SHGetKnownFolderPath for " << folder << "has failed.";
|
||||
return;
|
||||
}
|
||||
|
||||
const QDir links(QString::fromWCharArray(path));
|
||||
CoTaskMemFree(path);
|
||||
|
||||
const auto linkName = QDir(links).absoluteFilePath(folderDir.dirName() + QLatin1String(".lnk"));
|
||||
|
||||
qCInfo(lcUtility) << "Removing favorite link from" << folder << "to" << linkName;
|
||||
if (!QFile::remove(linkName)) {
|
||||
qCWarning(lcUtility) << "Removing a favorite link from" << folder << "to" << linkName << "failed.";
|
||||
}
|
||||
}
|
||||
|
||||
bool hasSystemLaunchOnStartup_private(const QString &appName)
|
||||
{
|
||||
QString runPath = QLatin1String(systemRunPathC);
|
||||
@@ -346,6 +389,17 @@ QString Utility::formatWinError(long errorCode)
|
||||
return QStringLiteral("WindowsError: %1: %2").arg(QString::number(errorCode, 16), QString::fromWCharArray(_com_error(errorCode).ErrorMessage()));
|
||||
}
|
||||
|
||||
QString Utility::getCurrentUserName()
|
||||
{
|
||||
TCHAR username[UNLEN + 1] = {0};
|
||||
DWORD len = sizeof(username) / sizeof(TCHAR);
|
||||
|
||||
if (!GetUserName(username, &len)) {
|
||||
qCWarning(lcUtility) << "Could not retrieve Windows user name." << formatWinError(GetLastError());
|
||||
}
|
||||
|
||||
return QString::fromWCharArray(username);
|
||||
}
|
||||
|
||||
Utility::NtfsPermissionLookupRAII::NtfsPermissionLookupRAII()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
project(gui)
|
||||
find_package(Qt5 REQUIRED COMPONENTS Widgets Svg Qml Quick QuickControls2)
|
||||
find_package(Qt5 REQUIRED COMPONENTS Widgets Svg Qml Quick QuickControls2 Xml Network)
|
||||
|
||||
if(QUICK_COMPILER)
|
||||
find_package(Qt5QuickCompiler)
|
||||
set_package_properties(Qt5QuickCompiler PROPERTIES
|
||||
DESCRIPTION "Compile QML at build time"
|
||||
TYPE REQUIRED
|
||||
)
|
||||
endif()
|
||||
|
||||
if (NOT TARGET Qt5::GuiPrivate)
|
||||
message(FATAL_ERROR "Could not find GuiPrivate component of Qt5. It might be shipped as a separate package, please check that.")
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
add_definitions(-DQT_QML_DEBUG)
|
||||
@@ -10,14 +22,12 @@ IF(BUILD_UPDATER)
|
||||
endif()
|
||||
|
||||
configure_file(${CMAKE_SOURCE_DIR}/theme.qrc.in ${CMAKE_SOURCE_DIR}/theme.qrc)
|
||||
|
||||
set(MIRALL_RC_SRC ../../resources.qrc)
|
||||
list(APPEND MIRALL_RC_SRC ${CMAKE_SOURCE_DIR}/theme.qrc)
|
||||
set(theme_dir ${CMAKE_SOURCE_DIR}/theme)
|
||||
|
||||
set(client_UI_SRCS
|
||||
accountsettings.ui
|
||||
conflictdialog.ui
|
||||
internallinkwidget.ui
|
||||
invalidfilenamedialog.ui
|
||||
foldercreationdialog.ui
|
||||
folderwizardsourcepage.ui
|
||||
@@ -34,23 +44,9 @@ set(client_UI_SRCS
|
||||
shareuserline.ui
|
||||
sslerrordialog.ui
|
||||
addcertificatedialog.ui
|
||||
passwordinputdialog.ui
|
||||
proxyauthdialog.ui
|
||||
mnemonicdialog.ui
|
||||
UserStatusSelector.qml
|
||||
UserStatusSelectorDialog.qml
|
||||
tray/ActivityActionButton.qml
|
||||
tray/ActivityItem.qml
|
||||
tray/ActivityList.qml
|
||||
tray/Window.qml
|
||||
tray/UserLine.qml
|
||||
tray/UnifiedSearchInputContainer.qml
|
||||
tray/UnifiedSearchResultFetchMoreTrigger.qml
|
||||
tray/UnifiedSearchResultItem.qml
|
||||
tray/UnifiedSearchResultItemSkeleton.qml
|
||||
tray/UnifiedSearchResultItemSkeletonContainer.qml
|
||||
tray/UnifiedSearchResultListItem.qml
|
||||
tray/UnifiedSearchResultNothingFound.qml
|
||||
tray/UnifiedSearchResultSectionItem.qml
|
||||
wizard/flow2authwidget.ui
|
||||
wizard/owncloudadvancedsetuppage.ui
|
||||
wizard/owncloudconnectionmethoddialog.ui
|
||||
@@ -61,6 +57,12 @@ set(client_UI_SRCS
|
||||
wizard/welcomepage.ui
|
||||
)
|
||||
|
||||
if(QUICK_COMPILER)
|
||||
qtquick_compiler_add_resources(client_UI_SRCS ../../resources.qrc ${CMAKE_SOURCE_DIR}/theme.qrc)
|
||||
else()
|
||||
qt_add_resources(client_UI_SRCS ../../resources.qrc ${CMAKE_SOURCE_DIR}/theme.qrc)
|
||||
endif()
|
||||
|
||||
set(client_SRCS
|
||||
accountmanager.cpp
|
||||
accountsettings.cpp
|
||||
@@ -79,6 +81,7 @@ set(client_SRCS
|
||||
folderwizard.cpp
|
||||
generalsettings.cpp
|
||||
legalnotice.cpp
|
||||
internallinkwidget.cpp
|
||||
ignorelisteditor.cpp
|
||||
ignorelisttablewidget.cpp
|
||||
lockwatcher.cpp
|
||||
@@ -92,6 +95,7 @@ set(client_SRCS
|
||||
openfilemanager.cpp
|
||||
owncloudgui.cpp
|
||||
owncloudsetupwizard.cpp
|
||||
passwordinputdialog.cpp
|
||||
selectivesyncdialog.cpp
|
||||
settingsdialog.cpp
|
||||
sharedialog.cpp
|
||||
@@ -203,6 +207,7 @@ set(3rdparty_SRC
|
||||
../3rdparty/qtsingleapplication/qtsingleapplication.cpp
|
||||
../3rdparty/qtsingleapplication/qtsinglecoreapplication.cpp
|
||||
../3rdparty/kmessagewidget/kmessagewidget.cpp
|
||||
../3rdparty/kirigami/wheelhandler.cpp
|
||||
)
|
||||
|
||||
if(NOT WIN32)
|
||||
@@ -228,7 +233,6 @@ IF( WIN32 )
|
||||
ENDIF()
|
||||
|
||||
set( final_src
|
||||
${MIRALL_RC_SRC}
|
||||
${client_SRCS}
|
||||
${client_UI_SRCS}
|
||||
${guiMoc}
|
||||
@@ -254,6 +258,10 @@ if (NOT DEFINED APPLICATION_ICON_NAME)
|
||||
set(APPLICATION_ICON_NAME ${APPLICATION_SHORTNAME})
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED APPLICATION_FOLDER_ICON_INDEX)
|
||||
set(APPLICATION_FOLDER_ICON_INDEX 0)
|
||||
endif()
|
||||
|
||||
# Generate png icons from svg
|
||||
find_program(SVG_CONVERTER
|
||||
NAMES inkscape inkscape.exe rsvg-convert
|
||||
@@ -265,9 +273,24 @@ if (NOT SVG_CONVERTER)
|
||||
endif()
|
||||
|
||||
function(generate_sized_png_from_svg icon_path size)
|
||||
set(options)
|
||||
set(oneValueArgs OUTPUT_ICON_NAME OUTPUT_ICON_PATH)
|
||||
set(multiValueArgs)
|
||||
|
||||
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
get_filename_component(icon_name_dir ${icon_path} DIRECTORY)
|
||||
get_filename_component(icon_name_wle ${icon_path} NAME_WLE)
|
||||
|
||||
if (ARG_OUTPUT_ICON_NAME)
|
||||
set(icon_name_wle ${ARG_OUTPUT_ICON_NAME})
|
||||
endif ()
|
||||
|
||||
if (ARG_OUTPUT_ICON_PATH)
|
||||
set(icon_name_dir ${ARG_OUTPUT_ICON_PATH})
|
||||
endif ()
|
||||
|
||||
|
||||
if (EXISTS "${icon_name_dir}/${size}-${icon_name_wle}.png")
|
||||
return()
|
||||
endif()
|
||||
@@ -313,22 +336,86 @@ if(WIN32)
|
||||
endif()
|
||||
|
||||
set(APP_ICON_SVG "${theme_dir}/colored/${APPLICATION_ICON_NAME}-icon.svg")
|
||||
generate_sized_png_from_svg(${APP_ICON_SVG} 16)
|
||||
generate_sized_png_from_svg(${APP_ICON_SVG} 24)
|
||||
generate_sized_png_from_svg(${APP_ICON_SVG} 32)
|
||||
generate_sized_png_from_svg(${APP_ICON_SVG} 48)
|
||||
generate_sized_png_from_svg(${APP_ICON_SVG} 64)
|
||||
generate_sized_png_from_svg(${APP_ICON_SVG} 128)
|
||||
generate_sized_png_from_svg(${APP_ICON_SVG} 256)
|
||||
generate_sized_png_from_svg(${APP_ICON_SVG} 512)
|
||||
generate_sized_png_from_svg(${APP_ICON_SVG} 1024)
|
||||
|
||||
# generate secondary icon if available (currently for Windows only)--------------------------------------
|
||||
set(APP_SECONDARY_ICONS "${theme_dir}/colored/icons")
|
||||
set(APP_ICON_WIN_FOLDER_SVG "${APP_SECONDARY_ICONS}/${APPLICATION_ICON_NAME}-icon-win-folder.svg")
|
||||
|
||||
set(RC_DEPENDENCIES "")
|
||||
|
||||
if(WIN32)
|
||||
if (EXISTS ${APP_ICON_WIN_FOLDER_SVG})
|
||||
get_filename_component(output_icon_name_win ${APP_ICON_WIN_FOLDER_SVG} NAME_WLE)
|
||||
# Product icon (for smallest size)
|
||||
foreach(size IN ITEMS 16;20)
|
||||
generate_sized_png_from_svg(${APP_ICON_SVG} ${size} OUTPUT_ICON_NAME ${output_icon_name_win} OUTPUT_ICON_PATH "${APP_SECONDARY_ICONS}/")
|
||||
endforeach()
|
||||
|
||||
# Product icon with Windows folder (for sizes larger than 20)
|
||||
foreach(size IN ITEMS 24;32;40;48;64;128;256;512;1024)
|
||||
generate_sized_png_from_svg(${APP_ICON_WIN_FOLDER_SVG} ${size} OUTPUT_ICON_NAME ${output_icon_name_win} OUTPUT_ICON_PATH "${APP_SECONDARY_ICONS}/")
|
||||
endforeach()
|
||||
|
||||
file(GLOB_RECURSE OWNCLOUD_ICONS_WIN_FOLDER "${APP_SECONDARY_ICONS}/*-${APPLICATION_ICON_NAME}-icon*")
|
||||
set(APP_ICON_WIN_FOLDER_ICO_NAME "${APPLICATION_ICON_NAME}-win-folder")
|
||||
set(RC_DEPENDENCIES "${RC_DEPENDENCIES} ${APP_ICON_WIN_FOLDER_ICO_NAME}.ico")
|
||||
ecm_add_app_icon(APP_ICON_WIN_FOLDER ICONS "${OWNCLOUD_ICONS_WIN_FOLDER}" SIDEBAR_ICONS "${OWNCLOUD_SIDEBAR_ICONS}" OUTFILE_BASENAME "${APP_ICON_WIN_FOLDER_ICO_NAME}" ICON_INDEX 2)
|
||||
endif()
|
||||
endif()
|
||||
# --------------------------------------
|
||||
|
||||
if (NOT ${RC_DEPENDENCIES} STREQUAL "")
|
||||
string(STRIP ${RC_DEPENDENCIES} RC_DEPENDENCIES)
|
||||
endif()
|
||||
|
||||
# generate primary icon from SVG (due to Win .ico vs .rc dependency issues, primary icon must always be generated last)--------------------------------------
|
||||
if(WIN32)
|
||||
foreach(size IN ITEMS 16;20;24;32;40;48;64;128;256;512;1024)
|
||||
generate_sized_png_from_svg(${APP_ICON_SVG} ${size})
|
||||
endforeach()
|
||||
else()
|
||||
foreach(size IN ITEMS 16;24;32;48;64;128;256;512;1024)
|
||||
generate_sized_png_from_svg(${APP_ICON_SVG} ${size})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
file(GLOB_RECURSE OWNCLOUD_ICONS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-icon*")
|
||||
|
||||
if(APPLE)
|
||||
file(GLOB_RECURSE OWNCLOUD_SIDEBAR_ICONS "${theme_dir}/colored/*-${APPLICATION_ICON_NAME}-sidebar*")
|
||||
MESSAGE(STATUS "OWNCLOUD_SIDEBAR_ICONS: ${APPLICATION_ICON_NAME}: ${OWNCLOUD_SIDEBAR_ICONS}")
|
||||
endif()
|
||||
ecm_add_app_icon(APP_ICON ICONS "${OWNCLOUD_ICONS}" SIDEBAR_ICONS "${OWNCLOUD_SIDEBAR_ICONS}" OUTFILE_BASENAME "${APPLICATION_ICON_NAME}")
|
||||
|
||||
ecm_add_app_icon(APP_ICON RC_DEPENDENCIES ${RC_DEPENDENCIES} ICONS "${OWNCLOUD_ICONS}" SIDEBAR_ICONS "${OWNCLOUD_SIDEBAR_ICONS}" OUTFILE_BASENAME "${APPLICATION_ICON_NAME}" ICON_INDEX 1)
|
||||
# --------------------------------------
|
||||
|
||||
if(WIN32)
|
||||
# merge *.rc.in files for Windows (multiple ICON resources must be placed in a single file, otherwise, this won't work de to a bug in Windows compiler https://developercommunity.visualstudio.com/t/visual-studio-2017-prof-1557-cvt1100-duplicate-res/363156)
|
||||
function(merge_files IN_FILE OUT_FILE)
|
||||
file(READ ${IN_FILE} CONTENTS)
|
||||
message("Merging ${IN_FILE} into ${OUT_FILE}")
|
||||
file(APPEND ${OUT_FILE} "${CONTENTS}")
|
||||
endfunction()
|
||||
message("APP_ICON is: ${APP_ICON}")
|
||||
if(APP_ICON)
|
||||
get_filename_component(RC_IN_FOLDER ${APP_ICON}} DIRECTORY)
|
||||
|
||||
file(GLOB_RECURSE RC_IN_FILES "${RC_IN_FOLDER}/*rc.in")
|
||||
|
||||
foreach(rc_in_file IN ITEMS ${RC_IN_FILES})
|
||||
get_filename_component(rc_in_file_name ${rc_in_file} NAME)
|
||||
get_filename_component(app_icon_name "${APP_ICON}.in" NAME)
|
||||
if(NOT "${rc_in_file_name}" STREQUAL "${app_icon_name}")
|
||||
merge_files(${rc_in_file} "${APP_ICON}.in")
|
||||
if (DEFINED APPLICATION_FOLDER_ICON_INDEX)
|
||||
MATH(EXPR APPLICATION_FOLDER_ICON_INDEX "${APPLICATION_FOLDER_ICON_INDEX}+1")
|
||||
message("APPLICATION_FOLDER_ICON_INDEX is now set to: ${APPLICATION_FOLDER_ICON_INDEX}")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
endif()
|
||||
# --------------------------------------
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIE")
|
||||
@@ -364,7 +451,6 @@ endif()
|
||||
set_target_properties(nextcloudCore
|
||||
PROPERTIES
|
||||
AUTOUIC ON
|
||||
AUTORCC ON
|
||||
AUTOMOC ON
|
||||
)
|
||||
|
||||
@@ -372,6 +458,7 @@ target_include_directories(nextcloudCore
|
||||
PUBLIC
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/QProgressIndicator
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/qtlockedfile
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/kirigami
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/qtsingleapplication
|
||||
${CMAKE_SOURCE_DIR}/src/3rdparty/kmessagewidget
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
@@ -540,3 +627,5 @@ if(NOT BUILD_OWNCLOUD_OSX_BUNDLE AND NOT WIN32)
|
||||
update_xdg_mimetypes( ${CMAKE_INSTALL_DATADIR}/mime/packages )
|
||||
endif(SharedMimeInfo_FOUND)
|
||||
endif()
|
||||
|
||||
configure_file(configgui.h.in ${CMAKE_CURRENT_BINARY_DIR}/configgui.h)
|
||||
|
||||
@@ -24,11 +24,7 @@ ColumnLayout {
|
||||
spacing: 0
|
||||
property NC.UserStatusSelectorModel userStatusSelectorModel
|
||||
|
||||
FontMetrics {
|
||||
id: metrics
|
||||
}
|
||||
|
||||
Text {
|
||||
Label {
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
@@ -89,7 +85,7 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Label {
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
@@ -108,8 +104,8 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Button {
|
||||
Layout.preferredWidth: userStatusMessageTextField.height // metrics.height * 2
|
||||
Layout.preferredHeight: userStatusMessageTextField.height // metrics.height * 2
|
||||
Layout.preferredWidth: userStatusMessageTextField.height
|
||||
Layout.preferredHeight: userStatusMessageTextField.height
|
||||
text: userStatusSelectorModel.userStatusEmoji
|
||||
onClicked: emojiDialog.open()
|
||||
}
|
||||
@@ -161,7 +157,7 @@ ColumnLayout {
|
||||
Layout.bottomMargin: 8
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
Text {
|
||||
Label {
|
||||
text: qsTr("Clear status message after")
|
||||
}
|
||||
|
||||
|
||||
@@ -587,6 +587,7 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
|
||||
|
||||
ac = availabilityMenu->addAction(Utility::vfsPinActionText());
|
||||
connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::AlwaysLocal); });
|
||||
ac->setDisabled(Theme::instance()->enforceVirtualFilesSyncFolder());
|
||||
|
||||
ac = availabilityMenu->addAction(Utility::vfsFreeSpaceActionText());
|
||||
connect(ac, &QAction::triggered, this, [this]() { slotSetCurrentFolderAvailability(PinState::OnlineOnly); });
|
||||
@@ -761,6 +762,7 @@ void AccountSettings::slotRemoveCurrentFolder()
|
||||
messageBox->addButton(tr("Cancel"), QMessageBox::NoRole);
|
||||
connect(messageBox, &QMessageBox::finished, this, [messageBox, yesButton, folder, row, this]{
|
||||
if (messageBox->clickedButton() == yesButton) {
|
||||
Utility::removeFavLink(folder->path());
|
||||
FolderMan::instance()->removeFolder(folder);
|
||||
_model->removeRow(row);
|
||||
|
||||
|
||||
@@ -221,6 +221,19 @@ void AccountState::setDesktopNotificationsAllowed(bool isAllowed)
|
||||
emit desktopNotificationsAllowedChanged();
|
||||
}
|
||||
|
||||
AccountState::ConnectionStatus AccountState::lastConnectionStatus() const
|
||||
{
|
||||
return _lastConnectionValidatorStatus;
|
||||
}
|
||||
|
||||
void AccountState::trySignIn()
|
||||
{
|
||||
if (isSignedOut() && account()) {
|
||||
account()->resetRejectedCertificates();
|
||||
signIn();
|
||||
}
|
||||
}
|
||||
|
||||
void AccountState::checkConnectivity()
|
||||
{
|
||||
if (isSignedOut() || _waitingForNewCredentials) {
|
||||
@@ -285,6 +298,8 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
|
||||
return;
|
||||
}
|
||||
|
||||
_lastConnectionValidatorStatus = status;
|
||||
|
||||
// Come online gradually from 503 or maintenance mode
|
||||
if (status == ConnectionValidator::Connected
|
||||
&& (_connectionStatus == ConnectionValidator::ServiceUnavailable
|
||||
|
||||
@@ -171,6 +171,10 @@ public:
|
||||
*/
|
||||
void setDesktopNotificationsAllowed(bool isAllowed);
|
||||
|
||||
ConnectionStatus lastConnectionStatus() const;
|
||||
|
||||
void trySignIn();
|
||||
|
||||
public slots:
|
||||
/// Triggers a ping to the server to update state and
|
||||
/// connection status and errors.
|
||||
@@ -205,6 +209,7 @@ private:
|
||||
AccountPtr _account;
|
||||
State _state;
|
||||
ConnectionStatus _connectionStatus;
|
||||
ConnectionStatus _lastConnectionValidatorStatus = ConnectionStatus::Undefined;
|
||||
QStringList _connectionErrors;
|
||||
bool _waitingForNewCredentials;
|
||||
QDateTime _timeOfLastETagCheck;
|
||||
|
||||
@@ -250,6 +250,10 @@ Application::Application(int &argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
if (_theme->doNotUseProxy()) {
|
||||
ConfigFile().setProxyType(QNetworkProxy::NoProxy);
|
||||
}
|
||||
|
||||
parseOptions(arguments());
|
||||
//no need to waste time;
|
||||
if (_helpOnly || _versionOnly)
|
||||
@@ -465,6 +469,9 @@ void Application::slotCheckConnection()
|
||||
if (state != AccountState::SignedOut && state != AccountState::ConfigurationError
|
||||
&& state != AccountState::AskingCredentials && !pushNotificationsAvailable) {
|
||||
accountState->checkConnectivity();
|
||||
} else if (state == AccountState::SignedOut && accountState->lastConnectionStatus() == AccountState::ConnectionStatus::SslError) {
|
||||
qCWarning(lcApplication) << "Account is signed out due to SSL Handshake error. Going to perform a sign-in attempt...";
|
||||
accountState->trySignIn();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
src/gui/configgui.h.in
Normal file
@@ -0,0 +1,4 @@
|
||||
#ifndef CONFIG_GUI_H
|
||||
#define CONFIG_GUI_H
|
||||
#cmakedefine APPLICATION_FOLDER_ICON_INDEX "@APPLICATION_FOLDER_ICON_INDEX@"
|
||||
#endif
|
||||
@@ -136,7 +136,7 @@ void ConnectionValidator::slotStatusFound(const QUrl &url, const QJsonObject &in
|
||||
void ConnectionValidator::slotNoStatusFound(QNetworkReply *reply)
|
||||
{
|
||||
auto job = qobject_cast<CheckServerJob *>(sender());
|
||||
qCWarning(lcConnectionValidator) << reply->error() << job->errorString() << reply->peek(1024);
|
||||
qCWarning(lcConnectionValidator) << reply->error() << reply->errorString() << job->errorString() << reply->peek(1024);
|
||||
if (reply->error() == QNetworkReply::SslHandshakeFailedError) {
|
||||
reportResult(SslError);
|
||||
return;
|
||||
|
||||
@@ -132,6 +132,16 @@ void Flow2Auth::fetchNewToken(const TokenAction action)
|
||||
|
||||
|
||||
_loginUrl = loginUrl;
|
||||
|
||||
if (_account->isUsernamePrefillSupported()) {
|
||||
const auto userName = Utility::getCurrentUserName();
|
||||
if (!userName.isEmpty()) {
|
||||
auto query = QUrlQuery(_loginUrl);
|
||||
query.addQueryItem(QStringLiteral("user"), userName);
|
||||
_loginUrl.setQuery(query);
|
||||
}
|
||||
}
|
||||
|
||||
_pollToken = pollToken;
|
||||
_pollEndpoint = pollEndpoint;
|
||||
|
||||
|
||||
@@ -691,6 +691,15 @@ void Folder::setRootPinState(PinState state)
|
||||
void Folder::switchToVirtualFiles()
|
||||
{
|
||||
SyncEngine::switchToVirtualFiles(path(), _journal, *_vfs);
|
||||
_hasSwitchedToVfs = true;
|
||||
}
|
||||
|
||||
void Folder::processSwitchedToVirtualFiles()
|
||||
{
|
||||
if (_hasSwitchedToVfs) {
|
||||
_hasSwitchedToVfs = false;
|
||||
saveToSettings();
|
||||
}
|
||||
}
|
||||
|
||||
bool Folder::supportsSelectiveSync() const
|
||||
@@ -866,11 +875,27 @@ void Folder::startSync(const QStringList &pathList)
|
||||
|
||||
_engine->setIgnoreHiddenFiles(_definition.ignoreHiddenFiles);
|
||||
|
||||
correctPlaceholderFiles();
|
||||
|
||||
QMetaObject::invokeMethod(_engine.data(), "startSync", Qt::QueuedConnection);
|
||||
|
||||
emit syncStarted();
|
||||
}
|
||||
|
||||
void Folder::correctPlaceholderFiles()
|
||||
{
|
||||
if (_definition.virtualFilesMode == Vfs::Off) {
|
||||
return;
|
||||
}
|
||||
static const auto placeholdersCorrectedKey = QStringLiteral("placeholders_corrected");
|
||||
const auto placeholdersCorrected = _journal.keyValueStoreGetInt(placeholdersCorrectedKey, 0);
|
||||
if (!placeholdersCorrected) {
|
||||
qCDebug(lcFolder) << "Make sure all virtual files are placeholder files";
|
||||
switchToVirtualFiles();
|
||||
_journal.keyValueStoreSet(placeholdersCorrectedKey, true);
|
||||
}
|
||||
}
|
||||
|
||||
void Folder::setSyncOptions()
|
||||
{
|
||||
SyncOptions opt;
|
||||
|
||||
@@ -289,6 +289,8 @@ public:
|
||||
|
||||
void switchToVirtualFiles();
|
||||
|
||||
void processSwitchedToVirtualFiles();
|
||||
|
||||
/** Whether this folder should show selective sync ui */
|
||||
bool supportsSelectiveSync() const;
|
||||
|
||||
@@ -444,6 +446,8 @@ private:
|
||||
|
||||
void startVfs();
|
||||
|
||||
void correctPlaceholderFiles();
|
||||
|
||||
AccountStatePtr _accountState;
|
||||
FolderDefinition _definition;
|
||||
QString _canonicalLocalPath; // As returned with QFileInfo:canonicalFilePath. Always ends with "/"
|
||||
@@ -498,6 +502,10 @@ private:
|
||||
*/
|
||||
bool _vfsOnOffPending = false;
|
||||
|
||||
/** Whether this folder has just switched to VFS or not
|
||||
*/
|
||||
bool _hasSwitchedToVfs = false;
|
||||
|
||||
/**
|
||||
* Watches this folder's local directory for changes.
|
||||
*
|
||||
|
||||
@@ -211,6 +211,10 @@ int FolderMan::setupFolders()
|
||||
|
||||
emit folderListChanged(_folderMap);
|
||||
|
||||
for (const auto folder : _folderMap) {
|
||||
folder->processSwitchedToVirtualFiles();
|
||||
}
|
||||
|
||||
return _folderMap.size();
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,6 @@
|
||||
#include "common/utility.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "legalnotice.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#ifndef MIRALL_GENERALSETTINGS_H
|
||||
#define MIRALL_GENERALSETTINGS_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QPointer>
|
||||
|
||||
|
||||
86
src/gui/internallinkwidget.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.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 "internallinkwidget.h"
|
||||
#include "accountstate.h"
|
||||
#include "folderman.h"
|
||||
#include "theme.h"
|
||||
|
||||
#include "QProgressIndicator.h"
|
||||
#include <QClipboard>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcInternalLink, "nextcloud.gui.internallink", QtInfoMsg)
|
||||
|
||||
InternalLinkWidget::InternalLinkWidget(const QString &localPath,
|
||||
QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, _localPath(localPath)
|
||||
{
|
||||
_ui->setupUi(this);
|
||||
|
||||
const auto folder = FolderMan::instance()->folderForPath(_localPath);
|
||||
const auto folderRelativePath = _localPath.mid(folder->cleanPath().length() + 1);
|
||||
const auto serverRelativePath = QDir(folder->remotePath()).filePath(folderRelativePath);
|
||||
|
||||
const auto bindLinkSlot = [this](QString link) { slotLinkFetched(link); };
|
||||
|
||||
fetchPrivateLinkUrl(
|
||||
folder->accountState()->account(),
|
||||
serverRelativePath,
|
||||
{},
|
||||
this,
|
||||
bindLinkSlot
|
||||
);
|
||||
|
||||
_ui->copyInternalLinkButton->setEnabled(false);
|
||||
_ui->internalLinkProgressIndicator->setVisible(true);
|
||||
_ui->internalLinkProgressIndicator->startAnimation();
|
||||
|
||||
connect(_ui->copyInternalLinkButton, &QPushButton::clicked, this, &InternalLinkWidget::slotCopyInternalLink);
|
||||
}
|
||||
|
||||
void InternalLinkWidget::slotLinkFetched(const QString &url)
|
||||
{
|
||||
_internalUrl = url;
|
||||
_ui->copyInternalLinkButton->setEnabled(true);
|
||||
_ui->internalLinkProgressIndicator->setVisible(false);
|
||||
_ui->internalLinkProgressIndicator->stopAnimation();
|
||||
_ui->horizontalSpacer->changeSize(0, 0);
|
||||
_ui->horizontalSpacer_2->changeSize(0, 0);
|
||||
}
|
||||
|
||||
void InternalLinkWidget::slotCopyInternalLink() const
|
||||
{
|
||||
QApplication::clipboard()->setText(_internalUrl);
|
||||
}
|
||||
|
||||
void InternalLinkWidget::setupUiOptions()
|
||||
{
|
||||
customizeStyle();
|
||||
}
|
||||
|
||||
void InternalLinkWidget::slotStyleChanged()
|
||||
{
|
||||
customizeStyle();
|
||||
}
|
||||
|
||||
void InternalLinkWidget::customizeStyle()
|
||||
{
|
||||
_ui->copyInternalLinkButton->setIcon(Theme::createColorAwareIcon(":/client/theme/copy.svg"));
|
||||
_ui->internalLinkIconLabel->setPixmap(Theme::createColorAwarePixmap(":/client/theme/external.svg"));
|
||||
}
|
||||
|
||||
}
|
||||
59
src/gui/internallinkwidget.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.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 INTERNALLINKWIDGET_H
|
||||
#define INTERNALLINKWIDGET_H
|
||||
|
||||
#include "QProgressIndicator.h"
|
||||
#include <QList>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "ui_internallinkwidget.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
/**
|
||||
* @brief The ShareDialog class
|
||||
* @ingroup gui
|
||||
*/
|
||||
class InternalLinkWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit InternalLinkWidget(const QString &localPath,
|
||||
QWidget *parent = nullptr);
|
||||
~InternalLinkWidget() override = default;
|
||||
|
||||
void setupUiOptions();
|
||||
|
||||
public slots:
|
||||
void slotStyleChanged();
|
||||
|
||||
private slots:
|
||||
void slotLinkFetched(const QString &url);
|
||||
void slotCopyInternalLink() const;
|
||||
|
||||
private:
|
||||
void customizeStyle();
|
||||
|
||||
std::unique_ptr<Ui::InternalLinkWidget> _ui = std::make_unique<Ui::InternalLinkWidget>();
|
||||
QString _localPath;
|
||||
QString _internalUrl;
|
||||
|
||||
QPushButton *_copyInternalLinkButton{};
|
||||
};
|
||||
}
|
||||
|
||||
#endif // INTERNALLINKWIDGET_H
|
||||
168
src/gui/internallinkwidget.ui
Normal file
@@ -0,0 +1,168 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OCC::InternalLinkWidget</class>
|
||||
<widget class="QWidget" name="OCC::InternalLinkWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>238</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="internalLinkIconLabel">
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="../../theme.qrc">:/client/theme/external.svg</pixmap>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalTextLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="internalLinkLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Internal link</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="infoMessage">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: rgb(118, 118, 118)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Only works for users with access to this folder</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</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="internalLinkProgressIndicator" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>28</width>
|
||||
<height>27</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</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="copyInternalLinkButton">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../theme.qrc">
|
||||
<normaloff>:/client/theme/copy.svg</normaloff>:/client/theme/copy.svg</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QProgressIndicator</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>QProgressIndicator.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../theme.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -34,42 +34,51 @@ NetworkSettings::NetworkSettings(QWidget *parent)
|
||||
{
|
||||
_ui->setupUi(this);
|
||||
|
||||
_ui->hostLineEdit->setPlaceholderText(tr("Hostname of proxy server"));
|
||||
_ui->userLineEdit->setPlaceholderText(tr("Username for proxy server"));
|
||||
_ui->passwordLineEdit->setPlaceholderText(tr("Password for proxy server"));
|
||||
_ui->proxyGroupBox->setVisible(!Theme::instance()->doNotUseProxy());
|
||||
|
||||
_ui->typeComboBox->addItem(tr("HTTP(S) proxy"), QNetworkProxy::HttpProxy);
|
||||
_ui->typeComboBox->addItem(tr("SOCKS5 proxy"), QNetworkProxy::Socks5Proxy);
|
||||
if (!Theme::instance()->doNotUseProxy()) {
|
||||
_ui->hostLineEdit->setPlaceholderText(tr("Hostname of proxy server"));
|
||||
_ui->userLineEdit->setPlaceholderText(tr("Username for proxy server"));
|
||||
_ui->passwordLineEdit->setPlaceholderText(tr("Password for proxy server"));
|
||||
|
||||
_ui->authRequiredcheckBox->setEnabled(true);
|
||||
_ui->typeComboBox->addItem(tr("HTTP(S) proxy"), QNetworkProxy::HttpProxy);
|
||||
_ui->typeComboBox->addItem(tr("SOCKS5 proxy"), QNetworkProxy::Socks5Proxy);
|
||||
|
||||
// Explicitly set up the enabled status of the proxy auth widgets to ensure
|
||||
// toggling the parent enables/disables the children
|
||||
_ui->userLineEdit->setEnabled(true);
|
||||
_ui->passwordLineEdit->setEnabled(true);
|
||||
_ui->authWidgets->setEnabled(_ui->authRequiredcheckBox->isChecked());
|
||||
connect(_ui->authRequiredcheckBox, &QAbstractButton::toggled,
|
||||
_ui->authWidgets, &QWidget::setEnabled);
|
||||
_ui->authRequiredcheckBox->setEnabled(true);
|
||||
|
||||
connect(_ui->manualProxyRadioButton, &QAbstractButton::toggled,
|
||||
_ui->manualSettings, &QWidget::setEnabled);
|
||||
connect(_ui->manualProxyRadioButton, &QAbstractButton::toggled,
|
||||
_ui->typeComboBox, &QWidget::setEnabled);
|
||||
connect(_ui->manualProxyRadioButton, &QAbstractButton::toggled,
|
||||
this, &NetworkSettings::checkAccountLocalhost);
|
||||
// Explicitly set up the enabled status of the proxy auth widgets to ensure
|
||||
// toggling the parent enables/disables the children
|
||||
_ui->userLineEdit->setEnabled(true);
|
||||
_ui->passwordLineEdit->setEnabled(true);
|
||||
_ui->authWidgets->setEnabled(_ui->authRequiredcheckBox->isChecked());
|
||||
connect(_ui->authRequiredcheckBox, &QAbstractButton::toggled, _ui->authWidgets, &QWidget::setEnabled);
|
||||
|
||||
connect(_ui->manualProxyRadioButton, &QAbstractButton::toggled, _ui->manualSettings, &QWidget::setEnabled);
|
||||
connect(_ui->manualProxyRadioButton, &QAbstractButton::toggled, _ui->typeComboBox, &QWidget::setEnabled);
|
||||
connect(_ui->manualProxyRadioButton, &QAbstractButton::toggled, this, &NetworkSettings::checkAccountLocalhost);
|
||||
|
||||
loadProxySettings();
|
||||
|
||||
connect(_ui->typeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
&NetworkSettings::saveProxySettings);
|
||||
connect(_ui->proxyButtonGroup, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this,
|
||||
&NetworkSettings::saveProxySettings);
|
||||
connect(_ui->hostLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings);
|
||||
connect(_ui->userLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings);
|
||||
connect(_ui->passwordLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings);
|
||||
connect(_ui->portSpinBox, &QAbstractSpinBox::editingFinished, this, &NetworkSettings::saveProxySettings);
|
||||
connect(_ui->authRequiredcheckBox, &QAbstractButton::toggled, this, &NetworkSettings::saveProxySettings);
|
||||
|
||||
// Warn about empty proxy host
|
||||
connect(_ui->hostLineEdit, &QLineEdit::textChanged, this, &NetworkSettings::checkEmptyProxyHost);
|
||||
checkEmptyProxyHost();
|
||||
checkAccountLocalhost();
|
||||
} else {
|
||||
_ui->noProxyRadioButton->setChecked(false);
|
||||
}
|
||||
|
||||
loadProxySettings();
|
||||
loadBWLimitSettings();
|
||||
|
||||
// proxy
|
||||
connect(_ui->typeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &NetworkSettings::saveProxySettings);
|
||||
connect(_ui->proxyButtonGroup, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &NetworkSettings::saveProxySettings);
|
||||
connect(_ui->hostLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings);
|
||||
connect(_ui->userLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings);
|
||||
connect(_ui->passwordLineEdit, &QLineEdit::editingFinished, this, &NetworkSettings::saveProxySettings);
|
||||
connect(_ui->portSpinBox, &QAbstractSpinBox::editingFinished, this, &NetworkSettings::saveProxySettings);
|
||||
connect(_ui->authRequiredcheckBox, &QAbstractButton::toggled, this, &NetworkSettings::saveProxySettings);
|
||||
|
||||
connect(_ui->uploadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings);
|
||||
connect(_ui->noUploadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings);
|
||||
connect(_ui->autoUploadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings);
|
||||
@@ -78,11 +87,6 @@ NetworkSettings::NetworkSettings(QWidget *parent)
|
||||
connect(_ui->autoDownloadLimitRadioButton, &QAbstractButton::clicked, this, &NetworkSettings::saveBWLimitSettings);
|
||||
connect(_ui->downloadSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &NetworkSettings::saveBWLimitSettings);
|
||||
connect(_ui->uploadSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &NetworkSettings::saveBWLimitSettings);
|
||||
|
||||
// Warn about empty proxy host
|
||||
connect(_ui->hostLineEdit, &QLineEdit::textChanged, this, &NetworkSettings::checkEmptyProxyHost);
|
||||
checkEmptyProxyHost();
|
||||
checkAccountLocalhost();
|
||||
}
|
||||
|
||||
NetworkSettings::~NetworkSettings()
|
||||
|
||||
41
src/gui/passwordinputdialog.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) by Oleksandr Zolotov <alex@nextcloud.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 "passwordinputdialog.h"
|
||||
#include "ui_passwordinputdialog.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
PasswordInputDialog::PasswordInputDialog(const QString &description, const QString &error, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, _ui(new Ui::PasswordInputDialog)
|
||||
{
|
||||
_ui->setupUi(this);
|
||||
|
||||
_ui->passwordLineEditLabel->setText(description);
|
||||
_ui->passwordLineEditLabel->setVisible(!description.isEmpty());
|
||||
|
||||
_ui->labelErrorMessage->setText(error);
|
||||
_ui->labelErrorMessage->setVisible(!error.isEmpty());
|
||||
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
}
|
||||
|
||||
PasswordInputDialog::~PasswordInputDialog() = default;
|
||||
|
||||
QString PasswordInputDialog::password() const
|
||||
{
|
||||
return _ui->passwordLineEdit->text();
|
||||
}
|
||||
}
|
||||
39
src/gui/passwordinputdialog.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) by Oleksandr Zolotov <alex@nextcloud.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 <QDialog>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
namespace Ui {
|
||||
class PasswordInputDialog;
|
||||
}
|
||||
|
||||
class PasswordInputDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PasswordInputDialog(const QString &description, const QString &error, QWidget *parent = nullptr);
|
||||
~PasswordInputDialog() override;
|
||||
|
||||
QString password() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::PasswordInputDialog> _ui;
|
||||
};
|
||||
|
||||
}
|
||||
115
src/gui/passwordinputdialog.ui
Normal file
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OCC::PasswordInputDialog</class>
|
||||
<widget class="QDialog" name="OCC::PasswordInputDialog">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::WindowModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>276</width>
|
||||
<height>125</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Password for share required</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="passwordLineEditLabel">
|
||||
<property name="text">
|
||||
<string>Please enter a password for your share:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="passwordLineEdit">
|
||||
<property name="inputMask">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelErrorMessage">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: rgb(118, 118, 118)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>OCC::PasswordInputDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>OCC::PasswordInputDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
* Copyright (C) by Roeland Jago Douma <roeland@famdouma.nl>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -16,7 +16,9 @@
|
||||
#include "sharedialog.h"
|
||||
#include "sharee.h"
|
||||
#include "sharelinkwidget.h"
|
||||
#include "internallinkwidget.h"
|
||||
#include "shareusergroupwidget.h"
|
||||
#include "passwordinputdialog.h"
|
||||
|
||||
#include "sharemanager.h"
|
||||
|
||||
@@ -135,28 +137,24 @@ ShareDialog::ShareDialog(QPointer<AccountState> accountState,
|
||||
connect(job, &PropfindJob::finishedWithError, this, &ShareDialog::slotPropfindError);
|
||||
job->start();
|
||||
|
||||
bool sharingPossible = true;
|
||||
if (!accountState->account()->capabilities().sharePublicLink()) {
|
||||
qCWarning(lcSharing) << "Link shares have been disabled";
|
||||
sharingPossible = false;
|
||||
} else if (!(maxSharingPermissions & SharePermissionShare)) {
|
||||
qCWarning(lcSharing) << "The file cannot be shared because it does not have sharing permission.";
|
||||
sharingPossible = false;
|
||||
}
|
||||
initShareManager();
|
||||
|
||||
_scrollAreaViewPort = new QWidget(_ui->scrollArea);
|
||||
_scrollAreaLayout = new QVBoxLayout(_scrollAreaViewPort);
|
||||
_scrollAreaLayout->setContentsMargins(0, 0, 0, 0);
|
||||
_ui->scrollArea->setWidget(_scrollAreaViewPort);
|
||||
|
||||
if (sharingPossible) {
|
||||
_manager = new ShareManager(accountState->account(), this);
|
||||
connect(_manager, &ShareManager::sharesFetched, this, &ShareDialog::slotSharesFetched);
|
||||
connect(_manager, &ShareManager::linkShareCreated, this, &ShareDialog::slotAddLinkShareWidget);
|
||||
connect(_manager, &ShareManager::linkShareRequiresPassword, this, &ShareDialog::slotLinkShareRequiresPassword);
|
||||
}
|
||||
_internalLinkWidget = new InternalLinkWidget(localPath, this);
|
||||
_ui->verticalLayout->addWidget(_internalLinkWidget);
|
||||
_internalLinkWidget->setupUiOptions();
|
||||
connect(this, &ShareDialog::styleChanged, _internalLinkWidget, &InternalLinkWidget::slotStyleChanged);
|
||||
}
|
||||
|
||||
ShareLinkWidget *ShareDialog::addLinkShareWidget(const QSharedPointer<LinkShare> &linkShare)
|
||||
{
|
||||
_linkWidgetList.append(new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, this));
|
||||
const auto linkShareWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _ui->scrollArea);
|
||||
_linkWidgetList.append(linkShareWidget);
|
||||
|
||||
const auto linkShareWidget = _linkWidgetList.at(_linkWidgetList.size() - 1);
|
||||
linkShareWidget->setLinkShare(linkShare);
|
||||
|
||||
connect(linkShare.data(), &Share::serverError, linkShareWidget, &ShareLinkWidget::slotServerError);
|
||||
@@ -171,13 +169,13 @@ ShareLinkWidget *ShareDialog::addLinkShareWidget(const QSharedPointer<LinkShare>
|
||||
connect(linkShareWidget, &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare);
|
||||
connect(linkShareWidget, &ShareLinkWidget::deleteLinkShare, this, &ShareDialog::slotDeleteShare);
|
||||
connect(linkShareWidget, &ShareLinkWidget::createPassword, this, &ShareDialog::slotCreatePasswordForLinkShare);
|
||||
|
||||
//connect(_linkWidgetList.at(index), &ShareLinkWidget::resizeRequested, this, &ShareDialog::slotAdjustScrollWidgetSize);
|
||||
|
||||
|
||||
// Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
|
||||
connect(this, &ShareDialog::styleChanged, linkShareWidget, &ShareLinkWidget::slotStyleChanged);
|
||||
|
||||
_ui->verticalLayout->insertWidget(_linkWidgetList.size() + 1, linkShareWidget);
|
||||
_scrollAreaLayout->addWidget(linkShareWidget);
|
||||
|
||||
linkShareWidget->setupUiOptions();
|
||||
|
||||
return linkShareWidget;
|
||||
@@ -186,18 +184,17 @@ ShareLinkWidget *ShareDialog::addLinkShareWidget(const QSharedPointer<LinkShare>
|
||||
void ShareDialog::initLinkShareWidget()
|
||||
{
|
||||
if(_linkWidgetList.size() == 0) {
|
||||
_emptyShareLinkWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, this);
|
||||
_emptyShareLinkWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _ui->scrollArea);
|
||||
_linkWidgetList.append(_emptyShareLinkWidget);
|
||||
|
||||
connect(_emptyShareLinkWidget, &ShareLinkWidget::resizeRequested, this, &ShareDialog::slotAdjustScrollWidgetSize);
|
||||
connect(this, &ShareDialog::toggleShareLinkAnimation, _emptyShareLinkWidget, &ShareLinkWidget::slotToggleShareLinkAnimation);
|
||||
connect(_emptyShareLinkWidget, &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare);
|
||||
|
||||
connect(_emptyShareLinkWidget, &ShareLinkWidget::createPassword, this, &ShareDialog::slotCreatePasswordForLinkShare);
|
||||
|
||||
_ui->verticalLayout->insertWidget(_linkWidgetList.size()+1, _emptyShareLinkWidget);
|
||||
_scrollAreaLayout->addWidget(_emptyShareLinkWidget);
|
||||
_emptyShareLinkWidget->show();
|
||||
} else if(_emptyShareLinkWidget) {
|
||||
} else if (_emptyShareLinkWidget) {
|
||||
_emptyShareLinkWidget->hide();
|
||||
_ui->verticalLayout->removeWidget(_emptyShareLinkWidget);
|
||||
_linkWidgetList.removeAll(_emptyShareLinkWidget);
|
||||
@@ -210,6 +207,7 @@ void ShareDialog::slotAddLinkShareWidget(const QSharedPointer<LinkShare> &linkSh
|
||||
emit toggleShareLinkAnimation(true);
|
||||
const auto addedLinkShareWidget = addLinkShareWidget(linkShare);
|
||||
initLinkShareWidget();
|
||||
adjustScrollWidgetSize();
|
||||
if (linkShare->isPasswordSet()) {
|
||||
addedLinkShareWidget->focusPasswordLineEdit();
|
||||
}
|
||||
@@ -222,6 +220,7 @@ void ShareDialog::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
|
||||
|
||||
const QString versionString = _accountState->account()->serverVersion();
|
||||
qCInfo(lcSharing) << versionString << "Fetched" << shares.count() << "shares";
|
||||
|
||||
foreach (auto share, shares) {
|
||||
if (share->getShareType() != Share::TypeLink || share->getUidOwner() != share->account()->davUser()) {
|
||||
continue;
|
||||
@@ -232,17 +231,20 @@ void ShareDialog::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
|
||||
}
|
||||
|
||||
initLinkShareWidget();
|
||||
adjustScrollWidgetSize();
|
||||
emit toggleShareLinkAnimation(false);
|
||||
}
|
||||
|
||||
void ShareDialog::slotAdjustScrollWidgetSize()
|
||||
void ShareDialog::adjustScrollWidgetSize()
|
||||
{
|
||||
int count = this->findChildren<ShareLinkWidget *>().count();
|
||||
_ui->scrollArea->setVisible(count > 0);
|
||||
if (count > 0 && count <= 3) {
|
||||
_ui->scrollArea->setFixedHeight(_ui->scrollArea->widget()->sizeHint().height());
|
||||
}
|
||||
_ui->scrollArea->setFrameShape(count > 3 ? QFrame::StyledPanel : QFrame::NoFrame);
|
||||
const auto count = _scrollAreaLayout->count();
|
||||
const auto margin = 10;
|
||||
const auto height = _linkWidgetList.empty() ? 0 : _linkWidgetList.last()->sizeHint().height() + margin;
|
||||
const auto totalHeight = height * count;
|
||||
_ui->scrollArea->setFixedWidth(_ui->verticalLayout->sizeHint().width());
|
||||
_ui->scrollArea->setFixedHeight(totalHeight > 400 ? 400 : totalHeight);
|
||||
_ui->scrollArea->setVisible(height > 0);
|
||||
_ui->scrollArea->setFrameShape(count > 6 ? QFrame::StyledPanel : QFrame::NoFrame);
|
||||
}
|
||||
|
||||
ShareDialog::~ShareDialog()
|
||||
@@ -303,21 +305,19 @@ void ShareDialog::showSharingUi()
|
||||
return;
|
||||
}
|
||||
|
||||
// We only do user/group sharing from 8.2.0
|
||||
bool userGroupSharing =
|
||||
theme->userGroupSharing()
|
||||
&& _accountState->account()->serverVersionInt() >= Account::makeServerVersion(8, 2, 0);
|
||||
|
||||
if (userGroupSharing) {
|
||||
_userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _privateLinkUrl, this);
|
||||
|
||||
if (theme->userGroupSharing()) {
|
||||
_userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _privateLinkUrl, _ui->scrollArea);
|
||||
_userGroupWidget->getShares();
|
||||
|
||||
// Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
|
||||
connect(this, &ShareDialog::styleChanged, _userGroupWidget, &ShareUserGroupWidget::slotStyleChanged);
|
||||
|
||||
_ui->verticalLayout->insertWidget(1, _userGroupWidget);
|
||||
_userGroupWidget->getShares();
|
||||
_scrollAreaLayout->addLayout(_userGroupWidget->shareUserGroupLayout());
|
||||
}
|
||||
|
||||
initShareManager();
|
||||
|
||||
if (theme->linkSharing()) {
|
||||
if(_manager) {
|
||||
_manager->fetchShares(_sharePath);
|
||||
@@ -325,6 +325,25 @@ void ShareDialog::showSharingUi()
|
||||
}
|
||||
}
|
||||
|
||||
void ShareDialog::initShareManager()
|
||||
{
|
||||
bool sharingPossible = true;
|
||||
if (!_accountState->account()->capabilities().sharePublicLink()) {
|
||||
qCWarning(lcSharing) << "Link shares have been disabled";
|
||||
sharingPossible = false;
|
||||
} else if (!(_maxSharingPermissions & SharePermissionShare)) {
|
||||
qCWarning(lcSharing) << "The file cannot be shared because it does not have sharing permission.";
|
||||
sharingPossible = false;
|
||||
}
|
||||
|
||||
if (!_manager && sharingPossible) {
|
||||
_manager = new ShareManager(_accountState->account(), this);
|
||||
connect(_manager, &ShareManager::sharesFetched, this, &ShareDialog::slotSharesFetched);
|
||||
connect(_manager, &ShareManager::linkShareCreated, this, &ShareDialog::slotAddLinkShareWidget);
|
||||
connect(_manager, &ShareManager::linkShareRequiresPassword, this, &ShareDialog::slotLinkShareRequiresPassword);
|
||||
}
|
||||
}
|
||||
|
||||
void ShareDialog::slotCreateLinkShare()
|
||||
{
|
||||
if(_manager) {
|
||||
@@ -359,26 +378,21 @@ void ShareDialog::slotCreatePasswordForLinkShareProcessed()
|
||||
}
|
||||
}
|
||||
|
||||
void ShareDialog::slotLinkShareRequiresPassword()
|
||||
void ShareDialog::slotLinkShareRequiresPassword(const QString &message)
|
||||
{
|
||||
bool ok = false;
|
||||
QString password = QInputDialog::getText(this,
|
||||
tr("Password for share required"),
|
||||
tr("Please enter a password for your link share:"),
|
||||
QLineEdit::Password,
|
||||
QString(),
|
||||
&ok);
|
||||
const auto passwordInputDialog = new PasswordInputDialog(tr("Please enter a password for your link share:"), message, this);
|
||||
passwordInputDialog->setWindowTitle(tr("Password for share required"));
|
||||
passwordInputDialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
passwordInputDialog->open();
|
||||
|
||||
if (!ok) {
|
||||
// The dialog was canceled so no need to do anything
|
||||
connect(passwordInputDialog, &QDialog::finished, this, [this, passwordInputDialog](const int result) {
|
||||
if (result == QDialog::Accepted && _manager) {
|
||||
// Try to create the link share again with the newly entered password
|
||||
_manager->createLinkShare(_sharePath, QString(), passwordInputDialog->password());
|
||||
return;
|
||||
}
|
||||
emit toggleShareLinkAnimation(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if(_manager) {
|
||||
// Try to create the link share again with the newly entered password
|
||||
_manager->createLinkShare(_sharePath, QString(), password);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ShareDialog::slotDeleteShare()
|
||||
@@ -386,8 +400,10 @@ void ShareDialog::slotDeleteShare()
|
||||
auto sharelinkWidget = dynamic_cast<ShareLinkWidget*>(sender());
|
||||
sharelinkWidget->hide();
|
||||
_ui->verticalLayout->removeWidget(sharelinkWidget);
|
||||
_scrollAreaLayout->removeWidget(sharelinkWidget);
|
||||
_linkWidgetList.removeAll(sharelinkWidget);
|
||||
initLinkShareWidget();
|
||||
adjustScrollWidgetSize();
|
||||
}
|
||||
|
||||
void ShareDialog::slotThumbnailFetched(const int &statusCode, const QByteArray &reply)
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <QWidget>
|
||||
|
||||
class QProgressIndicator;
|
||||
class QVBoxLayout;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
@@ -34,6 +35,7 @@ namespace Ui {
|
||||
}
|
||||
|
||||
class ShareLinkWidget;
|
||||
class InternalLinkWidget;
|
||||
class ShareUserGroupWidget;
|
||||
class ShareManager;
|
||||
class LinkShare;
|
||||
@@ -66,8 +68,7 @@ private slots:
|
||||
void slotCreateLinkShare();
|
||||
void slotCreatePasswordForLinkShare(const QString &password);
|
||||
void slotCreatePasswordForLinkShareProcessed();
|
||||
void slotLinkShareRequiresPassword();
|
||||
void slotAdjustScrollWidgetSize();
|
||||
void slotLinkShareRequiresPassword(const QString &message);
|
||||
|
||||
signals:
|
||||
void toggleShareLinkAnimation(bool start);
|
||||
@@ -78,8 +79,10 @@ protected:
|
||||
|
||||
private:
|
||||
void showSharingUi();
|
||||
void initShareManager();
|
||||
ShareLinkWidget *addLinkShareWidget(const QSharedPointer<LinkShare> &linkShare);
|
||||
void initLinkShareWidget();
|
||||
void adjustScrollWidgetSize();
|
||||
|
||||
Ui::ShareDialog *_ui;
|
||||
|
||||
@@ -94,8 +97,12 @@ private:
|
||||
|
||||
QList<ShareLinkWidget*> _linkWidgetList;
|
||||
ShareLinkWidget* _emptyShareLinkWidget = nullptr;
|
||||
InternalLinkWidget* _internalLinkWidget = nullptr;
|
||||
ShareUserGroupWidget *_userGroupWidget = nullptr;
|
||||
QProgressIndicator *_progressIndicator = nullptr;
|
||||
|
||||
QWidget *_scrollAreaViewPort = nullptr;
|
||||
QVBoxLayout *_scrollAreaLayout = nullptr;
|
||||
};
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
@@ -6,18 +6,36 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>385</width>
|
||||
<height>150</height>
|
||||
<width>480</width>
|
||||
<height>280</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>480</width>
|
||||
<height>250</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="shareDialogVerticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
<enum>QLayout::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0" columnstretch="0,0">
|
||||
@@ -33,6 +51,31 @@
|
||||
<property name="spacing">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<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>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_name">
|
||||
<property name="sizePolicy">
|
||||
@@ -89,49 +132,36 @@
|
||||
</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">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>200</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustIgnored</enum>
|
||||
<enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
@@ -141,11 +171,10 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>69</width>
|
||||
<height>69</height>
|
||||
<width>460</width>
|
||||
<height>200</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="scrollAreaVerticalLayout"/>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -411,22 +411,6 @@ void ShareLinkWidget::slotPasswordSetError(const int code, const QString &messag
|
||||
emit createPasswordProcessed();
|
||||
}
|
||||
|
||||
void ShareLinkWidget::startAnimation(const int start, const int end)
|
||||
{
|
||||
auto *animation = new QPropertyAnimation(this, "maximumHeight", this);
|
||||
|
||||
animation->setDuration(500);
|
||||
animation->setStartValue(start);
|
||||
animation->setEndValue(end);
|
||||
|
||||
connect(animation, &QAbstractAnimation::finished, this, &ShareLinkWidget::slotAnimationFinished);
|
||||
if (end < start) // that is to remove the widget, not to show it
|
||||
connect(animation, &QAbstractAnimation::finished, this, &ShareLinkWidget::slotDeleteAnimationFinished);
|
||||
connect(animation, &QVariantAnimation::valueChanged, this, &ShareLinkWidget::resizeRequested);
|
||||
|
||||
animation->start();
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotDeleteShareFetched()
|
||||
{
|
||||
slotToggleShareLinkAnimation(false);
|
||||
@@ -450,12 +434,6 @@ void ShareLinkWidget::toggleNoteOptions(const bool enable)
|
||||
}
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotAnimationFinished()
|
||||
{
|
||||
emit resizeRequested();
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotCreateLabel()
|
||||
{
|
||||
const auto labelText = _shareLinkEdit->text();
|
||||
@@ -474,14 +452,6 @@ void ShareLinkWidget::slotLabelSet()
|
||||
displayShareLinkLabel();
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotDeleteAnimationFinished()
|
||||
{
|
||||
// There is a painting bug where a small line of this widget isn't
|
||||
// properly cleared. This explicit repaint() call makes sure any trace of
|
||||
// the share widget is removed once it's destroyed. #4189
|
||||
connect(this, SIGNAL(destroyed(QObject *)), parentWidget(), SLOT(repaint()));
|
||||
}
|
||||
|
||||
void ShareLinkWidget::slotCreateShareRequiresPassword(const QString &message)
|
||||
{
|
||||
slotToggleShareLinkAnimation(message.isEmpty());
|
||||
|
||||
@@ -90,9 +90,6 @@ private slots:
|
||||
|
||||
void slotContextMenuButtonClicked();
|
||||
void slotLinkContextMenuActionTriggered(QAction *action);
|
||||
|
||||
void slotDeleteAnimationFinished();
|
||||
void slotAnimationFinished();
|
||||
|
||||
void slotCreateLabel();
|
||||
void slotLabelSet();
|
||||
@@ -100,7 +97,6 @@ private slots:
|
||||
signals:
|
||||
void createLinkShare();
|
||||
void deleteLinkShare();
|
||||
void resizeRequested();
|
||||
void visualDeletionDone();
|
||||
void createPassword(const QString &password);
|
||||
void createPasswordProcessed();
|
||||
@@ -119,8 +115,6 @@ private:
|
||||
/** Retrieve a share's name, accounting for _namesSupported */
|
||||
QString shareName() const;
|
||||
|
||||
void startAnimation(const int start, const int end);
|
||||
|
||||
void customizeStyle();
|
||||
|
||||
void displayShareLinkLabel();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>365</width>
|
||||
<width>400</width>
|
||||
<height>238</height>
|
||||
</rect>
|
||||
</property>
|
||||
@@ -17,8 +17,29 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="shareLinkIconLabel">
|
||||
<property name="text">
|
||||
|
||||
@@ -154,16 +154,18 @@ ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
|
||||
_completionTimer.setInterval(600);
|
||||
|
||||
_ui->errorLabel->hide();
|
||||
|
||||
// TODO Progress Indicator where should it go?
|
||||
// Setup the sharee search progress indicator
|
||||
//_ui->shareeHorizontalLayout->addWidget(&_pi_sharee);
|
||||
|
||||
|
||||
_parentScrollArea = parentWidget()->findChild<QScrollArea*>("scrollArea");
|
||||
|
||||
_shareUserGroup = new QVBoxLayout(_parentScrollArea);
|
||||
_shareUserGroup->setContentsMargins(0, 0, 0, 0);
|
||||
customizeStyle();
|
||||
}
|
||||
|
||||
QVBoxLayout *ShareUserGroupWidget::shareUserGroupLayout()
|
||||
{
|
||||
return _shareUserGroup;
|
||||
}
|
||||
|
||||
ShareUserGroupWidget::~ShareUserGroupWidget()
|
||||
{
|
||||
delete _ui;
|
||||
@@ -247,17 +249,16 @@ void ShareUserGroupWidget::slotShareCreated(const QSharedPointer<Share> &share)
|
||||
|
||||
void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
|
||||
{
|
||||
QScrollArea *scrollArea = _parentScrollArea;
|
||||
|
||||
auto newViewPort = new QWidget(scrollArea);
|
||||
auto layout = new QVBoxLayout(newViewPort);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
int x = 0;
|
||||
int height = 0;
|
||||
QList<QString> linkOwners({});
|
||||
|
||||
ShareUserLine *justCreatedShareThatNeedsPassword = nullptr;
|
||||
|
||||
|
||||
while (QLayoutItem *shareUserLine = _shareUserGroup->takeAt(0)) {
|
||||
delete shareUserLine->widget();
|
||||
delete shareUserLine;
|
||||
}
|
||||
|
||||
foreach (const auto &share, shares) {
|
||||
// We don't handle link shares, only TypeUser or TypeGroup
|
||||
if (share->getShareType() == Share::TypeLink) {
|
||||
@@ -278,14 +279,12 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
|
||||
Q_ASSERT(Share::isShareTypeUserGroupEmailRoomOrRemote(share->getShareType()));
|
||||
auto userGroupShare = qSharedPointerDynamicCast<UserGroupShare>(share);
|
||||
auto *s = new ShareUserLine(_account, userGroupShare, _maxSharingPermissions, _isFile, _parentScrollArea);
|
||||
connect(s, &ShareUserLine::resizeRequested, this, &ShareUserGroupWidget::slotAdjustScrollWidgetSize);
|
||||
connect(s, &ShareUserLine::visualDeletionDone, this, &ShareUserGroupWidget::getShares);
|
||||
s->setBackgroundRole(layout->count() % 2 == 0 ? QPalette::Base : QPalette::AlternateBase);
|
||||
s->setBackgroundRole(_shareUserGroup->count() % 2 == 0 ? QPalette::Base : QPalette::AlternateBase);
|
||||
|
||||
// Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
|
||||
connect(this, &ShareUserGroupWidget::styleChanged, s, &ShareUserLine::slotStyleChanged);
|
||||
|
||||
layout->addWidget(s);
|
||||
_shareUserGroup->addWidget(s);
|
||||
|
||||
if (!_lastCreatedShareId.isEmpty() && share->getId() == _lastCreatedShareId) {
|
||||
_lastCreatedShareId = QString();
|
||||
@@ -295,27 +294,14 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
|
||||
}
|
||||
|
||||
x++;
|
||||
if (x <= 3) {
|
||||
height = newViewPort->sizeHint().height();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const QString &owner, linkOwners) {
|
||||
auto ownerLabel = new QLabel(QString(owner + " shared via link"));
|
||||
layout->addWidget(ownerLabel);
|
||||
_shareUserGroup->addWidget(ownerLabel);
|
||||
ownerLabel->setVisible(true);
|
||||
|
||||
x++;
|
||||
if (x <= 6) {
|
||||
height = newViewPort->sizeHint().height();
|
||||
}
|
||||
}
|
||||
|
||||
scrollArea->setFrameShape(x > 6 ? QFrame::StyledPanel : QFrame::NoFrame);
|
||||
scrollArea->setVisible(!shares.isEmpty());
|
||||
scrollArea->setFixedHeight(height);
|
||||
scrollArea->setWidget(newViewPort);
|
||||
|
||||
|
||||
_disableCompleterActivated = false;
|
||||
activateShareeLineEdit();
|
||||
|
||||
@@ -325,24 +311,6 @@ void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>>
|
||||
}
|
||||
}
|
||||
|
||||
void ShareUserGroupWidget::slotAdjustScrollWidgetSize()
|
||||
{
|
||||
QScrollArea *scrollArea = _parentScrollArea;
|
||||
const auto shareUserLineChilds = scrollArea->findChildren<ShareUserLine *>();
|
||||
|
||||
// Ask the child widgets to calculate their size
|
||||
for (const auto shareUserLineChild : shareUserLineChilds) {
|
||||
shareUserLineChild->adjustSize();
|
||||
}
|
||||
|
||||
const auto shareUserLineChildsCount = shareUserLineChilds.count();
|
||||
scrollArea->setVisible(shareUserLineChildsCount > 0);
|
||||
if (shareUserLineChildsCount > 0 && shareUserLineChildsCount <= 3) {
|
||||
scrollArea->setFixedHeight(scrollArea->widget()->sizeHint().height());
|
||||
}
|
||||
scrollArea->setFrameShape(shareUserLineChildsCount > 3 ? QFrame::StyledPanel : QFrame::NoFrame);
|
||||
}
|
||||
|
||||
void ShareUserGroupWidget::slotPrivateLinkShare()
|
||||
{
|
||||
auto menu = new QMenu(this);
|
||||
@@ -380,22 +348,11 @@ void ShareUserGroupWidget::slotCompleterActivated(const QModelIndex &index)
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO Progress Indicator where should it go?
|
||||
// auto indicator = new QProgressIndicator(viewPort);
|
||||
// indicator->startAnimation();
|
||||
// if (layout->count() == 1) {
|
||||
// // No shares yet! Remove the label, add some stretch.
|
||||
// delete layout->itemAt(0)->widget();
|
||||
// layout->addStretch(1);
|
||||
// }
|
||||
// layout->insertWidget(layout->count() - 1, indicator);
|
||||
|
||||
/*
|
||||
* Don't send the reshare permissions for federated shares for servers <9.1
|
||||
* https://github.com/owncloud/core/issues/22122#issuecomment-185637344
|
||||
* https://github.com/owncloud/client/issues/4996
|
||||
*/
|
||||
|
||||
_lastCreatedShareId = QString();
|
||||
|
||||
QString password;
|
||||
|
||||
@@ -77,6 +77,8 @@ public:
|
||||
const QString &privateLinkUrl,
|
||||
QWidget *parent = nullptr);
|
||||
~ShareUserGroupWidget() override;
|
||||
|
||||
QVBoxLayout *shareUserGroupLayout();
|
||||
|
||||
signals:
|
||||
void togglePublicLinkShare(bool);
|
||||
@@ -98,7 +100,6 @@ private slots:
|
||||
void slotCompleterActivated(const QModelIndex &index);
|
||||
void slotCompleterHighlighted(const QModelIndex &index);
|
||||
void slotShareesReady();
|
||||
void slotAdjustScrollWidgetSize();
|
||||
void slotPrivateLinkShare();
|
||||
void displayError(int code, const QString &message);
|
||||
|
||||
@@ -113,6 +114,7 @@ private:
|
||||
|
||||
Ui::ShareUserGroupWidget *_ui;
|
||||
QScrollArea *_parentScrollArea;
|
||||
QVBoxLayout *_shareUserGroup;
|
||||
AccountPtr _account;
|
||||
QString _sharePath;
|
||||
QString _localPath;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>350</width>
|
||||
<height>70</height>
|
||||
<height>106</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@@ -17,6 +17,21 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="mainOwnerLabel">
|
||||
<property name="sizePolicy">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>899</width>
|
||||
<width>400</width>
|
||||
<height>310</height>
|
||||
</rect>
|
||||
</property>
|
||||
@@ -26,8 +26,26 @@
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>12</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
@@ -75,11 +93,11 @@
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
<enum>QSizePolicy::Minimum</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
|
||||
@@ -1225,9 +1225,11 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe
|
||||
auto makePinContextMenu = [&](bool makeAvailableLocally, bool freeSpace) {
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:CURRENT_PIN:d:")
|
||||
+ Utility::vfsCurrentAvailabilityText(*combined));
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:MAKE_AVAILABLE_LOCALLY:")
|
||||
+ (makeAvailableLocally ? QLatin1String(":") : QLatin1String("d:"))
|
||||
+ Utility::vfsPinActionText());
|
||||
if (!Theme::instance()->enforceVirtualFilesSyncFolder()) {
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:MAKE_AVAILABLE_LOCALLY:")
|
||||
+ (makeAvailableLocally ? QLatin1String(":") : QLatin1String("d:")) + Utility::vfsPinActionText());
|
||||
}
|
||||
|
||||
listener->sendMessage(QLatin1String("MENU_ITEM:MAKE_ONLINE_ONLY:")
|
||||
+ (freeSpace ? QLatin1String(":") : QLatin1String("d:"))
|
||||
+ Utility::vfsFreeSpaceActionText());
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
#include "common/utility.h"
|
||||
#include "tray/svgimageprovider.h"
|
||||
#include "tray/usermodel.h"
|
||||
#include "wheelhandler.h"
|
||||
#include "tray/unifiedsearchresultimageprovider.h"
|
||||
#include "configfile.h"
|
||||
#include "accessmanager.h"
|
||||
|
||||
#include <QCursor>
|
||||
#include <QGuiApplication>
|
||||
@@ -58,6 +60,8 @@ void Systray::setTrayEngine(QQmlApplicationEngine *trayEngine)
|
||||
{
|
||||
_trayEngine = trayEngine;
|
||||
|
||||
_trayEngine->setNetworkAccessManagerFactory(&_accessManagerFactory);
|
||||
|
||||
_trayEngine->addImportPath("qrc:/qml/theme");
|
||||
_trayEngine->addImageProvider("avatars", new ImageProvider);
|
||||
_trayEngine->addImageProvider(QLatin1String("svgimage-custom-color"), new OCC::Ui::SvgImageProvider);
|
||||
@@ -91,6 +95,8 @@ Systray::Systray()
|
||||
}
|
||||
);
|
||||
|
||||
qmlRegisterType<WheelHandler>("com.nextcloud.desktopclient", 1, 0, "WheelHandler");
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
auto contextMenu = new QMenu();
|
||||
if (AccountManager::instance()->accounts().isEmpty()) {
|
||||
@@ -502,4 +508,14 @@ QPoint Systray::calcTrayIconCenter() const
|
||||
#endif
|
||||
}
|
||||
|
||||
AccessManagerFactory::AccessManagerFactory()
|
||||
: QQmlNetworkAccessManagerFactory()
|
||||
{
|
||||
}
|
||||
|
||||
QNetworkAccessManager* AccessManagerFactory::create(QObject *parent)
|
||||
{
|
||||
return new AccessManager(parent);
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include "accountmanager.h"
|
||||
#include "tray/usermodel.h"
|
||||
|
||||
#include <QQmlNetworkAccessManagerFactory>
|
||||
|
||||
class QScreen;
|
||||
class QQmlApplicationEngine;
|
||||
class QQuickWindow;
|
||||
@@ -28,6 +30,14 @@ class QQuickWindow;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class AccessManagerFactory : public QQmlNetworkAccessManagerFactory
|
||||
{
|
||||
public:
|
||||
AccessManagerFactory();
|
||||
|
||||
QNetworkAccessManager* create(QObject *parent) override;
|
||||
};
|
||||
|
||||
#ifdef Q_OS_OSX
|
||||
bool canOsXSendUserNotification();
|
||||
void sendOsXUserNotification(const QString &title, const QString &message);
|
||||
@@ -105,6 +115,8 @@ private:
|
||||
bool _isOpen = false;
|
||||
bool _syncIsPaused = true;
|
||||
QPointer<QQmlApplicationEngine> _trayEngine;
|
||||
|
||||
AccessManagerFactory _accessManagerFactory;
|
||||
};
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
BIN
src/gui/tray/.wheelhandler.h.swo
Normal file
@@ -1,109 +1,65 @@
|
||||
import QtQuick 2.5
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.15
|
||||
import Style 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
readonly property bool labelVisible: label.visible
|
||||
readonly property bool iconVisible: icon.visible
|
||||
|
||||
// label value
|
||||
property string text: ""
|
||||
|
||||
// font value
|
||||
property var font: label.font
|
||||
property string toolTipText: ""
|
||||
|
||||
property bool bold: false
|
||||
|
||||
// icon value
|
||||
property string imageSource: ""
|
||||
property string imageSourceHover: ""
|
||||
|
||||
// Tooltip value
|
||||
property string tooltipText: text
|
||||
|
||||
// text color
|
||||
property color textColor: Style.ncTextColor
|
||||
property color textColorHovered: Style.lightHover
|
||||
|
||||
// text background color
|
||||
property color textBgColor: "transparent"
|
||||
property color textBgColorHovered: Style.lightHover
|
||||
|
||||
// icon background color
|
||||
property color iconBgColor: "transparent"
|
||||
property color iconBgColorHovered: Style.lightHover
|
||||
|
||||
// text border color
|
||||
property color textBorderColor: "transparent"
|
||||
|
||||
property alias hovered: mouseArea.containsMouse
|
||||
property color textColor: Style.unifiedSearchResulTitleColor
|
||||
property color textColorHovered: Style.unifiedSearchResulSublineColor
|
||||
|
||||
signal clicked()
|
||||
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: text !== "" ? text : (tooltipText !== "" ? tooltipText : qsTr("Activity action button"))
|
||||
Accessible.onPressAction: clicked()
|
||||
|
||||
// background with border around the Text
|
||||
Rectangle {
|
||||
visible: parent.labelVisible
|
||||
Loader {
|
||||
active: root.imageSource === ""
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
// padding
|
||||
anchors.topMargin: 10
|
||||
anchors.bottomMargin: 10
|
||||
sourceComponent: CustomTextButton {
|
||||
anchors.fill: parent
|
||||
text: root.text
|
||||
toolTipText: root.toolTipText
|
||||
|
||||
border.color: parent.textBorderColor
|
||||
border.width: 1
|
||||
textColor: root.textColor
|
||||
textColorHovered: root.textColorHovered
|
||||
|
||||
color: parent.hovered ? parent.textBgColorHovered : parent.textBgColor
|
||||
|
||||
radius: 25
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
// background with border around the Image
|
||||
Rectangle {
|
||||
visible: parent.iconVisible
|
||||
Loader {
|
||||
active: root.imageSource !== ""
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
color: parent.hovered ? parent.iconBgColorHovered : parent.iconBgColor
|
||||
}
|
||||
sourceComponent: CustomButton {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Style.roundedButtonBackgroundVerticalMargins
|
||||
anchors.bottomMargin: Style.roundedButtonBackgroundVerticalMargins
|
||||
|
||||
// label
|
||||
Text {
|
||||
id: label
|
||||
visible: parent.text !== ""
|
||||
text: parent.text
|
||||
font: parent.font
|
||||
color: parent.hovered ? parent.textColorHovered : parent.textColor
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 10
|
||||
anchors.rightMargin: 10
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
text: root.text
|
||||
toolTipText: root.toolTipText
|
||||
|
||||
// icon
|
||||
Image {
|
||||
id: icon
|
||||
visible: parent.imageSource !== ""
|
||||
anchors.centerIn: parent
|
||||
source: parent.imageSource
|
||||
sourceSize.width: visible ? 32 : 0
|
||||
sourceSize.height: visible ? 32 : 0
|
||||
}
|
||||
textColor: root.textColor
|
||||
textColorHovered: root.textColorHovered
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
onClicked: parent.clicked()
|
||||
hoverEnabled: true
|
||||
}
|
||||
bold: root.bold
|
||||
|
||||
ToolTip {
|
||||
text: parent.tooltipText
|
||||
delay: 1000
|
||||
visible: text != "" && parent.hovered
|
||||
imageSource: root.imageSource
|
||||
imageSourceHover: root.imageSourceHover
|
||||
|
||||
bgColor: Style.ncBlue
|
||||
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,260 +1,84 @@
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQml 2.15
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
import Style 1.0
|
||||
import com.nextcloud.desktopclient 1.0
|
||||
|
||||
MouseArea {
|
||||
id: activityMouseArea
|
||||
id: root
|
||||
|
||||
readonly property int maxActionButtons: 2
|
||||
property Flickable flickable
|
||||
|
||||
property bool isFileActivityList: false
|
||||
|
||||
property bool isChatActivity: model.objectType === "chat" || model.objectType === "room"
|
||||
|
||||
signal fileActivityButtonClicked(string absolutePath)
|
||||
|
||||
enabled: (path !== "" || link !== "")
|
||||
enabled: (model.path !== "" || model.link !== "" || model.isCurrentUserFileActivity === true)
|
||||
hoverEnabled: true
|
||||
|
||||
height: childrenRect.height
|
||||
|
||||
ToolTip.visible: containsMouse && !activityContent.childHovered && model.displayLocation !== ""
|
||||
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
ToolTip.text: qsTr("In %1").arg(model.displayLocation)
|
||||
|
||||
Accessible.role: Accessible.ListItem
|
||||
Accessible.name: (model.path !== "" && model.displayPath !== "") ? qsTr("Open %1 locally").arg(model.displayPath) : model.message
|
||||
Accessible.onPressAction: root.clicked()
|
||||
|
||||
Rectangle {
|
||||
id: activityHover
|
||||
anchors.fill: parent
|
||||
color: (parent.containsMouse ? Style.lightHover : "transparent")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: activityItem
|
||||
|
||||
readonly property variant links: model.links
|
||||
|
||||
readonly property int itemIndex: model.index
|
||||
|
||||
width: activityMouseArea.width
|
||||
height: Style.trayWindowHeaderHeight
|
||||
|
||||
ColumnLayout {
|
||||
anchors.left: root.left
|
||||
anchors.right: root.right
|
||||
anchors.leftMargin: 15
|
||||
anchors.rightMargin: 10
|
||||
|
||||
spacing: 0
|
||||
|
||||
Accessible.role: Accessible.ListItem
|
||||
Accessible.name: path !== "" ? qsTr("Open %1 locally").arg(displayPath)
|
||||
: message
|
||||
Accessible.onPressAction: activityMouseArea.clicked()
|
||||
|
||||
Image {
|
||||
id: activityIcon
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||
Layout.leftMargin: 20
|
||||
Layout.preferredWidth: shareButton.icon.width
|
||||
Layout.preferredHeight: shareButton.icon.height
|
||||
verticalAlignment: Qt.AlignCenter
|
||||
cache: true
|
||||
source: icon
|
||||
sourceSize.height: 64
|
||||
sourceSize.width: 64
|
||||
}
|
||||
|
||||
Column {
|
||||
id: activityTextColumn
|
||||
Layout.leftMargin: 14
|
||||
Layout.topMargin: 4
|
||||
Layout.bottomMargin: 4
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
|
||||
Text {
|
||||
id: activityTextTitle
|
||||
text: (type === "Activity" || type === "Notification") ? subject : message
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: Style.topLinePixelSize
|
||||
color: activityTextTitleColor
|
||||
}
|
||||
|
||||
Text {
|
||||
id: activityTextInfo
|
||||
text: (type === "Sync") ? displayPath
|
||||
: (type === "File") ? subject
|
||||
: (type === "Notification") ? message
|
||||
: ""
|
||||
height: (text === "") ? 0 : activityTextTitle.height
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: Style.subLinePixelSize
|
||||
}
|
||||
|
||||
Text {
|
||||
id: activityTextDateTime
|
||||
text: dateTime
|
||||
height: (text === "") ? 0 : activityTextTitle.height
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: Style.subLinePixelSize
|
||||
color: "#808080"
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: activityActionsLayout
|
||||
spacing: 0
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
Layout.minimumWidth: 28
|
||||
Layout.fillWidth: true
|
||||
|
||||
function actionButtonIcon(actionIndex) {
|
||||
const verb = String(model.links[actionIndex].verb);
|
||||
if (verb === "WEB" && (model.objectType === "chat" || model.objectType === "call")) {
|
||||
return "qrc:///client/theme/reply.svg";
|
||||
} else if (verb === "DELETE") {
|
||||
return "qrc:///client/theme/close.svg";
|
||||
}
|
||||
|
||||
return "qrc:///client/theme/confirm.svg";
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: activityItem.links.length > maxActionButtons ? 1 : activityItem.links.length
|
||||
|
||||
ActivityActionButton {
|
||||
id: activityActionButton
|
||||
|
||||
readonly property int actionIndex: model.index
|
||||
readonly property bool primary: model.index === 0 && String(activityItem.links[actionIndex].verb) !== "DELETE"
|
||||
|
||||
Layout.fillHeight: true
|
||||
|
||||
text: !primary ? "" : activityItem.links[actionIndex].label
|
||||
|
||||
imageSource: !primary ? activityActionsLayout.actionButtonIcon(actionIndex) : ""
|
||||
|
||||
textColor: primary ? Style.ncBlue : "black"
|
||||
textColorHovered: Style.lightHover
|
||||
|
||||
textBorderColor: Style.ncBlue
|
||||
|
||||
textBgColor: "transparent"
|
||||
textBgColorHovered: Style.ncBlue
|
||||
|
||||
tooltipText: activityItem.links[actionIndex].label
|
||||
|
||||
Layout.minimumWidth: primary ? 80 : -1
|
||||
Layout.minimumHeight: parent.height
|
||||
|
||||
Layout.preferredWidth: primary ? -1 : parent.height
|
||||
|
||||
onClicked: activityModel.triggerAction(activityItem.itemIndex, actionIndex)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Button {
|
||||
id: shareButton
|
||||
|
||||
Layout.preferredWidth: parent.height
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignRight
|
||||
flat: true
|
||||
hoverEnabled: true
|
||||
visible: displayActions && (path !== "")
|
||||
display: AbstractButton.IconOnly
|
||||
icon.source: "qrc:///client/theme/share.svg"
|
||||
icon.color: "transparent"
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Style.lightHover : "transparent"
|
||||
}
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
ToolTip.text: qsTr("Open share dialog")
|
||||
onClicked: Systray.openShareDialog(displayPath, absolutePath)
|
||||
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: qsTr("Share %1").arg(displayPath)
|
||||
Accessible.onPressAction: shareButton.clicked()
|
||||
}
|
||||
|
||||
Button {
|
||||
id: moreActionsButton
|
||||
|
||||
Layout.preferredWidth: parent.height
|
||||
Layout.preferredHeight: parent.height
|
||||
Layout.alignment: Qt.AlignRight
|
||||
|
||||
flat: true
|
||||
hoverEnabled: true
|
||||
visible: displayActions && ((path !== "") || (activityItem.links.length > maxActionButtons))
|
||||
display: AbstractButton.IconOnly
|
||||
icon.source: "qrc:///client/theme/more.svg"
|
||||
icon.color: "transparent"
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? Style.lightHover : "transparent"
|
||||
}
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
ToolTip.text: qsTr("Show more actions")
|
||||
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: qsTr("Show more actions")
|
||||
Accessible.onPressAction: moreActionsButton.clicked()
|
||||
|
||||
onClicked: moreActionsButtonContextMenu.popup();
|
||||
|
||||
Connections {
|
||||
target: flickable
|
||||
|
||||
function onMovementStarted() {
|
||||
moreActionsButtonContextMenu.close();
|
||||
}
|
||||
}
|
||||
|
||||
Container {
|
||||
id: moreActionsButtonContextMenuContainer
|
||||
visible: moreActionsButtonContextMenu.opened
|
||||
|
||||
width: moreActionsButtonContextMenu.width
|
||||
height: moreActionsButtonContextMenu.height
|
||||
anchors.right: moreActionsButton.right
|
||||
anchors.top: moreActionsButton.top
|
||||
|
||||
Menu {
|
||||
id: moreActionsButtonContextMenu
|
||||
anchors.centerIn: parent
|
||||
|
||||
// transform model to contain indexed actions with primary action filtered out
|
||||
function actionListToContextMenuList(actionList) {
|
||||
// early out with non-altered data
|
||||
if (activityItem.links.length <= maxActionButtons) {
|
||||
return actionList;
|
||||
}
|
||||
|
||||
// add index to every action and filter 'primary' action out
|
||||
var reducedActionList = actionList.reduce(function(reduced, action, index) {
|
||||
if (!action.primary) {
|
||||
var actionWithIndex = { actionIndex: index, label: action.label };
|
||||
reduced.push(actionWithIndex);
|
||||
}
|
||||
return reduced;
|
||||
}, []);
|
||||
|
||||
|
||||
return reducedActionList;
|
||||
}
|
||||
ActivityItemContent {
|
||||
id: activityContent
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("View activity")
|
||||
onClicked: fileActivityButtonClicked(absolutePath)
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: moreActionsButtonContextMenuRepeater
|
||||
|
||||
model: moreActionsButtonContextMenu.actionListToContextMenuList(activityItem.links)
|
||||
|
||||
delegate: MenuItem {
|
||||
id: moreActionsButtonContextMenuEntry
|
||||
text: model.modelData.label
|
||||
onTriggered: activityModel.triggerAction(activityItem.itemIndex, model.modelData.actionIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
|
||||
showDismissButton: model.links.length > 0 && model.linksForActionButtons.length === 0
|
||||
|
||||
activityData: model
|
||||
|
||||
Layout.preferredHeight: Style.trayWindowHeaderHeight
|
||||
|
||||
onShareButtonClicked: Systray.openShareDialog(model.displayPath, model.absolutePath)
|
||||
onDismissButtonClicked: activityModel.slotTriggerDismiss(model.index)
|
||||
}
|
||||
|
||||
ActivityItemActions {
|
||||
id: activityActions
|
||||
|
||||
visible: !root.isFileActivityList && model.linksForActionButtons.length > 0
|
||||
|
||||
Layout.preferredHeight: Style.trayWindowHeaderHeight * 0.85
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 40
|
||||
Layout.bottomMargin: model.links.length > 1 ? 5 : 0
|
||||
|
||||
displayActions: model.displayActions
|
||||
objectType: model.objectType
|
||||
linksForActionButtons: model.linksForActionButtons
|
||||
linksContextMenu: model.linksContextMenu
|
||||
|
||||
moreActionsButtonColor: activityHover.color
|
||||
maxActionButtons: activityModel.maxActionButtons
|
||||
|
||||
flickable: root.flickable
|
||||
|
||||
onTriggerAction: activityModel.slotTriggerAction(model.index, actionIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
103
src/gui/tray/ActivityItemActions.qml
Normal file
@@ -0,0 +1,103 @@
|
||||
import QtQml 2.15
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import Style 1.0
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
spacing: 20
|
||||
|
||||
property string objectType: ""
|
||||
property variant linksForActionButtons: []
|
||||
property variant linksContextMenu: []
|
||||
property bool displayActions: false
|
||||
|
||||
property color moreActionsButtonColor: "transparent"
|
||||
|
||||
property int maxActionButtons: 0
|
||||
|
||||
property Flickable flickable
|
||||
|
||||
signal triggerAction(int actionIndex)
|
||||
|
||||
Repeater {
|
||||
id: actionsRepeater
|
||||
// a max of maxActionButtons will get dispayed as separate buttons
|
||||
model: root.linksForActionButtons
|
||||
|
||||
ActivityActionButton {
|
||||
id: activityActionButton
|
||||
|
||||
readonly property bool primary: model.index === 0 && model.modelData.verb !== "DELETE"
|
||||
|
||||
Layout.minimumWidth: primary ? Style.activityItemActionPrimaryButtonMinWidth : Style.activityItemActionSecondaryButtonMinWidth
|
||||
Layout.preferredHeight: primary ? parent.height : parent.height * 0.3
|
||||
Layout.preferredWidth: primary ? -1 : parent.height
|
||||
|
||||
text: model.modelData.label
|
||||
toolTipText: model.modelData.label
|
||||
|
||||
imageSource: model.modelData.imageSource
|
||||
imageSourceHover: model.modelData.imageSourceHovered
|
||||
|
||||
textColor: imageSource !== "" ? Style.ncBlue : Style.unifiedSearchResulSublineColor
|
||||
textColorHovered: imageSource !== "" ? Style.lightHover : Style.unifiedSearchResulTitleColor
|
||||
|
||||
bold: primary
|
||||
|
||||
onClicked: root.triggerAction(model.index)
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
// actions that do not fit maxActionButtons limit, must be put into a context menu
|
||||
id: moreActionsButtonContainer
|
||||
|
||||
Layout.preferredWidth: parent.height
|
||||
Layout.topMargin: Style.roundedButtonBackgroundVerticalMargins
|
||||
Layout.bottomMargin: Style.roundedButtonBackgroundVerticalMargins
|
||||
Layout.fillHeight: true
|
||||
|
||||
active: root.displayActions && (root.linksContextMenu.length > 0)
|
||||
|
||||
sourceComponent: Button {
|
||||
id: moreActionsButton
|
||||
|
||||
icon.source: "qrc:///client/theme/more.svg"
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.hovered ? "white" : root.moreActionsButtonColor
|
||||
radius: width / 2
|
||||
}
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
ToolTip.text: qsTr("Show more actions")
|
||||
|
||||
Accessible.name: qsTr("Show more actions")
|
||||
|
||||
onClicked: moreActionsButtonContextMenu.popup(moreActionsButton.x, moreActionsButton.y);
|
||||
|
||||
Connections {
|
||||
target: root.flickable
|
||||
|
||||
function onMovementStarted() {
|
||||
moreActionsButtonContextMenu.close();
|
||||
}
|
||||
}
|
||||
|
||||
ActivityItemContextMenu {
|
||||
id: moreActionsButtonContextMenu
|
||||
|
||||
maxActionButtons: root.maxActionButtons
|
||||
linksContextMenu: root.linksContextMenu
|
||||
|
||||
onMenuEntryTriggered: function(entryIndex) {
|
||||
root.triggerAction(entryIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
127
src/gui/tray/ActivityItemContent.qml
Normal file
@@ -0,0 +1,127 @@
|
||||
import QtQml 2.15
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
import Style 1.0
|
||||
import com.nextcloud.desktopclient 1.0
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
property variant activityData: {{}}
|
||||
|
||||
property color activityTextTitleColor: Style.ncTextColor
|
||||
|
||||
property bool showDismissButton: false
|
||||
|
||||
property bool childHovered: shareButton.hovered || dismissActionButton.hovered
|
||||
|
||||
signal dismissButtonClicked()
|
||||
signal shareButtonClicked()
|
||||
|
||||
spacing: 10
|
||||
|
||||
Image {
|
||||
id: activityIcon
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||
Layout.preferredWidth: 32
|
||||
Layout.preferredHeight: 32
|
||||
|
||||
verticalAlignment: Qt.AlignCenter
|
||||
source: icon
|
||||
sourceSize.height: 64
|
||||
sourceSize.width: 64
|
||||
}
|
||||
|
||||
Column {
|
||||
id: activityTextColumn
|
||||
|
||||
Layout.topMargin: 4
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
|
||||
spacing: 4
|
||||
|
||||
Label {
|
||||
id: activityTextTitle
|
||||
text: (root.activityData.type === "Activity" || root.activityData.type === "Notification") ? root.activityData.subject : root.activityData.message
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: Style.topLinePixelSize
|
||||
color: root.activityData.activityTextTitleColor
|
||||
}
|
||||
|
||||
Label {
|
||||
id: activityTextInfo
|
||||
text: (root.activityData.type === "Sync") ? root.activityData.displayPath
|
||||
: (root.activityData.type === "File") ? root.activityData.subject
|
||||
: (root.activityData.type === "Notification") ? root.activityData.message
|
||||
: ""
|
||||
height: (text === "") ? 0 : activityTextTitle.height
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: Style.subLinePixelSize
|
||||
}
|
||||
|
||||
Label {
|
||||
id: activityTextDateTime
|
||||
text: root.activityData.dateTime
|
||||
height: (text === "") ? 0 : activityTextTitle.height
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: Style.subLinePixelSize
|
||||
color: "#808080"
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: dismissActionButton
|
||||
|
||||
Layout.preferredWidth: parent.height * 0.40
|
||||
Layout.preferredHeight: parent.height * 0.40
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
Layout.margins: Style.roundButtonBackgroundVerticalMargins
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
ToolTip.text: qsTr("Dismiss")
|
||||
|
||||
Accessible.name: qsTr("Dismiss")
|
||||
|
||||
visible: root.showDismissButton && !shareButton.visible
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
contentItem: Image {
|
||||
anchors.fill: parent
|
||||
source: parent.hovered ? "image://svgimage-custom-color/clear.svg/black" : "image://svgimage-custom-color/clear.svg/grey"
|
||||
sourceSize.width: 24
|
||||
sourceSize.height: 24
|
||||
}
|
||||
|
||||
onClicked: root.dismissButtonClicked()
|
||||
}
|
||||
|
||||
CustomButton {
|
||||
id: shareButton
|
||||
|
||||
Layout.preferredWidth: parent.height * 0.70
|
||||
Layout.preferredHeight: parent.height * 0.70
|
||||
|
||||
visible: root.activityData.isShareable
|
||||
|
||||
imageSource: "image://svgimage-custom-color/share.svg" + "/" + Style.ncBlue
|
||||
imageSourceHover: "image://svgimage-custom-color/share.svg" + "/" + Style.ncTextColor
|
||||
|
||||
toolTipText: qsTr("Open share dialog")
|
||||
|
||||
bgColor: Style.ncBlue
|
||||
|
||||
onClicked: root.shareButtonClicked()
|
||||
}
|
||||
}
|
||||
25
src/gui/tray/ActivityItemContextMenu.qml
Normal file
@@ -0,0 +1,25 @@
|
||||
import QtQml 2.15
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
AutoSizingMenu {
|
||||
id: moreActionsButtonContextMenu
|
||||
|
||||
property int maxActionButtons: 0
|
||||
|
||||
property var linksContextMenu: []
|
||||
|
||||
signal menuEntryTriggered(int index)
|
||||
|
||||
Repeater {
|
||||
id: moreActionsButtonContextMenuRepeater
|
||||
|
||||
model: moreActionsButtonContextMenu.linksContextMenu
|
||||
|
||||
delegate: MenuItem {
|
||||
id: moreActionsButtonContextMenuEntry
|
||||
text: model.modelData.label
|
||||
onTriggered: menuEntryTriggered(model.modelData.actionIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,26 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
|
||||
import Style 1.0
|
||||
|
||||
import com.nextcloud.desktopclient 1.0 as NC
|
||||
|
||||
ScrollView {
|
||||
id: controlRoot
|
||||
property alias model: activityList.model
|
||||
|
||||
property bool isFileActivityList: false
|
||||
|
||||
signal showFileActivity(string displayPath, string absolutePath)
|
||||
signal activityItemClicked(int index)
|
||||
|
||||
contentWidth: availableWidth
|
||||
padding: 1
|
||||
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
data: NC.WheelHandler {
|
||||
target: controlRoot.contentItem
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: activityList
|
||||
|
||||
@@ -25,12 +31,19 @@ ScrollView {
|
||||
|
||||
clip: true
|
||||
|
||||
spacing: 10
|
||||
|
||||
delegate: ActivityItem {
|
||||
isFileActivityList: controlRoot.isFileActivityList
|
||||
width: activityList.contentWidth
|
||||
height: Style.trayWindowHeaderHeight
|
||||
flickable: activityList
|
||||
onClicked: activityItemClicked(model.index)
|
||||
onFileActivityButtonClicked: showFileActivity(displayPath, absolutePath)
|
||||
onClicked: {
|
||||
if (model.isCurrentUserFileActivity) {
|
||||
showFileActivity(model.displayPath, model.absolutePath)
|
||||
} else {
|
||||
activityItemClicked(model.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
61
src/gui/tray/CustomButton.qml
Normal file
@@ -0,0 +1,61 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.2
|
||||
|
||||
Button {
|
||||
id: root
|
||||
|
||||
property string imageSource: ""
|
||||
property string imageSourceHover: ""
|
||||
|
||||
property string toolTipText: ""
|
||||
|
||||
property color textColor
|
||||
property color textColorHovered
|
||||
|
||||
property color bgColor: "transparent"
|
||||
|
||||
property bool bold: false
|
||||
|
||||
background: Rectangle {
|
||||
color: root.bgColor
|
||||
opacity: parent.hovered ? 1.0 : 0.3
|
||||
radius: width / 2
|
||||
}
|
||||
|
||||
leftPadding: root.text === "" ? 5 : 10
|
||||
rightPadding: root.text === "" ? 5 : 10
|
||||
|
||||
contentItem: RowLayout {
|
||||
Image {
|
||||
id: icon
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
|
||||
source: root.hovered ? root.imageSourceHover : root.imageSource
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.maximumWidth: icon.width > 0 ? parent.width - icon.width - parent.spacing : parent.width
|
||||
Layout.fillWidth: icon.status !== Image.Ready
|
||||
|
||||
text: root.text
|
||||
font.bold: root.bold
|
||||
|
||||
visible: root.text !== ""
|
||||
|
||||
color: root.hovered ? root.textColorHovered : root.textColor
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
ToolTip {
|
||||
text: root.toolTipText
|
||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
visible: root.toolTipText !== "" && root.hovered
|
||||
}
|
||||
}
|
||||
49
src/gui/tray/CustomTextButton.qml
Normal file
@@ -0,0 +1,49 @@
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.3
|
||||
import Style 1.0
|
||||
|
||||
Label {
|
||||
id: root
|
||||
|
||||
property string toolTipText: ""
|
||||
property Action action: null
|
||||
property alias acceptedButtons: mouseArea.acceptedButtons
|
||||
property bool hovered: mouseArea.containsMouse
|
||||
|
||||
height: implicitHeight
|
||||
|
||||
property color textColor: Style.unifiedSearchResulTitleColor
|
||||
property color textColorHovered: Style.unifiedSearchResulSublineColor
|
||||
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: text
|
||||
Accessible.onPressAction: root.clicked(null)
|
||||
|
||||
text: action ? action.text : ""
|
||||
enabled: !action || action.enabled
|
||||
onClicked: if (action) action.trigger()
|
||||
|
||||
font.underline: true
|
||||
color: root.hovered ? root.textColorHovered : root.textColor
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
|
||||
signal pressed(QtObject mouse)
|
||||
signal clicked(QtObject mouse)
|
||||
|
||||
ToolTip {
|
||||
text: root.toolTipText
|
||||
delay: Qt.styleHints.mousePressAndHoldInterval
|
||||
visible: root.toolTipText !== "" && root.hovered
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
onClicked: root.clicked(mouse)
|
||||
onPressed: root.pressed(mouse)
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ Window {
|
||||
height: 500
|
||||
|
||||
ActivityList {
|
||||
isFileActivityList: true
|
||||
anchors.fill: parent
|
||||
model: dialog.model
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ Button {
|
||||
Layout.preferredHeight: Style.trayWindowHeaderHeight
|
||||
|
||||
background: Rectangle {
|
||||
color: root.hovered ? "white" : "transparent"
|
||||
color: root.hovered || root.visualFocus ? "white" : "transparent"
|
||||
opacity: 0.2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ MenuItem {
|
||||
|
||||
property variant dialog;
|
||||
property variant comp;
|
||||
activeFocusOnTab: false
|
||||
|
||||
signal showUserStatusSelectorDialog(int id)
|
||||
|
||||
@@ -35,29 +36,19 @@ MenuItem {
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: qsTr("Switch to account") + " " + name
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onContainsMouseChanged: {
|
||||
accountStatusIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white")
|
||||
}
|
||||
onClicked: {
|
||||
if (!isCurrentUser) {
|
||||
UserModel.switchCurrentUser(id)
|
||||
} else {
|
||||
accountMenu.close()
|
||||
}
|
||||
}
|
||||
onClicked: if (!isCurrentUser) {
|
||||
UserModel.switchCurrentUser(id)
|
||||
} else {
|
||||
accountMenu.close()
|
||||
}
|
||||
|
||||
|
||||
background: Item {
|
||||
height: parent.height
|
||||
width: userLine.menu ? userLine.menu.width : 0
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
color: parent.parent.hovered ? Style.lightHover : "transparent"
|
||||
color: parent.parent.hovered || parent.parent.visualFocus ? Style.lightHover : "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +72,7 @@ MenuItem {
|
||||
height: width
|
||||
anchors.bottom: accountAvatar.bottom
|
||||
anchors.right: accountAvatar.right
|
||||
color: "white"
|
||||
color: accountButton.hovered || accountButton.visualFocus ? "#f6f6f6" : "white"
|
||||
radius: width*0.5
|
||||
}
|
||||
Image {
|
||||
@@ -120,14 +111,12 @@ MenuItem {
|
||||
width: parent.width
|
||||
Label {
|
||||
id: emoji
|
||||
height: Style.topLinePixelSize
|
||||
visible: model.statusEmoji !== ""
|
||||
text: statusEmoji
|
||||
topPadding: -Style.accountLabelsSpacing
|
||||
}
|
||||
Label {
|
||||
id: message
|
||||
height: Style.topLinePixelSize
|
||||
width: parent.width - parent.spacing - emoji.width
|
||||
visible: model.statusMessage !== ""
|
||||
text: statusMessage
|
||||
@@ -163,29 +152,23 @@ MenuItem {
|
||||
Accessible.name: qsTr("Account actions")
|
||||
Accessible.onPressAction: userMoreButtonMouseArea.clicked()
|
||||
|
||||
MouseArea {
|
||||
id: userMoreButtonMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
if (userMoreButtonMenu.visible) {
|
||||
userMoreButtonMenu.close()
|
||||
} else {
|
||||
userMoreButtonMenu.popup()
|
||||
}
|
||||
onClicked: {
|
||||
if (userMoreButtonMenu.visible) {
|
||||
userMoreButtonMenu.close()
|
||||
} else {
|
||||
userMoreButtonMenu.popup()
|
||||
}
|
||||
}
|
||||
background:
|
||||
Rectangle {
|
||||
color: userMoreButtonMouseArea.containsMouse ? "grey" : "transparent"
|
||||
color: userMoreButton.hovered || userMoreButton.visualFocus ? "grey" : "transparent"
|
||||
opacity: 0.2
|
||||
height: userMoreButton.height - 2
|
||||
y: userMoreButton.y + 1
|
||||
}
|
||||
|
||||
Menu {
|
||||
AutoSizingMenu {
|
||||
id: userMoreButtonMenu
|
||||
width: 120
|
||||
closePolicy: Menu.CloseOnPressOutsideParent | Menu.CloseOnEscape
|
||||
|
||||
background: Rectangle {
|
||||
@@ -196,7 +179,6 @@ MenuItem {
|
||||
MenuItem {
|
||||
visible: model.isConnected && model.serverHasUserStatus
|
||||
height: visible ? implicitHeight : 0
|
||||
|
||||
text: qsTr("Set status")
|
||||
font.pixelSize: Style.topLinePixelSize
|
||||
hoverEnabled: true
|
||||
|
||||
@@ -142,187 +142,196 @@ Window {
|
||||
Accessible.name: qsTr("Current account")
|
||||
Accessible.onPressAction: currentAccountButton.clicked()
|
||||
|
||||
MouseArea {
|
||||
id: accountBtnMouseArea
|
||||
// We call open() instead of popup() because we want to position it
|
||||
// exactly below the dropdown button, not the mouse
|
||||
onClicked: {
|
||||
syncPauseButton.text = Systray.syncIsPaused() ? qsTr("Resume sync for all") : qsTr("Pause sync for all")
|
||||
if (accountMenu.visible) {
|
||||
accountMenu.close()
|
||||
} else {
|
||||
accountMenu.open()
|
||||
}
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: Style.hoverEffectsEnabled
|
||||
Loader {
|
||||
id: userStatusSelectorDialogLoader
|
||||
}
|
||||
|
||||
// We call open() instead of popup() because we want to position it
|
||||
// exactly below the dropdown button, not the mouse
|
||||
onClicked: {
|
||||
syncPauseButton.text = Systray.syncIsPaused() ? qsTr("Resume sync for all") : qsTr("Pause sync for all")
|
||||
if (accountMenu.visible) {
|
||||
accountMenu.close()
|
||||
} else {
|
||||
accountMenu.open()
|
||||
Menu {
|
||||
id: accountMenu
|
||||
|
||||
// x coordinate grows towards the right
|
||||
// y coordinate grows towards the bottom
|
||||
x: (currentAccountButton.x + 2)
|
||||
y: (currentAccountButton.y + Style.trayWindowHeaderHeight + 2)
|
||||
|
||||
width: (Style.currentAccountButtonWidth - 2)
|
||||
height: Math.min(implicitHeight, maxMenuHeight)
|
||||
closePolicy: Menu.CloseOnPressOutsideParent | Menu.CloseOnEscape
|
||||
|
||||
background: Rectangle {
|
||||
border.color: Style.menuBorder
|
||||
radius: Style.currentAccountButtonRadius
|
||||
}
|
||||
|
||||
contentItem: ScrollView {
|
||||
id: accMenuScrollView
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
data: WheelHandler {
|
||||
target: accMenuScrollView.contentItem
|
||||
}
|
||||
ListView {
|
||||
implicitHeight: contentHeight
|
||||
model: accountMenu.contentModel
|
||||
interactive: true
|
||||
clip: true
|
||||
currentIndex: accountMenu.currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: userStatusSelectorDialogLoader
|
||||
onClosed: {
|
||||
// HACK: reload account Instantiator immediately by restting it - could be done better I guess
|
||||
// see also onVisibleChanged above
|
||||
userLineInstantiator.active = false;
|
||||
userLineInstantiator.active = true;
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: accountMenu
|
||||
|
||||
// x coordinate grows towards the right
|
||||
// y coordinate grows towards the bottom
|
||||
x: (currentAccountButton.x + 2)
|
||||
y: (currentAccountButton.y + Style.trayWindowHeaderHeight + 2)
|
||||
|
||||
width: (Style.currentAccountButtonWidth - 2)
|
||||
height: Math.min(implicitHeight, maxMenuHeight)
|
||||
closePolicy: Menu.CloseOnPressOutsideParent | Menu.CloseOnEscape
|
||||
|
||||
background: Rectangle {
|
||||
border.color: Style.menuBorder
|
||||
radius: Style.currentAccountButtonRadius
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
// HACK: reload account Instantiator immediately by restting it - could be done better I guess
|
||||
// see also onVisibleChanged above
|
||||
userLineInstantiator.active = false;
|
||||
userLineInstantiator.active = true;
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
id: userLineInstantiator
|
||||
model: UserModel
|
||||
delegate: UserLine {
|
||||
onShowUserStatusSelectorDialog: {
|
||||
userStatusSelectorDialogLoader.source = "qrc:/qml/src/gui/UserStatusSelectorDialog.qml"
|
||||
userStatusSelectorDialogLoader.item.title = qsTr("Set user status")
|
||||
userStatusSelectorDialogLoader.item.model.load(index)
|
||||
userStatusSelectorDialogLoader.item.show()
|
||||
}
|
||||
Instantiator {
|
||||
id: userLineInstantiator
|
||||
model: UserModel
|
||||
delegate: UserLine {
|
||||
onShowUserStatusSelectorDialog: {
|
||||
userStatusSelectorDialogLoader.source = "qrc:/qml/src/gui/UserStatusSelectorDialog.qml"
|
||||
userStatusSelectorDialogLoader.item.title = qsTr("Set user status")
|
||||
userStatusSelectorDialogLoader.item.model.load(index)
|
||||
userStatusSelectorDialogLoader.item.show()
|
||||
}
|
||||
onObjectAdded: accountMenu.insertItem(index, object)
|
||||
onObjectRemoved: accountMenu.removeItem(object)
|
||||
}
|
||||
onObjectAdded: accountMenu.insertItem(index, object)
|
||||
onObjectRemoved: accountMenu.removeItem(object)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
id: addAccountButton
|
||||
height: Style.addAccountButtonHeight
|
||||
hoverEnabled: true
|
||||
MenuItem {
|
||||
id: addAccountButton
|
||||
height: Style.addAccountButtonHeight
|
||||
hoverEnabled: true
|
||||
|
||||
background: Item {
|
||||
height: parent.height
|
||||
width: parent.menu.width
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
color: parent.parent.hovered ? Style.lightHover : "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
background: Item {
|
||||
height: parent.height
|
||||
width: parent.menu.width
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Image {
|
||||
Layout.leftMargin: 12
|
||||
verticalAlignment: Qt.AlignCenter
|
||||
source: "qrc:///client/theme/black/add.svg"
|
||||
sourceSize.width: Style.headerButtonIconSize
|
||||
sourceSize.height: Style.headerButtonIconSize
|
||||
}
|
||||
Label {
|
||||
Layout.leftMargin: 14
|
||||
text: qsTr("Add account")
|
||||
color: "black"
|
||||
font.pixelSize: Style.topLinePixelSize
|
||||
}
|
||||
// Filler on the right
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
onClicked: UserModel.addAccount()
|
||||
|
||||
Accessible.role: Accessible.MenuItem
|
||||
Accessible.name: qsTr("Add new account")
|
||||
Accessible.onPressAction: addAccountButton.clicked()
|
||||
}
|
||||
|
||||
MenuSeparator {
|
||||
contentItem: Rectangle {
|
||||
implicitHeight: 1
|
||||
color: Style.menuBorder
|
||||
anchors.margins: 1
|
||||
color: parent.parent.hovered || parent.parent.visualFocus ? Style.lightHover : "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
id: syncPauseButton
|
||||
font.pixelSize: Style.topLinePixelSize
|
||||
hoverEnabled: true
|
||||
onClicked: Systray.pauseResumeSync()
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
background: Item {
|
||||
height: parent.height
|
||||
width: parent.menu.width
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
color: parent.parent.hovered ? Style.lightHover : "transparent"
|
||||
}
|
||||
Image {
|
||||
Layout.leftMargin: 12
|
||||
verticalAlignment: Qt.AlignCenter
|
||||
source: "qrc:///client/theme/black/add.svg"
|
||||
sourceSize.width: Style.headerButtonIconSize
|
||||
sourceSize.height: Style.headerButtonIconSize
|
||||
}
|
||||
Label {
|
||||
Layout.leftMargin: 14
|
||||
text: qsTr("Add account")
|
||||
color: "black"
|
||||
font.pixelSize: Style.topLinePixelSize
|
||||
}
|
||||
// Filler on the right
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
onClicked: UserModel.addAccount()
|
||||
|
||||
Accessible.role: Accessible.MenuItem
|
||||
Accessible.name: Systray.syncIsPaused() ? qsTr("Resume sync for all") : qsTr("Pause sync for all")
|
||||
Accessible.onPressAction: syncPauseButton.clicked()
|
||||
Accessible.role: Accessible.MenuItem
|
||||
Accessible.name: qsTr("Add new account")
|
||||
Accessible.onPressAction: addAccountButton.clicked()
|
||||
}
|
||||
|
||||
MenuSeparator {
|
||||
contentItem: Rectangle {
|
||||
implicitHeight: 1
|
||||
color: Style.menuBorder
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
id: syncPauseButton
|
||||
font.pixelSize: Style.topLinePixelSize
|
||||
hoverEnabled: true
|
||||
onClicked: Systray.pauseResumeSync()
|
||||
|
||||
background: Item {
|
||||
height: parent.height
|
||||
width: parent.menu.width
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
color: parent.parent.hovered || parent.parent.visualFocus ? Style.lightHover : "transparent"
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
id: settingsButton
|
||||
text: qsTr("Settings")
|
||||
font.pixelSize: Style.topLinePixelSize
|
||||
hoverEnabled: true
|
||||
onClicked: Systray.openSettings()
|
||||
Accessible.role: Accessible.MenuItem
|
||||
Accessible.name: Systray.syncIsPaused() ? qsTr("Resume sync for all") : qsTr("Pause sync for all")
|
||||
Accessible.onPressAction: syncPauseButton.clicked()
|
||||
}
|
||||
|
||||
background: Item {
|
||||
height: parent.height
|
||||
width: parent.menu.width
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
color: parent.parent.hovered ? Style.lightHover : "transparent"
|
||||
}
|
||||
MenuItem {
|
||||
id: settingsButton
|
||||
text: qsTr("Settings")
|
||||
font.pixelSize: Style.topLinePixelSize
|
||||
hoverEnabled: true
|
||||
onClicked: Systray.openSettings()
|
||||
|
||||
background: Item {
|
||||
height: parent.height
|
||||
width: parent.menu.width
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
color: parent.parent.hovered || parent.parent.visualFocus ? Style.lightHover : "transparent"
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.MenuItem
|
||||
Accessible.name: text
|
||||
Accessible.onPressAction: settingsButton.clicked()
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
id: exitButton
|
||||
text: qsTr("Exit");
|
||||
font.pixelSize: Style.topLinePixelSize
|
||||
hoverEnabled: true
|
||||
onClicked: Systray.shutdown()
|
||||
Accessible.role: Accessible.MenuItem
|
||||
Accessible.name: text
|
||||
Accessible.onPressAction: settingsButton.clicked()
|
||||
}
|
||||
|
||||
background: Item {
|
||||
height: parent.height
|
||||
width: parent.menu.width
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
color: parent.parent.hovered ? Style.lightHover : "transparent"
|
||||
}
|
||||
MenuItem {
|
||||
id: exitButton
|
||||
text: qsTr("Exit");
|
||||
font.pixelSize: Style.topLinePixelSize
|
||||
hoverEnabled: true
|
||||
onClicked: Systray.shutdown()
|
||||
|
||||
background: Item {
|
||||
height: parent.height
|
||||
width: parent.menu.width
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
color: parent.parent.hovered || parent.parent.visualFocus ? Style.lightHover : "transparent"
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.MenuItem
|
||||
Accessible.name: text
|
||||
Accessible.onPressAction: exitButton.clicked()
|
||||
}
|
||||
|
||||
Accessible.role: Accessible.MenuItem
|
||||
Accessible.name: text
|
||||
Accessible.onPressAction: exitButton.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: accountBtnMouseArea.containsMouse ? "white" : "transparent"
|
||||
color: parent.hovered || parent.visualFocus ? "white" : "transparent"
|
||||
opacity: 0.2
|
||||
}
|
||||
|
||||
@@ -658,51 +667,61 @@ Window {
|
||||
iconColor: "#afafaf"
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: unifiedSearchResultsListView
|
||||
ScrollView {
|
||||
id: controlRoot
|
||||
padding: 1
|
||||
contentWidth: availableWidth
|
||||
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
|
||||
data: WheelHandler {
|
||||
target: controlRoot.contentItem
|
||||
}
|
||||
visible: unifiedSearchResultsListView.count > 0
|
||||
|
||||
anchors.top: trayWindowUnifiedSearchInputContainer.bottom
|
||||
anchors.left: trayWindowBackground.left
|
||||
anchors.right: trayWindowBackground.right
|
||||
anchors.bottom: trayWindowBackground.bottom
|
||||
spacing: 4
|
||||
visible: count > 0
|
||||
clip: true
|
||||
ScrollBar.vertical: ScrollBar {
|
||||
id: unifiedSearchResultsListViewScrollbar
|
||||
}
|
||||
|
||||
keyNavigationEnabled: true
|
||||
ListView {
|
||||
id: unifiedSearchResultsListView
|
||||
spacing: 4
|
||||
clip: true
|
||||
|
||||
reuseItems: true
|
||||
keyNavigationEnabled: true
|
||||
|
||||
Accessible.role: Accessible.List
|
||||
Accessible.name: qsTr("Unified search results list")
|
||||
reuseItems: true
|
||||
|
||||
model: UserModel.currentUser.unifiedSearchResultsListModel
|
||||
Accessible.role: Accessible.List
|
||||
Accessible.name: qsTr("Unified search results list")
|
||||
|
||||
delegate: UnifiedSearchResultListItem {
|
||||
width: unifiedSearchResultsListView.width
|
||||
height: trayWindowBackground.Style.unifiedSearchItemHeight
|
||||
isSearchInProgress: unifiedSearchResultsListView.model.isSearchInProgress
|
||||
textLeftMargin: trayWindowBackground.Style.unifiedSearchResultTextLeftMargin
|
||||
textRightMargin: trayWindowBackground.Style.unifiedSearchResultTextRightMargin
|
||||
iconWidth: trayWindowBackground.Style.unifiedSearchResulIconWidth
|
||||
iconLeftMargin: trayWindowBackground.Style.unifiedSearchResulIconLeftMargin
|
||||
titleFontSize: trayWindowBackground.Style.unifiedSearchResulTitleFontSize
|
||||
sublineFontSize: trayWindowBackground.Style.unifiedSearchResulSublineFontSize
|
||||
titleColor: trayWindowBackground.Style.unifiedSearchResulTitleColor
|
||||
sublineColor: trayWindowBackground.Style.unifiedSearchResulSublineColor
|
||||
currentFetchMoreInProgressProviderId: unifiedSearchResultsListView.model.currentFetchMoreInProgressProviderId
|
||||
fetchMoreTriggerClicked: unifiedSearchResultsListView.model.fetchMoreTriggerClicked
|
||||
resultClicked: unifiedSearchResultsListView.model.resultClicked
|
||||
ListView.onPooled: isPooled = true
|
||||
ListView.onReused: isPooled = false
|
||||
}
|
||||
model: UserModel.currentUser.unifiedSearchResultsListModel
|
||||
|
||||
section.property: "providerName"
|
||||
section.criteria: ViewSection.FullString
|
||||
section.delegate: UnifiedSearchResultSectionItem {
|
||||
width: unifiedSearchResultsListView.width
|
||||
delegate: UnifiedSearchResultListItem {
|
||||
width: unifiedSearchResultsListView.width
|
||||
height: trayWindowBackground.Style.unifiedSearchItemHeight
|
||||
isSearchInProgress: unifiedSearchResultsListView.model.isSearchInProgress
|
||||
textLeftMargin: trayWindowBackground.Style.unifiedSearchResultTextLeftMargin
|
||||
textRightMargin: trayWindowBackground.Style.unifiedSearchResultTextRightMargin
|
||||
iconWidth: trayWindowBackground.Style.unifiedSearchResulIconWidth
|
||||
iconLeftMargin: trayWindowBackground.Style.unifiedSearchResulIconLeftMargin
|
||||
titleFontSize: trayWindowBackground.Style.unifiedSearchResulTitleFontSize
|
||||
sublineFontSize: trayWindowBackground.Style.unifiedSearchResulSublineFontSize
|
||||
titleColor: trayWindowBackground.Style.unifiedSearchResulTitleColor
|
||||
sublineColor: trayWindowBackground.Style.unifiedSearchResulSublineColor
|
||||
currentFetchMoreInProgressProviderId: unifiedSearchResultsListView.model.currentFetchMoreInProgressProviderId
|
||||
fetchMoreTriggerClicked: unifiedSearchResultsListView.model.fetchMoreTriggerClicked
|
||||
resultClicked: unifiedSearchResultsListView.model.resultClicked
|
||||
ListView.onPooled: isPooled = true
|
||||
ListView.onReused: isPooled = false
|
||||
}
|
||||
|
||||
section.property: "providerName"
|
||||
section.criteria: ViewSection.FullString
|
||||
section.delegate: UnifiedSearchResultSectionItem {
|
||||
width: unifiedSearchResultsListView.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,12 +742,13 @@ Window {
|
||||
anchors.right: trayWindowBackground.right
|
||||
anchors.bottom: trayWindowBackground.bottom
|
||||
|
||||
activeFocusOnTab: true
|
||||
model: activityModel
|
||||
onShowFileActivity: {
|
||||
openFileActivityDialog(displayPath, absolutePath)
|
||||
}
|
||||
onActivityItemClicked: {
|
||||
model.triggerDefaultAction(index)
|
||||
model.slotTriggerDefaultAction(index)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,4 +33,15 @@ Activity::Identifier Activity::ident() const
|
||||
{
|
||||
return Identifier(_id, _accName);
|
||||
}
|
||||
|
||||
ActivityLink ActivityLink::createFomJsonObject(const QJsonObject &obj)
|
||||
{
|
||||
ActivityLink activityLink;
|
||||
activityLink._label = QUrl::fromPercentEncoding(obj.value(QStringLiteral("label")).toString().toUtf8());
|
||||
activityLink._link = obj.value(QStringLiteral("link")).toString();
|
||||
activityLink._verb = obj.value(QStringLiteral("type")).toString().toUtf8();
|
||||
activityLink._primary = obj.value(QStringLiteral("primary")).toBool();
|
||||
|
||||
return activityLink;
|
||||
}
|
||||
}
|
||||
|
||||