Merge pull request #210 from amnezia-vpn/dev

Pre-release 3.0.4
This commit is contained in:
pokamest 2023-04-06 20:05:51 +01:00 committed by GitHub
commit a5e5c3d941
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
312 changed files with 6223 additions and 8789 deletions

View file

@ -1,5 +1,6 @@
name: 'Deploy workflow'
on: [push]
jobs:
@ -8,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
env:
QT_VERSION: 5.15.2
QT_VERSION: 6.4.1
QIF_VERSION: 4.5
steps:
@ -19,6 +20,7 @@ jobs:
host: 'linux'
target: 'desktop'
arch: 'gcc_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
tools: 'tools_ifw'
@ -36,38 +38,34 @@ jobs:
- name: 'Build project'
run: |
sudo apt-get install libxkbcommon-x11-0
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
bash deploy/build_linux.sh
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v3
with:
name: AmneziaVPN_Linux_installer
path: deploy/AmneziaVPN_Linux_Installer
retention-days: 7
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v3
with:
name: AmneziaVPN_Linux_unpacked
path: deploy/AppDir
retention-days: 7
# ------------------------------------------------------
Build-Windows:
name: Build-Windows
runs-on: windows-latest
continue-on-error: true
strategy:
matrix:
arch: [32, 64]
include:
- qt-arch: 'win32_msvc2019'
arch: 32
- qt-msvc-path: 'msvc2019'
arch: 32
- msvc-arch: 'x86'
arch: 32
- qt-arch: 'win64_msvc2019_64'
arch: 64
- qt-msvc-path: 'msvc2019_64'
arch: 64
- msvc-arch: 'x64'
arch: 64
env:
QT_VERSION: 5.15.2
QT_VERSION: 6.4.1
QIF_VERSION: 4.5
BUILD_ARCH: ${{ matrix.arch }}
BUILD_ARCH: 64
steps:
- name: 'Get sources'
@ -85,7 +83,8 @@ jobs:
version: ${{ env.QT_VERSION }}
host: 'windows'
target: 'desktop'
arch: '${{ matrix.qt-arch }}'
arch: 'win64_msvc2019_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
tools: 'tools_ifw'
@ -95,16 +94,29 @@ jobs:
- name: 'Setup mvsc'
uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.msvc-arch }}
arch: 'x64'
- name: 'Build project'
shell: cmd
run: |
set BUILD_ARCH=${{ env.BUILD_ARCH }}
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\${{ matrix.qt-msvc-path }}\\bin"
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2019_64\\bin"
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
call deploy\\build_windows.bat
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v3
with:
name: AmneziaVPN_Windows_installer
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
retention-days: 7
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v3
with:
name: AmneziaVPN_Windows_unpacked
path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release
retention-days: 7
# ------------------------------------------------------
Build-IOS:
@ -112,7 +124,7 @@ jobs:
runs-on: macos-latest
env:
QT_VERSION: 5.15.2
QT_VERSION: 6.4.1
QIF_VERSION: 4.4
steps:
@ -121,12 +133,24 @@ jobs:
with:
xcode-version: '13.4'
- name: 'Install Qt'
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
set-env: 'true'
- name: 'Install ios Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'ios'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
@ -153,9 +177,21 @@ jobs:
- name: 'Build project'
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/ios/bin"
export QT_MACOS_ROOT_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos"
export QT_IOS_BIN=$QT_BIN_DIR
export PATH=$PATH:~/go/bin
bash deploy/build_ios.sh
mkdir build-ios
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
# - name: iOS Build/Release With Multiple Targets Action
# uses: kumarsunil0007/build-ios-action@latest
# with:
# project-path: build-ios/AmneziaVPN.xcodeproj
# p12-base64: ~/amnezia-vpn/amnezia-ios-certificates/certs/distribution/443886Q5PL.p12
# mobileprovision-base64: ~/amnezia-vpn/amnezia-ios-certificates/testprofilez.mobileprovision
# code-signing-identity: 'iOS Distribution'
# team-id: 'X7UJ388FXK'
# configuration: Release
# ------------------------------------------------------
@ -164,7 +200,7 @@ jobs:
runs-on: macos-latest
env:
QT_VERSION: 5.15.2
QT_VERSION: 6.4.1
QIF_VERSION: 4.5
steps:
@ -180,6 +216,7 @@ jobs:
host: 'mac'
target: 'desktop'
arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
tools: 'tools_ifw'
@ -197,28 +234,60 @@ jobs:
- name: 'Build project'
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/clang_64/bin"
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin"
bash deploy/build_macos.sh
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v3
with:
name: AmneziaVPN_MacOS_installer
path: AmneziaVPN.dmg
retention-days: 7
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v3
with:
name: AmneziaVPN_MacOS_unpacked
path: deploy/build/client/AmneziaVPN.app
retention-days: 7
# ------------------------------------------------------
Build-Android:
name: 'Build-Android'
runs-on: ubuntu-latest
continue-on-error: true
strategy:
matrix:
arch: ['android_x86_64', 'android_x86'] #, 'android_armv7', 'android_arm64_v8a']
env:
QT_VERSION: 5.15.2
QT_VERSION: 6.4.1
QIF_VERSION: 4.5
steps:
- name: 'Install Qt'
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'desktop'
arch: 'gcc_64'
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
extra: '--external 7z'
- name: 'Install android Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'android'
arch: 'android'
arch: ${{ matrix.arch }}
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
@ -237,19 +306,21 @@ jobs:
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '8'
java-version: '11'
- name: 'Build project'
run: |
export NDK_VERSION=21d
export ANDROID_NDK_PLATFORM=android-21
export QT_HOST_PATH="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64"
export NDK_VERSION=23c
export ANDROID_NDK_PLATFORM=android-23
export ANDROID_NDK_HOME=${{ runner.temp }}/android-ndk-r${NDK_VERSION}
export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME
export ANDROID_CURRENT_ARCH=${{ matrix.arch }}
if [ ! -f $ANDROID_NDK_ROOT/ndk-build ]; then
wget https://dl.google.com/android/repository/android-ndk-r${NDK_VERSION}-linux-x86_64.zip -qO ${{ runner.temp }}/ndk.zip &&
wget https://dl.google.com/android/repository/android-ndk-r${NDK_VERSION}-linux.zip -qO ${{ runner.temp }}/ndk.zip &&
unzip -q -d ${{ runner.temp }} ${{ runner.temp }}/ndk.zip ;
fi
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/android/bin
bash deploy/build_android.sh
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/${{ matrix.arch }}/bin
bash deploy/build_android.sh

142
.github/workflows/tag-deploy.yml vendored Normal file
View file

@ -0,0 +1,142 @@
name: 'Release deploy workflow'
on:
workflow_dispatch:
# push:
# tags:
# - **
jobs:
Build-Android-Release:
name: 'Build-Android-Release'
runs-on: ubuntu-latest
env:
QT_VERSION: 6.4.1
QIF_VERSION: 4.5
steps:
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'desktop'
arch: 'gcc_64'
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
extra: '--external 7z'
- name: 'Install android Qt x86_64'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'android'
arch: 'android_x86_64'
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
extra: '--external 7z'
- name: 'Install android Qt x86'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'android'
arch: 'android_x86'
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
extra: '--external 7z'
- name: 'Install android Qt arm_v7'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'android'
arch: 'android_armv7'
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
extra: '--external 7z'
- name: 'Install android Qt arm_v8'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'android'
arch: 'android_arm64_v8a'
modules: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
extra: '--external 7z'
- name: 'Get sources'
uses: actions/checkout@v3
with:
path: main
submodules: 'true'
fetch-depth: 10
- name: 'Preparations before keystore fetching'
run: |
mkdir keystore
- name: 'Getting keystore'
uses: actions/checkout@v3
with:
repository: amnezia-vpn/amnezia-android-certificates
ssh-key: ${{ secrets.ANDROID_CERTS_SSH_PRIVATE_KEY }}
path: keystore
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup Java'
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '11'
- name: 'Build project'
run: |
export QT_HOST_PATH="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64"
export NDK_VERSION=23c
export ANDROID_NDK_PLATFORM=android-23
export ANDROID_NDK_HOME=${{ runner.temp }}/android-ndk-r${NDK_VERSION}
export ANDROID_NDK_ROOT=$ANDROID_NDK_HOME
if [ ! -f $ANDROID_NDK_ROOT/ndk-build ]; then
wget https://dl.google.com/android/repository/android-ndk-r${NDK_VERSION}-linux.zip -qO ${{ runner.temp }}/ndk.zip &&
unzip -q -d ${{ runner.temp }} ${{ runner.temp }}/ndk.zip ;
fi
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/android_arm64_v8a/bin
cd main
bash deploy/build_android.sh
- name: 'Signing APK'
run: |
pwd
ANDROID_BUILD_TOOLS_VERSION=30.0.3
${ANDROID_HOME}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}/zipalign -f -v 4 AmneziaVPN-release-unsigned.apk AmneziaVPN-release-aligned.apk
${ANDROID_HOME}/build-tools/${ANDROID_BUILD_TOOLS_VERSION}/apksigner sign --out AmneziaVPN-release-signed.apk --ks keystore/debug.keystore --ks-key-alias ${{ secrets.DEBUG_ANDROID_KEYSTORE_KEY_ALIAS }} --ks-pass pass:${{secrets.DEBUG_ANDROID_KEYSTOTE_KEY_PASS }} AmneziaVPN-release-aligned.apk
- name: 'Upload'
uses: actions/upload-artifact@v3
with:
name: Release APK
path: ${{ runner.temp }}/main/AmneziaVPN-release-signed.apk

4
.gitignore vendored
View file

@ -23,6 +23,7 @@ qrc_*.cpp
ui_*.h
Makefile*
*build-*
compile_commands.json
# fastlane
client/fastlane/report.xml
@ -126,3 +127,6 @@ captures/
# Android Profiling
*.hprof
client/3rd/ShadowSocks/ss_ios.xcconfig
# UML generated pics
out/

6
.gitmodules vendored
View file

@ -25,3 +25,9 @@
[submodule "client/3rd/qtkeychain"]
path = client/3rd/qtkeychain
url = https://github.com/frankosterfeld/qtkeychain.git
[submodule "deploy/amnezia-ios-certificates"]
path = deploy/amnezia-ios-certificates
url = https://github.com/amnezia-vpn/amnezia-ios-certificates.git
[submodule "client/3rd/SortFilterProxyModel"]
path = client/3rd/SortFilterProxyModel
url = https://github.com/mitchcurtis/SortFilterProxyModel.git

18
CMakeLists.txt Normal file
View file

@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT})
if(ANDROID)
set(QT_ANDROID_BUILD_ALL_ABIS ON)
endif()
if(APPLE AND NOT IOS)
set(CMAKE_OSX_ARCHITECTURES "x86_64")
endif()
add_subdirectory(client)
if(NOT IOS AND NOT ANDROID)
add_subdirectory(service)
endif()

View file

@ -67,7 +67,7 @@ a "`qmake` cannot be found in your `$PATH`" error. In this case run this script
using QT\IOS\_BIN env to set the path for the Qt5 macos build bin folder.
For example, the path could look like this:
```bash
QT_IOS_BIN="/Users/username/Qt/5.15.2/ios/bin" ./scripts/apple_compile.sh ios
QT_IOS_BIN="/Users/username/Qt/6.4.1/ios/bin" ./scripts/apple_compile.sh ios
```
If you get `gomobile: command not found` make sure to set PATH to the location
@ -96,6 +96,31 @@ Build might fail with "source files not found" error the first time you try it,
dependencies in parallel, and some dependencies end up being built after the ones that
require them. In this case simply restart the build.
## How to build the Android app
_tested on Mac OS_
The Android app has the following requirements:
* JDK 11
* Android platform SDK 33
* cmake 3.25.0
After you have installed QT, QT Creator and Android Studio installed, you need to configure QT Creator correctly. Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`.
* set path to jdk 11
* set path to Android SDK ($ANDROID_HOME)
In case you get errors regarding missing SDK or 'sdkmanager not running', you cannot fix them by correcting the paths and you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and click on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine!
Double check that the right cmake version is configured: Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab you'll find an entry `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at <path>` from the drop down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case click in the preferences window on the side menu item `CMake`, then on the tab `Tools`in the center content view and finally on the Button `Add` to set the path to your installed CMake.
Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on on `Projects`, on the left you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that always all Android targets will be build. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (The `Details` button might be hidden in case QT Creator Window is not running in full screen!). Here we are: choose `android-33` as `Android Build platfrom SDK`.
That's it you should be ready to compile the project from QT Creator!
### Development flow
After you've hit the build button, QT-Creator copies the whole project to a folder in the repositories parent directory. The folder should look something like `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`.
If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated proejct, you'll need to copy and paste the affected files to the corresponding path in the repositories Android project so that you can add and commit your changes!
You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the geneated project's root directory (`<path>/client/android-build/.`) and you should be good to continue.
## License
GPL v.3

View file

@ -0,0 +1,20 @@
include_directories(${CMAKE_CURRENT_LIST_DIR})
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/include/QAead.h
${CMAKE_CURRENT_LIST_DIR}/include/QBlockCipher.h
${CMAKE_CURRENT_LIST_DIR}/include/QCryptoError.h
${CMAKE_CURRENT_LIST_DIR}/include/QRsa.h
${CMAKE_CURRENT_LIST_DIR}/include/QSimpleCrypto_global.h
${CMAKE_CURRENT_LIST_DIR}/include/QX509.h
${CMAKE_CURRENT_LIST_DIR}/include/QX509Store.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/sources/QAead.cpp
${CMAKE_CURRENT_LIST_DIR}/sources/QBlockCipher.cpp
${CMAKE_CURRENT_LIST_DIR}/sources/QCryptoError.cpp
${CMAKE_CURRENT_LIST_DIR}/sources/QRsa.cpp
${CMAKE_CURRENT_LIST_DIR}/sources/QX509.cpp
${CMAKE_CURRENT_LIST_DIR}/sources/QX509Store.cpp
)

View file

@ -0,0 +1,71 @@
include_directories(
${CMAKE_CURRENT_LIST_DIR}/include
${CMAKE_CURRENT_LIST_DIR}/include/external
)
if(WIN32)
add_compile_definitions(MVPN_WINDOWS)
add_compile_options(/bigobj)
set(LIBS ${LIBS}
crypt32
)
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
include_directories(${CMAKE_CURRENT_LIST_DIR}/windows/x86_64)
set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/windows/x86_64/botan_all.h)
set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/windows/x86_64/botan_all.cpp)
else()
include_directories(${CMAKE_CURRENT_LIST_DIR}/windows/x86)
set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/windows/x86/botan_all.h)
set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/windows/x86/botan_all.cpp)
endif()
endif()
if(APPLE AND NOT IOS)
include_directories(${CMAKE_CURRENT_LIST_DIR}/macos)
set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/macos/botan_all.h)
set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/macos/botan_all.cpp)
endif()
if(LINUX AND NOT ANDROID)
include_directories(${CMAKE_CURRENT_LIST_DIR}/linux)
set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/linux/botan_all.h)
set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/linux/botan_all.cpp)
set(LIBS ${LIBS} dl)
endif()
if(ANDROID)
# We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api
set(LIBS ${LIBS} Qt6::CorePrivate)
set(abi ${CMAKE_ANDROID_ARCH_ABI})
include_directories(${CMAKE_CURRENT_LIST_DIR}/android/${abi})
link_directories(${CMAKE_CURRENT_LIST_DIR}/android/${abi})
set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/android/${abi}/botan_all.h)
set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/android/${abi}/botan_all.cpp)
endif()
if(IOS)
# CONFIG(iphoneos, iphoneos|iphonesimulator) {
# contains(QT_ARCH, arm64) {
# INCLUDEPATH += $$PWD/ios/iphone
# HEADERS += $$PWD/ios/iphone/botan_all.h
# SOURCES += $$PWD/ios/iphone/botan_all.cpp
# } else {
# message("Building for iOS/ARM v7 (32-bit) architecture")
# ARCH_TAG = "ios_armv7"
# }
# }
# CONFIG(iphonesimulator, iphoneos|iphonesimulator) {
# INCLUDEPATH += $$PWD/ios/iphone
# HEADERS += $$PWD/ios/iphone/botan_all.h
# SOURCES += $$PWD/ios/iphone/botan_all.cpp
# }
include_directories(${CMAKE_CURRENT_LIST_DIR}/ios/iphone)
set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/ios/iphone/botan_all.h)
set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/ios/iphone/botan_all.cpp)
endif()

View file

@ -37,13 +37,27 @@ linux-g++ {
}
android {
for (abi, ANDROID_ABIS): {
equals(ANDROID_TARGET_ARCH,$$abi) {
INCLUDEPATH += $$PWD/android/$${abi}
HEADERS += $$PWD/android/$${abi}/botan_all.h
SOURCES += $$PWD/android/$${abi}/botan_all.cpp
}
}
versionAtLeast(QT_VERSION, 6.0.0) {
# We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api
QT+=core-private
ANDROID_ABIS=ANDROID_TARGET_ARCH
INCLUDEPATH += $$PWD/android/$${ANDROID_TARGET_ARCH}
HEADERS += $$PWD/android/$${ANDROID_TARGET_ARCH}/botan_all.h
SOURCES += $$PWD/android/$${ANDROID_TARGET_ARCH}/botan_all.cpp
}
else {
QT += androidextras
for (abi, ANDROID_ABIS): {
equals(ANDROID_TARGET_ARCH,$$abi) {
INCLUDEPATH += $$PWD/android/$${abi}
HEADERS += $$PWD/android/$${abi}/botan_all.h
SOURCES += $$PWD/android/$${abi}/botan_all.cpp
}
}
}
}
ios: {

View file

@ -0,0 +1,96 @@
include_directories(${CMAKE_CURRENT_LIST_DIR})
find_package(Qt6 REQUIRED COMPONENTS
Widgets Gui Network Core5Compat
)
set(LIBS ${LIBS} Qt6::Widgets Qt6::Gui Qt6::Network Qt6::Core5Compat)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/sshsendfacility.cpp
${CMAKE_CURRENT_LIST_DIR}/sshremoteprocess.cpp
${CMAKE_CURRENT_LIST_DIR}/sshpacketparser.cpp
${CMAKE_CURRENT_LIST_DIR}/sshpacket.cpp
${CMAKE_CURRENT_LIST_DIR}/sshoutgoingpacket.cpp
${CMAKE_CURRENT_LIST_DIR}/sshkeygenerator.cpp
${CMAKE_CURRENT_LIST_DIR}/sshkeyexchange.cpp
${CMAKE_CURRENT_LIST_DIR}/sshincomingpacket.cpp
${CMAKE_CURRENT_LIST_DIR}/sshcryptofacility.cpp
${CMAKE_CURRENT_LIST_DIR}/sshconnection.cpp
${CMAKE_CURRENT_LIST_DIR}/sshchannelmanager.cpp
${CMAKE_CURRENT_LIST_DIR}/sshchannel.cpp
${CMAKE_CURRENT_LIST_DIR}/sshcapabilities.cpp
${CMAKE_CURRENT_LIST_DIR}/sftppacket.cpp
${CMAKE_CURRENT_LIST_DIR}/sftpoutgoingpacket.cpp
${CMAKE_CURRENT_LIST_DIR}/sftpoperation.cpp
${CMAKE_CURRENT_LIST_DIR}/sftpincomingpacket.cpp
${CMAKE_CURRENT_LIST_DIR}/sftpdefs.cpp
${CMAKE_CURRENT_LIST_DIR}/sftpchannel.cpp
${CMAKE_CURRENT_LIST_DIR}/sshremoteprocessrunner.cpp
${CMAKE_CURRENT_LIST_DIR}/sshconnectionmanager.cpp
${CMAKE_CURRENT_LIST_DIR}/sshkeypasswordretriever.cpp
${CMAKE_CURRENT_LIST_DIR}/sftpfilesystemmodel.cpp
${CMAKE_CURRENT_LIST_DIR}/sshdirecttcpiptunnel.cpp
${CMAKE_CURRENT_LIST_DIR}/sshhostkeydatabase.cpp
${CMAKE_CURRENT_LIST_DIR}/sshlogging.cpp
${CMAKE_CURRENT_LIST_DIR}/sshtcpipforwardserver.cpp
${CMAKE_CURRENT_LIST_DIR}/sshtcpiptunnel.cpp
${CMAKE_CURRENT_LIST_DIR}/sshforwardedtcpiptunnel.cpp
${CMAKE_CURRENT_LIST_DIR}/sshagent.cpp
${CMAKE_CURRENT_LIST_DIR}/sshx11channel.cpp
${CMAKE_CURRENT_LIST_DIR}/sshx11inforetriever.cpp
${CMAKE_CURRENT_LIST_DIR}/opensshkeyfilereader.cpp
)
set(PUBLIC_HEADERS ${PUBLIC_HEADERS}
${CMAKE_CURRENT_LIST_DIR}/sftpdefs.h
${CMAKE_CURRENT_LIST_DIR}/ssherrors.h
${CMAKE_CURRENT_LIST_DIR}/sshremoteprocess.h
${CMAKE_CURRENT_LIST_DIR}/sftpchannel.h
${CMAKE_CURRENT_LIST_DIR}/sshkeygenerator.h
${CMAKE_CURRENT_LIST_DIR}/sshremoteprocessrunner.h
${CMAKE_CURRENT_LIST_DIR}/sshconnectionmanager.h
${CMAKE_CURRENT_LIST_DIR}/sshpseudoterminal.h
${CMAKE_CURRENT_LIST_DIR}/sftpfilesystemmodel.h
${CMAKE_CURRENT_LIST_DIR}/sshdirecttcpiptunnel.h
${CMAKE_CURRENT_LIST_DIR}/sshtcpipforwardserver.h
${CMAKE_CURRENT_LIST_DIR}/sshhostkeydatabase.h
${CMAKE_CURRENT_LIST_DIR}/sshforwardedtcpiptunnel.h
${CMAKE_CURRENT_LIST_DIR}/ssh_global.h
${CMAKE_CURRENT_LIST_DIR}/sshconnection.h
)
set(HEADERS ${HEADERS}
${PUBLIC_HEADERS}
${CMAKE_CURRENT_LIST_DIR}/sshsendfacility_p.h
${CMAKE_CURRENT_LIST_DIR}/sshremoteprocess_p.h
${CMAKE_CURRENT_LIST_DIR}/sshpacketparser_p.h
${CMAKE_CURRENT_LIST_DIR}/sshpacket_p.h
${CMAKE_CURRENT_LIST_DIR}/sshoutgoingpacket_p.h
${CMAKE_CURRENT_LIST_DIR}/sshkeyexchange_p.h
${CMAKE_CURRENT_LIST_DIR}/sshincomingpacket_p.h
${CMAKE_CURRENT_LIST_DIR}/sshexception_p.h
${CMAKE_CURRENT_LIST_DIR}/sshcryptofacility_p.h
${CMAKE_CURRENT_LIST_DIR}/sshconnection_p.h
${CMAKE_CURRENT_LIST_DIR}/sshchannelmanager_p.h
${CMAKE_CURRENT_LIST_DIR}/sshchannel_p.h
${CMAKE_CURRENT_LIST_DIR}/sshcapabilities_p.h
${CMAKE_CURRENT_LIST_DIR}/sshbotanconversions_p.h
${CMAKE_CURRENT_LIST_DIR}/sftppacket_p.h
${CMAKE_CURRENT_LIST_DIR}/sftpoutgoingpacket_p.h
${CMAKE_CURRENT_LIST_DIR}/sftpoperation_p.h
${CMAKE_CURRENT_LIST_DIR}/sftpincomingpacket_p.h
${CMAKE_CURRENT_LIST_DIR}/sftpchannel_p.h
${CMAKE_CURRENT_LIST_DIR}/sshkeypasswordretriever_p.h
${CMAKE_CURRENT_LIST_DIR}/sshdirecttcpiptunnel_p.h
${CMAKE_CURRENT_LIST_DIR}/sshlogging_p.h
${CMAKE_CURRENT_LIST_DIR}/sshtcpipforwardserver_p.h
${CMAKE_CURRENT_LIST_DIR}/sshtcpiptunnel_p.h
${CMAKE_CURRENT_LIST_DIR}/sshforwardedtcpiptunnel_p.h
${CMAKE_CURRENT_LIST_DIR}/sshagent_p.h
${CMAKE_CURRENT_LIST_DIR}/sshx11channel_p.h
${CMAKE_CURRENT_LIST_DIR}/sshx11displayinfo_p.h
${CMAKE_CURRENT_LIST_DIR}/sshx11inforetriever_p.h
${CMAKE_CURRENT_LIST_DIR}/opensshkeyfilereader_p.h
)
# qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/qssh.qrc)

View file

@ -1,4 +1,5 @@
QT += gui network widgets
equals(QT_MAJOR_VERSION, 6): QT += core5compat
INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD

View file

@ -47,7 +47,7 @@
#include <QMutex>
#include <QMutexLocker>
#include <QNetworkProxy>
#include <QRegExp>
#include <QRegularExpression>
#include <QTcpSocket>
namespace QSsh {
@ -401,14 +401,14 @@ void SshConnectionPrivate::handleServerId()
// "printable US-ASCII characters, with the exception of whitespace characters
// and the minus sign"
QString legalString = QLatin1String("[]!\"#$!&'()*+,./0-9:;<=>?@A-Z[\\\\^_`a-z{|}~]+");
const QRegExp versionIdpattern(QString::fromLatin1("SSH-(%1)-%1(?: .+)?.*").arg(legalString));
if (!versionIdpattern.exactMatch(QString::fromLatin1(m_serverId))) {
const QRegularExpression versionIdpattern(QString::fromLatin1("SSH-(%1)-%1(?: .+)?.*").arg(legalString));
if (!versionIdpattern.match(QString::fromLatin1(m_serverId)).hasMatch()) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR,
"Identification string is invalid.",
tr("Server Identification string \"%1\" is invalid.")
.arg(QString::fromLatin1(m_serverId)));
}
const QString serverProtoVersion = versionIdpattern.cap(1);
const QString serverProtoVersion = versionIdpattern.match(QString::fromLatin1(m_serverId)).captured(1);
if (serverProtoVersion != QLatin1String("2.0") && serverProtoVersion != QLatin1String("1.99")) {
throw SshServerException(SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
"Invalid protocol version.",

View file

@ -138,7 +138,7 @@ QByteArray AbstractSshPacket::generateMac(const SshAbstractCryptoFacility &crypt
quint32 seqNr) const
{
const quint32 seqNrBe = qToBigEndian(seqNr);
QByteArray data(reinterpret_cast<const char *>(&seqNrBe), sizeof seqNrBe);
QByteArray data(reinterpret_cast<const char *>(&seqNrBe), static_cast<int>(sizeof seqNrBe));
data += QByteArray(m_data.constData(), length() + 4);
return crypt.generateMac(data, data.size());
}
@ -150,8 +150,9 @@ quint32 AbstractSshPacket::minPacketSize() const
void AbstractSshPacket::setLengthField(QByteArray &data)
{
const quint32 length = qToBigEndian(data.size() - 4);
data.replace(0, 4, reinterpret_cast<const char *>(&length), 4);
const quint32 length = qToBigEndian<quint32>(data.size() - 4);
data.replace(static_cast<qsizetype>(0), static_cast<qsizetype>(4),
reinterpret_cast<const char *>(&length), static_cast<qsizetype>(4));
}
} // namespace Internal

View file

@ -107,7 +107,7 @@ SshX11InfoRetriever::SshX11InfoRetriever(const QString &displayName, QObject *pa
return;
}
if (dotIndex != -1) {
displayInfo.screen = m_displayName.midRef(dotIndex + 1).toInt(&ok);
displayInfo.screen = m_displayName.mid(dotIndex + 1).toInt(&ok);
if (!ok) {
emitFailure(tr("Invalid display name \"%1\"").arg(m_displayName));
return;

View file

@ -0,0 +1,25 @@
include_directories(${CMAKE_CURRENT_LIST_DIR})
find_package(Qt6 REQUIRED COMPONENTS
Core Network
)
set(LIBS ${LIBS} Qt6::Core Qt6::Network)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/singleapplication.h
${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/singleapplication.cpp
${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.cpp
)
if(WIN32)
if(MSVC)
set(LIBS ${LIBS} Advapi32.lib)
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
set(LIBS ${LIBS} advapi32)
endif()
endif()

@ -0,0 +1 @@
Subproject commit f2881493e42bd7b7d5b7abe804dad084dd610b71

View file

@ -1,77 +0,0 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
*.qmlc
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
# qtcreator shadow builds
build-SortFilterProxyModel-*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe

View file

@ -1,49 +0,0 @@
cmake_minimum_required(VERSION 3.1)
set(CMAKE_CXX_STANDARD 11)
find_package(Qt5 REQUIRED
Core
Qml
)
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON) # This is to find generated *.moc and *.h files in build dir
add_library(SortFilterProxyModel OBJECT
qqmlsortfilterproxymodel.cpp
filters/filter.cpp
filters/filtercontainer.cpp
filters/rolefilter.cpp
filters/valuefilter.cpp
filters/indexfilter.cpp
filters/regexpfilter.cpp
filters/rangefilter.cpp
filters/expressionfilter.cpp
filters/filtercontainerfilter.cpp
filters/anyoffilter.cpp
filters/alloffilter.cpp
filters/filtersqmltypes.cpp
sorters/sorter.cpp
sorters/sortercontainer.cpp
sorters/rolesorter.cpp
sorters/stringsorter.cpp
sorters/expressionsorter.cpp
sorters/sortersqmltypes.cpp
proxyroles/proxyrole.cpp
proxyroles/proxyrolecontainer.cpp
proxyroles/joinrole.cpp
proxyroles/switchrole.cpp
proxyroles/expressionrole.cpp
proxyroles/proxyrolesqmltypes.cpp
proxyroles/singlerole.cpp
proxyroles/regexprole.cpp
sorters/filtersorter.cpp
proxyroles/filterrole.cpp
)
target_include_directories(SortFilterProxyModel PUBLIC
${CMAKE_CURRENT_LIST_DIR}
$<TARGET_PROPERTY:Qt5::Core,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:Qt5::Qml,INTERFACE_INCLUDE_DIRECTORIES>
)

View file

@ -1,8 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 Pierre-Yves Siret
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,126 +0,0 @@
SortFilterProxyModel
====================
SortFilterProxyModel is an implementation of `QSortFilterProxyModel` conveniently exposed for QML.
Install
-------
##### With [qpm](https://qpm.io) :
1. `qpm install fr.grecko.sortfilterproxymodel`
2. add `include(vendor/vendor.pri)` in your .pro if it is not already done
3. `import SortFilterProxyModel 0.2` to use this library in your QML files
##### Without qpm :
1. clone or download this repository
2. * `qmake` add `include (<path/to/SortFilterProxyModel>/SortFilterProxyModel.pri)` in your `.pro`
* `CMake` add $<TARGET_OBJECTS:SortFilterProxyModel> to the sources of your executable target in your cmake project
3. `import SortFilterProxyModel 0.2` to use this library in your QML files
Sample Usage
------------
- You can do simple filtering and sorting with SortFilterProxyModel:
```qml
import QtQuick 2.2
import QtQuick.Controls 1.2
import SortFilterProxyModel 0.2
ApplicationWindow {
visible: true
width: 640
height: 480
ListModel {
id: personModel
ListElement {
firstName: "Erwan"
lastName: "Castex"
favorite: true
}
// ...
}
TextField {
id: textField
anchors { top: parent.top; left: parent.left; right: parent.right }
height: implicitHeight
}
SortFilterProxyModel {
id: personProxyModel
sourceModel: personModel
filters: RegExpFilter {
roleName: "lastName"
pattern: textField.text
caseSensitivity: Qt.CaseInsensitive
}
sorters: StringSorter { roleName: "firstName" }
}
ListView {
anchors { top: textField.bottom; bottom: parent.bottom; left: parent.left; right: parent.right }
model: personProxyModel
delegate: Text { text: model.firstName + " " + model.lastName}
}
}
```
Here the `ListView` will only show elements that contains the content of the `TextField` in their `lastName` role.
- But you can also achieve more complex filtering or sorting with multiple `filters` and `sorters`:
```qml
SortFilterProxyModel {
id: personProxyModel
sourceModel: personModel
filters: [
ValueFilter {
enabled: onlyShowFavoritesCheckbox.checked
roleName: "favorite"
value: true
},
AnyOf {
RegExpFilter {
roleName: "lastName"
pattern: textField.text
caseSensitivity: Qt.CaseInsensitive
}
RegExpFilter {
roleName: "firstName"
pattern: textField.text
caseSensitivity: Qt.CaseInsensitive
}
}
]
sorters: [
RoleSorter { roleName: "favorite"; sortOrder: Qt.DescendingOrder },
StringSorter { roleName: "firstName" },
StringSorter { roleName: "lastName" }
]
}
CheckBox {
id:onlyShowFavoritesCheckbox
}
```
This will show in the corresponding `ListView` only the elements where the `firstName` or the `lastName` match the text entered in the `textField`, and if the `onlyShowFavoritesCheckbox` is checked it will aditionnally filter the elements where `favorite` is `true`.
The favorited elements will be shown first and all the elements are sorted by `firstName` and then `lastName`.
Showcase Application
--------------------
You can find an application showcasing this library here: https://github.com/oKcerG/SFPMShowcase
License
-------
This library is licensed under the MIT License.
Documentation
-------------
This component is a subclass of [`QSortFilterProxyModel`](http://doc.qt.io/qt-5/qsortfilterproxymodel.html), to use it, you need to set the `sourceModel` property to a [`QAbstractItemModel*`](http://doc.qt.io/qt-5/qabstractitemmodel.html) with correct role names.
This means you can use it with custom c++ models or `ListModel`, but not with JavaScript models like arrays, integers or object instances.
The complete documentation reference is available here: https://okcerg.github.io/SortFilterProxyModel/
Contributing
------------
Don't hesitate to open an issue about a suggestion, a bug, a lack of clarity in the documentation, etc.
Pull requests are also welcome, if it's a important change you should open an issue first though.

View file

@ -1,58 +0,0 @@
INCLUDEPATH += $$PWD
HEADERS += $$PWD/qqmlsortfilterproxymodel.h \
$$PWD/filters/filter.h \
$$PWD/filters/filtercontainer.h \
$$PWD/filters/rolefilter.h \
$$PWD/filters/valuefilter.h \
$$PWD/filters/indexfilter.h \
$$PWD/filters/regexpfilter.h \
$$PWD/filters/rangefilter.h \
$$PWD/filters/expressionfilter.h \
$$PWD/filters/filtercontainerfilter.h \
$$PWD/filters/anyoffilter.h \
$$PWD/filters/alloffilter.h \
$$PWD/sorters/sorter.h \
$$PWD/sorters/sortercontainer.h \
$$PWD/sorters/rolesorter.h \
$$PWD/sorters/stringsorter.h \
$$PWD/sorters/expressionsorter.h \
$$PWD/proxyroles/proxyrole.h \
$$PWD/proxyroles/proxyrolecontainer.h \
$$PWD/proxyroles/joinrole.h \
$$PWD/proxyroles/switchrole.h \
$$PWD/proxyroles/expressionrole.h \
$$PWD/proxyroles/singlerole.h \
$$PWD/proxyroles/regexprole.h \
$$PWD/sorters/filtersorter.h \
$$PWD/proxyroles/filterrole.h
SOURCES += $$PWD/qqmlsortfilterproxymodel.cpp \
$$PWD/filters/filter.cpp \
$$PWD/filters/filtercontainer.cpp \
$$PWD/filters/rolefilter.cpp \
$$PWD/filters/valuefilter.cpp \
$$PWD/filters/indexfilter.cpp \
$$PWD/filters/regexpfilter.cpp \
$$PWD/filters/rangefilter.cpp \
$$PWD/filters/expressionfilter.cpp \
$$PWD/filters/filtercontainerfilter.cpp \
$$PWD/filters/anyoffilter.cpp \
$$PWD/filters/alloffilter.cpp \
$$PWD/filters/filtersqmltypes.cpp \
$$PWD/sorters/sorter.cpp \
$$PWD/sorters/sortercontainer.cpp \
$$PWD/sorters/rolesorter.cpp \
$$PWD/sorters/stringsorter.cpp \
$$PWD/sorters/expressionsorter.cpp \
$$PWD/sorters/sortersqmltypes.cpp \
$$PWD/proxyroles/proxyrole.cpp \
$$PWD/proxyroles/proxyrolecontainer.cpp \
$$PWD/proxyroles/joinrole.cpp \
$$PWD/proxyroles/switchrole.cpp \
$$PWD/proxyroles/expressionrole.cpp \
$$PWD/proxyroles/proxyrolesqmltypes.cpp \
$$PWD/proxyroles/singlerole.cpp \
$$PWD/proxyroles/regexprole.cpp \
$$PWD/sorters/filtersorter.cpp \
$$PWD/proxyroles/filterrole.cpp

View file

@ -1,28 +0,0 @@
#include "alloffilter.h"
namespace qqsfpm {
/*!
\qmltype AllOf
\inherits Filter
\inqmlmodule SortFilterProxyModel
\ingroup Filters
\ingroup FilterContainer
\brief Filter container accepting rows accepted by all its child filters.
The AllOf type is a \l Filter container that accepts rows if all of its contained (and enabled) filters accept them, or if it has no filter.
Using it as a top level filter has the same effect as putting all its child filters as top level filters. It can however be usefull to use an AllOf filter when nested in an AnyOf filter.
\sa FilterContainer
*/
bool AllOfFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const
{
//return true if all filters return false, or if there is no filter.
return std::all_of(m_filters.begin(), m_filters.end(),
[&sourceIndex, &proxyModel] (Filter* filter) {
return filter->filterAcceptsRow(sourceIndex, proxyModel);
}
);
}
}

View file

@ -1,20 +0,0 @@
#ifndef ALLOFFILTER_H
#define ALLOFFILTER_H
#include "filtercontainerfilter.h"
namespace qqsfpm {
class AllOfFilter : public FilterContainerFilter {
Q_OBJECT
public:
using FilterContainerFilter::FilterContainerFilter;
protected:
bool filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const override;
};
}
#endif // ALLOFFILTER_H

View file

@ -1,49 +0,0 @@
#include "anyoffilter.h"
namespace qqsfpm {
/*!
\qmltype AnyOf
\inherits Filter
\inqmlmodule SortFilterProxyModel
\ingroup Filters
\ingroup FilterContainer
\brief Filter container accepting rows accepted by at least one of its child filters.
The AnyOf type is a \l Filter container that accepts rows if any of its contained (and enabled) filters accept them.
In the following example, only the rows where the \c firstName role or the \c lastName role match the text entered in the \c nameTextField will be accepted :
\code
TextField {
id: nameTextField
}
SortFilterProxyModel {
sourceModel: contactModel
filters: AnyOf {
RegExpFilter {
roleName: "lastName"
pattern: nameTextField.text
caseSensitivity: Qt.CaseInsensitive
}
RegExpFilter {
roleName: "firstName"
pattern: nameTextField.text
caseSensitivity: Qt.CaseInsensitive
}
}
}
\endcode
\sa FilterContainer
*/
bool AnyOfFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const
{
//return true if any of the enabled filters return true
return std::any_of(m_filters.begin(), m_filters.end(),
[&sourceIndex, &proxyModel] (Filter* filter) {
return filter->enabled() && filter->filterAcceptsRow(sourceIndex, proxyModel);
}
);
}
}

View file

@ -1,20 +0,0 @@
#ifndef ANYOFFILTER_H
#define ANYOFFILTER_H
#include "filtercontainerfilter.h"
namespace qqsfpm {
class AnyOfFilter : public FilterContainerFilter {
Q_OBJECT
public:
using FilterContainerFilter::FilterContainerFilter;
protected:
bool filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const override;
};
}
#endif // ANYOFFILTER_H

View file

@ -1,125 +0,0 @@
#include "expressionfilter.h"
#include "qqmlsortfilterproxymodel.h"
#include <QtQml>
namespace qqsfpm {
/*!
\qmltype ExpressionFilter
\inherits Filter
\inqmlmodule SortFilterProxyModel
\ingroup Filters
\brief Filters row with a custom filtering.
An ExpressionFilter is a \l Filter allowing to implement custom filtering based on a javascript expression.
*/
/*!
\qmlproperty expression ExpressionFilter::expression
An expression to implement custom filtering, it must evaluate to a boolean.
It has the same syntax has a \l {http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html} {Property Binding} except it will be evaluated for each of the source model's rows.
Rows that have their expression evaluating to \c true will be accepted by the model.
Data for each row is exposed like for a delegate of a QML View.
This expression is reevaluated for a row every time its model data changes.
When an external property (not \c index or in \c model) the expression depends on changes, the expression is reevaluated for every row of the source model.
To capture the properties the expression depends on, the expression is first executed with invalid data and each property access is detected by the QML engine.
This means that if a property is not accessed because of a conditional, it won't be captured and the expression won't be reevaluted when this property changes.
A workaround to this problem is to access all the properties the expressions depends unconditionally at the beggining of the expression.
*/
const QQmlScriptString& ExpressionFilter::expression() const
{
return m_scriptString;
}
void ExpressionFilter::setExpression(const QQmlScriptString& scriptString)
{
if (m_scriptString == scriptString)
return;
m_scriptString = scriptString;
updateExpression();
Q_EMIT expressionChanged();
invalidate();
}
void ExpressionFilter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
updateContext(proxyModel);
}
bool ExpressionFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const
{
if (!m_scriptString.isEmpty()) {
QVariantMap modelMap;
QHash<int, QByteArray> roles = proxyModel.roleNames();
QQmlContext context(qmlContext(this));
auto addToContext = [&] (const QString &name, const QVariant& value) {
context.setContextProperty(name, value);
modelMap.insert(name, value);
};
for (auto it = roles.cbegin(); it != roles.cend(); ++it)
addToContext(it.value(), proxyModel.sourceData(sourceIndex, it.key()));
addToContext("index", sourceIndex.row());
context.setContextProperty("model", modelMap);
QQmlExpression expression(m_scriptString, &context);
QVariant variantResult = expression.evaluate();
if (expression.hasError()) {
qWarning() << expression.error();
return true;
}
if (variantResult.canConvert<bool>()) {
return variantResult.toBool();
} else {
qWarning("%s:%i:%i : Can't convert result to bool",
expression.sourceFile().toUtf8().data(),
expression.lineNumber(),
expression.columnNumber());
return true;
}
}
return true;
}
void ExpressionFilter::updateContext(const QQmlSortFilterProxyModel& proxyModel)
{
delete m_context;
m_context = new QQmlContext(qmlContext(this), this);
// what about roles changes ?
QVariantMap modelMap;
auto addToContext = [&] (const QString &name, const QVariant& value) {
m_context->setContextProperty(name, value);
modelMap.insert(name, value);
};
for (const QByteArray& roleName : proxyModel.roleNames().values())
addToContext(roleName, QVariant());
addToContext("index", -1);
m_context->setContextProperty("model", modelMap);
updateExpression();
}
void ExpressionFilter::updateExpression()
{
if (!m_context)
return;
delete m_expression;
m_expression = new QQmlExpression(m_scriptString, m_context, 0, this);
connect(m_expression, &QQmlExpression::valueChanged, this, &ExpressionFilter::invalidate);
m_expression->setNotifyOnValueChanged(true);
m_expression->evaluate();
}
}

View file

@ -1,41 +0,0 @@
#ifndef EXPRESSIONFILTER_H
#define EXPRESSIONFILTER_H
#include "filter.h"
#include <QQmlScriptString>
class QQmlExpression;
namespace qqsfpm {
class ExpressionFilter : public Filter
{
Q_OBJECT
Q_PROPERTY(QQmlScriptString expression READ expression WRITE setExpression NOTIFY expressionChanged)
public:
using Filter::Filter;
const QQmlScriptString& expression() const;
void setExpression(const QQmlScriptString& scriptString);
void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override;
protected:
bool filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const override;
Q_SIGNALS:
void expressionChanged();
private:
void updateContext(const QQmlSortFilterProxyModel& proxyModel);
void updateExpression();
QQmlScriptString m_scriptString;
QQmlExpression* m_expression = nullptr;
QQmlContext* m_context = nullptr;
};
}
#endif // EXPRESSIONFILTER_H

View file

@ -1,85 +0,0 @@
#include "filter.h"
#include "qqmlsortfilterproxymodel.h"
namespace qqsfpm {
/*!
\qmltype Filter
\qmlabstract
\inqmlmodule SortFilterProxyModel
\ingroup Filters
\brief Base type for the \l SortFilterProxyModel filters.
The Filter type cannot be used directly in a QML file.
It exists to provide a set of common properties and methods,
available across all the other filter types that inherit from it.
Attempting to use the Filter type directly will result in an error.
*/
Filter::Filter(QObject *parent) : QObject(parent)
{
}
/*!
\qmlproperty bool Filter::enabled
This property holds whether the filter is enabled.
A disabled filter will accept every rows unconditionally (even if it's inverted).
By default, filters are enabled.
*/
bool Filter::enabled() const
{
return m_enabled;
}
void Filter::setEnabled(bool enabled)
{
if (m_enabled == enabled)
return;
m_enabled = enabled;
Q_EMIT enabledChanged();
Q_EMIT invalidated();
}
/*!
\qmlproperty bool Filter::inverted
This property holds whether the filter is inverted.
When a filter is inverted, a row normally accepted would be rejected, and vice-versa.
By default, filters are not inverted.
*/
bool Filter::inverted() const
{
return m_inverted;
}
void Filter::setInverted(bool inverted)
{
if (m_inverted == inverted)
return;
m_inverted = inverted;
Q_EMIT invertedChanged();
invalidate();
}
bool Filter::filterAcceptsRow(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const
{
return !m_enabled || filterRow(sourceIndex, proxyModel) ^ m_inverted;
}
void Filter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
Q_UNUSED(proxyModel)
}
void Filter::invalidate()
{
if (m_enabled)
Q_EMIT invalidated();
}
}

View file

@ -1,46 +0,0 @@
#ifndef FILTER_H
#define FILTER_H
#include <QObject>
namespace qqsfpm {
class QQmlSortFilterProxyModel;
class Filter : public QObject
{
Q_OBJECT
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(bool inverted READ inverted WRITE setInverted NOTIFY invertedChanged)
public:
explicit Filter(QObject *parent = nullptr);
virtual ~Filter() = default;
bool enabled() const;
void setEnabled(bool enabled);
bool inverted() const;
void setInverted(bool inverted);
bool filterAcceptsRow(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const;
virtual void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel);
Q_SIGNALS:
void enabledChanged();
void invertedChanged();
void invalidated();
protected:
virtual bool filterRow(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const = 0;
void invalidate();
private:
bool m_enabled = true;
bool m_inverted = false;
};
}
#endif // FILTER_H

View file

@ -1,126 +0,0 @@
#include "filtercontainer.h"
#include "filter.h"
#include <QtQml>
namespace qqsfpm {
/*!
\qmltype FilterContainer
\qmlabstract
\inqmlmodule SortFilterProxyModel
\ingroup FilterAttached
\brief Abstract interface for types containing \l {Filter}{Filters}.
\section2 Types implementing this interface:
\annotatedlist FilterContainer
*/
QList<Filter*> FilterContainer::filters() const
{
return m_filters;
}
void FilterContainer::appendFilter(Filter* filter)
{
m_filters.append(filter);
onFilterAppended(filter);
}
void FilterContainer::removeFilter(Filter* filter)
{
m_filters.removeOne(filter);
onFilterRemoved(filter);
}
void FilterContainer::clearFilters()
{
m_filters.clear();
onFiltersCleared();
}
QQmlListProperty<Filter> FilterContainer::filtersListProperty()
{
return QQmlListProperty<Filter>(reinterpret_cast<QObject*>(this), &m_filters,
&FilterContainer::append_filter,
&FilterContainer::count_filter,
&FilterContainer::at_filter,
&FilterContainer::clear_filters);
}
void FilterContainer::append_filter(QQmlListProperty<Filter>* list, Filter* filter)
{
if (!filter)
return;
FilterContainer* that = reinterpret_cast<FilterContainer*>(list->object);
that->appendFilter(filter);
}
int FilterContainer::count_filter(QQmlListProperty<Filter>* list)
{
QList<Filter*>* filters = static_cast<QList<Filter*>*>(list->data);
return filters->count();
}
Filter* FilterContainer::at_filter(QQmlListProperty<Filter>* list, int index)
{
QList<Filter*>* filters = static_cast<QList<Filter*>*>(list->data);
return filters->at(index);
}
void FilterContainer::clear_filters(QQmlListProperty<Filter> *list)
{
FilterContainer* that = reinterpret_cast<FilterContainer*>(list->object);
that->clearFilters();
}
FilterContainerAttached::FilterContainerAttached(QObject* object) : QObject(object),
m_filter(qobject_cast<Filter*>(object))
{
if (!m_filter)
qmlWarning(object) << "FilterContainer must be attached to a Filter";
}
FilterContainerAttached::~FilterContainerAttached()
{
if (m_filter && m_container) {
FilterContainer* container = qobject_cast<FilterContainer*>(m_container.data());
container->removeFilter(m_filter);
}
}
/*!
\qmlattachedproperty bool FilterContainer::container
This attached property allows you to include in a \l FilterContainer a \l Filter that
has been instantiated outside of the \l FilterContainer, for example in an Instantiator.
*/
QObject* FilterContainerAttached::container() const
{
return m_container;
}
void FilterContainerAttached::setContainer(QObject* object)
{
if (m_container == object)
return;
FilterContainer* container = qobject_cast<FilterContainer*>(object);
if (object && !container)
qmlWarning(parent()) << "container must inherits from FilterContainer, " << object->metaObject()->className() << " provided";
if (m_container && m_filter)
qobject_cast<FilterContainer*>(m_container.data())->removeFilter(m_filter);
m_container = container ? object : nullptr;
if (container && m_filter)
container->appendFilter(m_filter);
Q_EMIT containerChanged();
}
FilterContainerAttached* FilterContainerAttached::qmlAttachedProperties(QObject* object)
{
return new FilterContainerAttached(object);
}
}

View file

@ -1,68 +0,0 @@
#ifndef FILTERCONTAINER_H
#define FILTERCONTAINER_H
#include <QList>
#include <QQmlListProperty>
#include <qqml.h>
#include <QPointer>
namespace qqsfpm {
class Filter;
class QQmlSortFilterProxyModel;
class FilterContainer {
public:
virtual ~FilterContainer() = default;
QList<Filter*> filters() const;
void appendFilter(Filter* filter);
void removeFilter(Filter* filter);
void clearFilters();
QQmlListProperty<Filter> filtersListProperty();
protected:
QList<Filter*> m_filters;
private:
virtual void onFilterAppended(Filter* filter) = 0;
virtual void onFilterRemoved(Filter* filter) = 0;
virtual void onFiltersCleared() = 0;
static void append_filter(QQmlListProperty<Filter>* list, Filter* filter);
static int count_filter(QQmlListProperty<Filter>* list);
static Filter* at_filter(QQmlListProperty<Filter>* list, int index);
static void clear_filters(QQmlListProperty<Filter>* list);
};
class FilterContainerAttached : public QObject
{
Q_OBJECT
Q_PROPERTY(QObject* container READ container WRITE setContainer NOTIFY containerChanged)
public:
FilterContainerAttached(QObject* object);
~FilterContainerAttached();
QObject* container() const;
void setContainer(QObject* object);
static FilterContainerAttached* qmlAttachedProperties(QObject* object);
Q_SIGNALS:
void containerChanged();
private:
QPointer<QObject> m_container = nullptr;
Filter* m_filter = nullptr;
};
}
#define FilterContainer_iid "fr.grecko.SortFilterProxyModel.FilterContainer"
Q_DECLARE_INTERFACE(qqsfpm::FilterContainer, FilterContainer_iid)
QML_DECLARE_TYPEINFO(qqsfpm::FilterContainerAttached, QML_HAS_ATTACHED_PROPERTIES)
#endif // FILTERCONTAINER_H

View file

@ -1,28 +0,0 @@
#include "filtercontainerfilter.h"
namespace qqsfpm {
void FilterContainerFilter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
for (Filter* filter : m_filters)
filter->proxyModelCompleted(proxyModel);
}
void FilterContainerFilter::onFilterAppended(Filter* filter)
{
connect(filter, &Filter::invalidated, this, &FilterContainerFilter::invalidate);
invalidate();
}
void FilterContainerFilter::onFilterRemoved(Filter* filter)
{
Q_UNUSED(filter)
invalidate();
}
void qqsfpm::FilterContainerFilter::onFiltersCleared()
{
invalidate();
}
}

View file

@ -1,31 +0,0 @@
#ifndef FILTERCONTAINERFILTER_H
#define FILTERCONTAINERFILTER_H
#include "filter.h"
#include "filtercontainer.h"
namespace qqsfpm {
class FilterContainerFilter : public Filter, public FilterContainer {
Q_OBJECT
Q_INTERFACES(qqsfpm::FilterContainer)
Q_PROPERTY(QQmlListProperty<qqsfpm::Filter> filters READ filtersListProperty NOTIFY filtersChanged)
Q_CLASSINFO("DefaultProperty", "filters")
public:
using Filter::Filter;
void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override;
Q_SIGNALS:
void filtersChanged();
private:
void onFilterAppended(Filter* filter) override;
void onFilterRemoved(Filter* filter) override;
void onFiltersCleared() override;
};
}
#endif // FILTERCONTAINERFILTER_H

View file

@ -1,28 +0,0 @@
#include "filter.h"
#include "valuefilter.h"
#include "indexfilter.h"
#include "regexpfilter.h"
#include "rangefilter.h"
#include "expressionfilter.h"
#include "anyoffilter.h"
#include "alloffilter.h"
#include <QQmlEngine>
#include <QCoreApplication>
namespace qqsfpm {
void registerFiltersTypes() {
qmlRegisterUncreatableType<Filter>("SortFilterProxyModel", 0, 2, "Filter", "Filter is an abstract class");
qmlRegisterType<ValueFilter>("SortFilterProxyModel", 0, 2, "ValueFilter");
qmlRegisterType<IndexFilter>("SortFilterProxyModel", 0, 2, "IndexFilter");
qmlRegisterType<RegExpFilter>("SortFilterProxyModel", 0, 2, "RegExpFilter");
qmlRegisterType<RangeFilter>("SortFilterProxyModel", 0, 2, "RangeFilter");
qmlRegisterType<ExpressionFilter>("SortFilterProxyModel", 0, 2, "ExpressionFilter");
qmlRegisterType<AnyOfFilter>("SortFilterProxyModel", 0, 2, "AnyOf");
qmlRegisterType<AllOfFilter>("SortFilterProxyModel", 0, 2, "AllOf");
qmlRegisterUncreatableType<FilterContainerAttached>("SortFilterProxyModel", 0, 2, "FilterContainer", "FilterContainer can only be used as an attaching type");
}
Q_COREAPP_STARTUP_FUNCTION(registerFiltersTypes)
}

View file

@ -1,112 +0,0 @@
#include "indexfilter.h"
#include "qqmlsortfilterproxymodel.h"
namespace qqsfpm {
/*!
\qmltype IndexFilter
\inherits Filter
\inqmlmodule SortFilterProxyModel
\ingroup Filters
\brief Filters rows based on their source index.
An IndexFilter is a filter allowing contents to be filtered based on their source model index.
In the following example, only the first row of the source model will be accepted:
\code
SortFilterProxyModel {
sourceModel: contactModel
filters: IndexFilter {
maximumIndex: 0
}
}
\endcode
*/
/*!
\qmlproperty int IndexFilter::minimumIndex
This property holds the minimumIndex of the filter.
Rows with a source index lower than \c minimumIndex will be rejected.
If \c minimumIndex is negative, it is counted from the end of the source model, meaning that :
\code
minimumIndex: -1
\endcode
is equivalent to :
\code
minimumIndex: sourceModel.count - 1
\endcode
By default, no value is set.
*/
const QVariant& IndexFilter::minimumIndex() const
{
return m_minimumIndex;
}
void IndexFilter::setMinimumIndex(const QVariant& minimumIndex)
{
if (m_minimumIndex == minimumIndex)
return;
m_minimumIndex = minimumIndex;
Q_EMIT minimumIndexChanged();
invalidate();
}
/*!
\qmlproperty int IndexFilter::maximumIndex
This property holds the maximumIndex of the filter.
Rows with a source index higher than \c maximumIndex will be rejected.
If \c maximumIndex is negative, it is counted from the end of the source model, meaning that:
\code
maximumIndex: -1
\endcode
is equivalent to :
\code
maximumIndex: sourceModel.count - 1
\endcode
By default, no value is set.
*/
const QVariant& IndexFilter::maximumIndex() const
{
return m_maximumIndex;
}
void IndexFilter::setMaximumIndex(const QVariant& maximumIndex)
{
if (m_maximumIndex == maximumIndex)
return;
m_maximumIndex = maximumIndex;
Q_EMIT maximumIndexChanged();
invalidate();
}
bool IndexFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const
{
int sourceRowCount = proxyModel.sourceModel()->rowCount();
int sourceRow = sourceIndex.row();
bool minimumIsValid;
int minimum = m_minimumIndex.toInt(&minimumIsValid);
if (minimumIsValid) {
int actualMinimum = minimum < 0 ? sourceRowCount + minimum : minimum;
if (sourceRow < actualMinimum)
return false;
}
bool maximumIsValid;
int maximum = m_maximumIndex.toInt(&maximumIsValid);
if (maximumIsValid) {
int actualMaximum = maximum < 0 ? sourceRowCount + maximum : maximum;
if (sourceRow > actualMaximum)
return false;
}
return true;
}
}

View file

@ -1,37 +0,0 @@
#ifndef INDEXFILTER_H
#define INDEXFILTER_H
#include "filter.h"
#include <QVariant>
namespace qqsfpm {
class IndexFilter: public Filter {
Q_OBJECT
Q_PROPERTY(QVariant minimumIndex READ minimumIndex WRITE setMinimumIndex NOTIFY minimumIndexChanged)
Q_PROPERTY(QVariant maximumIndex READ maximumIndex WRITE setMaximumIndex NOTIFY maximumIndexChanged)
public:
using Filter::Filter;
const QVariant& minimumIndex() const;
void setMinimumIndex(const QVariant& minimumIndex);
const QVariant& maximumIndex() const;
void setMaximumIndex(const QVariant& maximumIndex);
protected:
bool filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const override;
Q_SIGNALS:
void minimumIndexChanged();
void maximumIndexChanged();
private:
QVariant m_minimumIndex;
QVariant m_maximumIndex;
};
}
#endif // INDEXFILTER_H

View file

@ -1,139 +0,0 @@
#include "rangefilter.h"
namespace qqsfpm {
/*!
\qmltype RangeFilter
\inherits RoleFilter
\inqmlmodule SortFilterProxyModel
\ingroup Filters
\brief Filters rows between boundary values.
A RangeFilter is a \l RoleFilter that accepts rows if their data is between the filter's minimum and maximum value.
In the following example, only rows with their \c price role set to a value between the tow boundary of the slider will be accepted :
\code
RangeSlider {
id: priceRangeSlider
}
SortFilterProxyModel {
sourceModel: priceModel
filters: RangeFilter {
roleName: "price"
minimumValue: priceRangeSlider.first.value
maximumValue: priceRangeSlider.second.value
}
}
\endcode
*/
/*!
\qmlproperty int RangeFilter::minimumValue
This property holds the minimumValue of the filter.
Rows with a value lower than \c minimumValue will be rejected.
By default, no value is set.
\sa minimumInclusive
*/
QVariant RangeFilter::minimumValue() const
{
return m_minimumValue;
}
void RangeFilter::setMinimumValue(QVariant minimumValue)
{
if (m_minimumValue == minimumValue)
return;
m_minimumValue = minimumValue;
Q_EMIT minimumValueChanged();
invalidate();
}
/*!
\qmlproperty int RangeFilter::minimumInclusive
This property holds whether the \l minimumValue is inclusive.
By default, the \l minimumValue is inclusive.
\sa minimumValue
*/
bool RangeFilter::minimumInclusive() const
{
return m_minimumInclusive;
}
void RangeFilter::setMinimumInclusive(bool minimumInclusive)
{
if (m_minimumInclusive == minimumInclusive)
return;
m_minimumInclusive = minimumInclusive;
Q_EMIT minimumInclusiveChanged();
invalidate();
}
/*!
\qmlproperty int RangeFilter::maximumValue
This property holds the maximumValue of the filter.
Rows with a value higher than \c maximumValue will be rejected.
By default, no value is set.
\sa maximumInclusive
*/
QVariant RangeFilter::maximumValue() const
{
return m_maximumValue;
}
void RangeFilter::setMaximumValue(QVariant maximumValue)
{
if (m_maximumValue == maximumValue)
return;
m_maximumValue = maximumValue;
Q_EMIT maximumValueChanged();
invalidate();
}
/*!
\qmlproperty int RangeFilter::maximumInclusive
This property holds whether the \l minimumValue is inclusive.
By default, the \l minimumValue is inclusive.
\sa minimumValue
*/
bool RangeFilter::maximumInclusive() const
{
return m_maximumInclusive;
}
void RangeFilter::setMaximumInclusive(bool maximumInclusive)
{
if (m_maximumInclusive == maximumInclusive)
return;
m_maximumInclusive = maximumInclusive;
Q_EMIT maximumInclusiveChanged();
invalidate();
}
bool RangeFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const
{
QVariant value = sourceData(sourceIndex, proxyModel);
bool lessThanMin = m_minimumValue.isValid() &&
(m_minimumInclusive ? value < m_minimumValue : value <= m_minimumValue);
bool moreThanMax = m_maximumValue.isValid() &&
(m_maximumInclusive ? value > m_maximumValue : value >= m_maximumValue);
return !(lessThanMin || moreThanMax);
}
}

View file

@ -1,48 +0,0 @@
#ifndef RANGEFILTER_H
#define RANGEFILTER_H
#include "rolefilter.h"
#include <QVariant>
namespace qqsfpm {
class RangeFilter : public RoleFilter
{
Q_OBJECT
Q_PROPERTY(QVariant minimumValue READ minimumValue WRITE setMinimumValue NOTIFY minimumValueChanged)
Q_PROPERTY(bool minimumInclusive READ minimumInclusive WRITE setMinimumInclusive NOTIFY minimumInclusiveChanged)
Q_PROPERTY(QVariant maximumValue READ maximumValue WRITE setMaximumValue NOTIFY maximumValueChanged)
Q_PROPERTY(bool maximumInclusive READ maximumInclusive WRITE setMaximumInclusive NOTIFY maximumInclusiveChanged)
public:
using RoleFilter::RoleFilter;
QVariant minimumValue() const;
void setMinimumValue(QVariant minimumValue);
bool minimumInclusive() const;
void setMinimumInclusive(bool minimumInclusive);
QVariant maximumValue() const;
void setMaximumValue(QVariant maximumValue);
bool maximumInclusive() const;
void setMaximumInclusive(bool maximumInclusive);
protected:
bool filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const override;
Q_SIGNALS:
void minimumValueChanged();
void minimumInclusiveChanged();
void maximumValueChanged();
void maximumInclusiveChanged();
private:
QVariant m_minimumValue;
bool m_minimumInclusive = true;
QVariant m_maximumValue;
bool m_maximumInclusive = true;
};
}
#endif // RANGEFILTER_H

View file

@ -1,113 +0,0 @@
#include "regexpfilter.h"
#include <QVariant>
namespace qqsfpm {
/*!
\qmltype RegExpFilter
\inherits RoleFilter
\inqmlmodule SortFilterProxyModel
\ingroup Filters
\brief Filters rows matching a regular expression.
A RegExpFilter is a \l RoleFilter that accepts rows matching a regular rexpression.
In the following example, only rows with their \c lastName role beggining with the content of textfield the will be accepted:
\code
TextField {
id: nameTextField
}
SortFilterProxyModel {
sourceModel: contactModel
filters: RegExpFilter {
roleName: "lastName"
pattern: "^" + nameTextField.displayText
}
}
\endcode
*/
/*!
\qmlproperty bool RegExpFilter::pattern
The pattern used to filter the contents of the source model.
\sa syntax
*/
QString RegExpFilter::pattern() const
{
return m_pattern;
}
void RegExpFilter::setPattern(const QString& pattern)
{
if (m_pattern == pattern)
return;
m_pattern = pattern;
m_regExp.setPattern(pattern);
Q_EMIT patternChanged();
invalidate();
}
/*!
\qmlproperty enum RegExpFilter::syntax
The pattern used to filter the contents of the source model.
Only the source model's value having their \l RoleFilter::roleName data matching this \l pattern with the specified \l syntax will be kept.
\value RegExpFilter.RegExp A rich Perl-like pattern matching syntax. This is the default.
\value RegExpFilter.Wildcard This provides a simple pattern matching syntax similar to that used by shells (command interpreters) for "file globbing".
\value RegExpFilter.FixedString The pattern is a fixed string. This is equivalent to using the RegExp pattern on a string in which all metacharacters are escaped.
\value RegExpFilter.RegExp2 Like RegExp, but with greedy quantifiers.
\value RegExpFilter.WildcardUnix This is similar to Wildcard but with the behavior of a Unix shell. The wildcard characters can be escaped with the character "\".
\value RegExpFilter.W3CXmlSchema11 The pattern is a regular expression as defined by the W3C XML Schema 1.1 specification.
\sa pattern
*/
RegExpFilter::PatternSyntax RegExpFilter::syntax() const
{
return m_syntax;
}
void RegExpFilter::setSyntax(RegExpFilter::PatternSyntax syntax)
{
if (m_syntax == syntax)
return;
m_syntax = syntax;
m_regExp.setPatternSyntax(static_cast<QRegExp::PatternSyntax>(syntax));
Q_EMIT syntaxChanged();
invalidate();
}
/*!
\qmlproperty Qt::CaseSensitivity RegExpFilter::caseSensitivity
This property holds the caseSensitivity of the filter.
*/
Qt::CaseSensitivity RegExpFilter::caseSensitivity() const
{
return m_caseSensitivity;
}
void RegExpFilter::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity)
{
if (m_caseSensitivity == caseSensitivity)
return;
m_caseSensitivity = caseSensitivity;
m_regExp.setCaseSensitivity(caseSensitivity);
Q_EMIT caseSensitivityChanged();
invalidate();
}
bool RegExpFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const
{
QString string = sourceData(sourceIndex, proxyModel).toString();
return m_regExp.indexIn(string) != -1;
}
}

View file

@ -1,52 +0,0 @@
#ifndef REGEXPFILTER_H
#define REGEXPFILTER_H
#include "rolefilter.h"
namespace qqsfpm {
class RegExpFilter : public RoleFilter {
Q_OBJECT
Q_PROPERTY(QString pattern READ pattern WRITE setPattern NOTIFY patternChanged)
Q_PROPERTY(PatternSyntax syntax READ syntax WRITE setSyntax NOTIFY syntaxChanged)
Q_PROPERTY(Qt::CaseSensitivity caseSensitivity READ caseSensitivity WRITE setCaseSensitivity NOTIFY caseSensitivityChanged)
public:
enum PatternSyntax {
RegExp = QRegExp::RegExp,
Wildcard = QRegExp::Wildcard,
FixedString = QRegExp::FixedString,
RegExp2 = QRegExp::RegExp2,
WildcardUnix = QRegExp::WildcardUnix,
W3CXmlSchema11 = QRegExp::W3CXmlSchema11 };
Q_ENUMS(PatternSyntax)
using RoleFilter::RoleFilter;
QString pattern() const;
void setPattern(const QString& pattern);
PatternSyntax syntax() const;
void setSyntax(PatternSyntax syntax);
Qt::CaseSensitivity caseSensitivity() const;
void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity);
protected:
bool filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const override;
Q_SIGNALS:
void patternChanged();
void syntaxChanged();
void caseSensitivityChanged();
private:
QRegExp m_regExp;
Qt::CaseSensitivity m_caseSensitivity = m_regExp.caseSensitivity();
PatternSyntax m_syntax = static_cast<PatternSyntax>(m_regExp.patternSyntax());
QString m_pattern = m_regExp.pattern();
};
}
#endif // REGEXPFILTER_H

View file

@ -1,45 +0,0 @@
#include "rolefilter.h"
#include "qqmlsortfilterproxymodel.h"
namespace qqsfpm {
/*!
\qmltype RoleFilter
\qmlabstract
\inherits Filter
\inqmlmodule SortFilterProxyModel
\ingroup Filters
\brief Base type for filters based on a source model role.
The RoleFilter type cannot be used directly in a QML file.
It exists to provide a set of common properties and methods,
available across all the other filter types that inherit from it.
Attempting to use the RoleFilter type directly will result in an error.
*/
/*!
\qmlproperty string RoleFilter::roleName
This property holds the role name that the filter is using to query the source model's data when filtering items.
*/
const QString& RoleFilter::roleName() const
{
return m_roleName;
}
void RoleFilter::setRoleName(const QString& roleName)
{
if (m_roleName == roleName)
return;
m_roleName = roleName;
Q_EMIT roleNameChanged();
invalidate();
}
QVariant RoleFilter::sourceData(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const
{
return proxyModel.sourceData(sourceIndex, m_roleName);
}
}

View file

@ -1,31 +0,0 @@
#ifndef ROLEFILTER_H
#define ROLEFILTER_H
#include "filter.h"
namespace qqsfpm {
class RoleFilter : public Filter
{
Q_OBJECT
Q_PROPERTY(QString roleName READ roleName WRITE setRoleName NOTIFY roleNameChanged)
public:
using Filter::Filter;
const QString& roleName() const;
void setRoleName(const QString& roleName);
Q_SIGNALS:
void roleNameChanged();
protected:
QVariant sourceData(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const;
private:
QString m_roleName;
};
}
#endif // ROLEFILTER_H

View file

@ -1,57 +0,0 @@
#include "valuefilter.h"
namespace qqsfpm {
/*!
\qmltype ValueFilter
\inherits RoleFilter
\inqmlmodule SortFilterProxyModel
\ingroup Filters
\brief Filters rows matching exactly a value.
A ValueFilter is a simple \l RoleFilter that accepts rows matching exactly the filter's value
In the following example, only rows with their \c favorite role set to \c true will be accepted when the checkbox is checked :
\code
CheckBox {
id: showOnlyFavoriteCheckBox
}
SortFilterProxyModel {
sourceModel: contactModel
filters: ValueFilter {
roleName: "favorite"
value: true
enabled: showOnlyFavoriteCheckBox.checked
}
}
\endcode
*/
/*!
\qmlproperty variant ValueFilter::value
This property holds the value used to filter the contents of the source model.
*/
const QVariant &ValueFilter::value() const
{
return m_value;
}
void ValueFilter::setValue(const QVariant& value)
{
if (m_value == value)
return;
m_value = value;
Q_EMIT valueChanged();
invalidate();
}
bool ValueFilter::filterRow(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const
{
return !m_value.isValid() || m_value == sourceData(sourceIndex, proxyModel);
}
}

View file

@ -1,31 +0,0 @@
#ifndef VALUEFILTER_H
#define VALUEFILTER_H
#include "rolefilter.h"
#include <QVariant>
namespace qqsfpm {
class ValueFilter : public RoleFilter {
Q_OBJECT
Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged)
public:
using RoleFilter::RoleFilter;
const QVariant& value() const;
void setValue(const QVariant& value);
protected:
bool filterRow(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const override;
Q_SIGNALS:
void valueChanged();
private:
QVariant m_value;
};
}
#endif // VALUEFILTER_H

View file

@ -1,27 +0,0 @@
/*!
\page index.html overview
\title SortFilterProxyModel QML Module
SortFilterProxyModel is an implementation of QSortFilterProxyModel conveniently exposed for QML.
\annotatedlist SortFilterProxyModel
\section1 Filters
\annotatedlist Filters
\section2 Related attached types
\annotatedlist FilterAttached
\section1 Sorters
\annotatedlist Sorters
\section2 Related attached types
\annotatedlist SorterAttached
\section1 ProxyRoles
\annotatedlist ProxyRoles
*/
/*!
\qmlmodule SortFilterProxyModel
*/

View file

@ -1,805 +0,0 @@
body {
font: normal 400 14px/1.2 Arial;
margin-top: 85px;
font-family: Arial, Helvetica;
text-align: left;
margin-left: 5px;
margin-right: 5px;
background-color: #fff;
}
p {
line-height: 20px
}
img {
margin-left: 0px;
max-width: 800px;
height: auto;
}
.content .border img {
box-shadow:3px 3px 8px 3px rgba(200,200,200,0.5)
}
.content .border .player {
box-shadow:3px 3px 8px 3px rgba(200,200,200,0.5)
}
.content .indexboxcont li {
font: normal bold 13px/1 Verdana
}
.content .normallist li {
font: normal 13px/1 Verdana
}
.descr {
margin-top: 35px;
margin-bottom: 45px;
margin-left: 5px;
text-align: left;
vertical-align: top;
}
.name {
max-width: 75%;
font-weight: 100;
}
tt {
text-align: left
}
/*
-----------
links
-----------
*/
a:link {
color: #007330;
text-decoration: none;
text-align: left;
}
a.qa-mark:target:before {
content: "***";
color: #ff0000;
}
a:hover {
color: #44a51c;
text-align: left;
}
a:visited {
color: #007330;
text-align: left;
}
a:visited:hover {
color: #44a51c;
text-align: left;
}
/*
-----------
offline viewing: HTML links display an icon
-----------
*/
a[href*="http://"], a[href*="ftp://"], a[href*="https://"] {
text-decoration: none;
background-image: url(../images/ico_out.png);
background-repeat: no-repeat;
background-position: left;
padding-left: 20px;
text-align: left;
}
.flags {
text-decoration: none;
text-height: 24px;
}
.flags:target {
background-color: #FFFFD6;
}
/*
-------------------------------
NOTE styles
-------------------------------
*/
.notetitle, .tiptitle, .fastpathtitle {
font-weight: bold
}
.attentiontitle, .cautiontitle, .dangertitle, .importanttitle, .remembertitle, .restrictiontitle {
font-weight: bold
}
.note, .tip, .fastpath {
background: #F2F2F2 url(../images/ico_note.png);
background-repeat: no-repeat;
background-position: top left;
padding: 5px;
padding-left: 40px;
padding-bottom: 10px;
border: #999 1px dotted;
color: #666666;
margin: 5px;
}
.attention, .caution, .danger, .important, .remember, .restriction {
background: #F2F2F2 url(../images/ico_note_attention.png);
background-repeat: no-repeat;
background-position: top left;
padding: 5px;
padding-left: 40px;
padding-bottom: 10px;
border: #999 1px dotted;
color: #666666;
margin: 5px;
}
/*
-------------------------------
Top navigation
-------------------------------
*/
.qtref {
display: block;
position: relative;
height: 15px;
z-index: 1;
font-size: 11px;
padding-right: 10px;
float: right;
}
.naviNextPrevious {
clear: both;
display: block;
position: relative;
text-align: right;
top: -47px;
float: right;
height: 20px;
z-index: 1;
padding-right: 10px;
padding-top: 2px;
vertical-align: top;
margin: 0px;
}
.naviNextPrevious > a:first-child {
background-image: url(../images/btn_prev.png);
background-repeat: no-repeat;
background-position: left;
padding-left: 20px;
height: 20px;
padding-left: 20px;
}
.naviNextPrevious > a:last-child {
background-image: url(../images/btn_next.png);
background-repeat: no-repeat;
background-position: right;
padding-right: 20px;
height: 20px;
margin-left: 30px;
}
.naviSeparator { display: none }
/*
-----------
footer and license
-----------
*/
.footer {
text-align: left;
padding-top: 45px;
padding-left: 5px;
margin-top: 45px;
margin-bottom: 45px;
font-size: 10px;
border-top: 1px solid #999;
}
.footer p {
line-height: 14px;
font-size: 11px;
padding: 0;
margin: 0;
}
.footer a[href*="http://"], a[href*="ftp://"], a[href*="https://"] {
font-weight: bold;
}
.footerNavi {
width: auto;
text-align: right;
margin-top: 50px;
z-index: 1;
}
.navigationbar {
display: block;
position: relative;
top: -20px;
border-top: 1px solid #cecece;
border-bottom: 1px solid #cecece;
background-color: #F2F2F2;
z-index: 1;
height: 20px;
padding-left: 7px;
margin: 0px;
padding-top: 2px;
margin-left: -5px;
margin-right: -5px;
}
.navigationbar .first {
background: url(../images/home.png);
background-position: left;
background-repeat: no-repeat;
padding-left: 20px;
}
.navigationbar ul {
margin: 0px;
padding: 0px;
}
.navigationbar ul li {
list-style-type: none;
padding-top: 2px;
padding-left: 4px;
margin: 0;
height: 20px;
}
.navigationbar li {
float: left
}
.navigationbar li a, .navigationbar td a {
display: block;
text-decoration: none;
background: url(../images/arrow_bc.png);
background-repeat: no-repeat;
background-position: right;
padding-right: 17px;
}
table.buildversion {
float: right;
margin-top: -18px !important;
}
.navigationbar table {
border-radius: 0;
border: 0 none;
background-color: #F2F2F2;
margin: 0;
}
.navigationbar table td {
padding: 0;
border: 0 none;
}
#buildversion {
font-style: italic;
font-size: small;
float: right;
margin-right: 5px;
}
/*
/* table of content
no display
*/
/*
-----------
headers
-----------
*/
@media screen {
.title {
color: #313131;
font-size: 24px;
font-weight: normal;
left: 0;
padding-bottom: 20px;
padding-left: 10px;
padding-top: 20px;
position: absolute;
right: 0;
top: 0;
background-color: #E6E6E6;
border-bottom: 1px #CCC solid;
border-top: 2px #CCC solid;
font-weight: bold;
margin-left: 0px;
margin-right: 0px;
}
.subtitle, .small-subtitle {
display: block;
clear: left;
}
}
h1 {
margin: 0
}
h2, p.h2 {
font: 500 16px/1.2 Arial;
font-weight: 100;
background-color: #F2F3F4;
padding: 4px;
margin-bottom: 30px;
margin-top: 30px;
border-top: #E0E0DE 1px solid;
border-bottom: #E0E0DE 1px solid;
max-width: 99%;
}
h2:target {
background-color: #F2F3D4;
}
h3 {
font: 500 14px/1.2 Arial;
font-weight: 100;
text-decoration: underline;
margin-bottom: 30px;
margin-top: 30px;
}
h3.fn, span.fn {
border-width: 1px;
border-style: solid;
border-color: #E6E6E6;
-moz-border-radius: 7px 7px 7px 7px;
-webkit-border-radius: 7px 7px 7px 7px;
border-radius: 7px 7px 7px 7px;
background-color: #F6F6F6;
word-spacing: 3px;
padding: 5px 5px;
text-decoration: none;
font-weight: bold;
max-width: 75%;
font-size: 14px;
margin: 0px;
margin-top: 45px;
}
.fngroup h3.fngroupitem {
margin-bottom: 5px;
}
h3.fn code {
float: right;
}
h3.fn:target {
background-color: #F6F6D6;
}
.name {
color: #1A1A1A
}
.type {
color: #808080
}
@media print {
.title {
color: #0066CB;
font-family: Arial, Helvetica;
font-size: 32px;
font-weight: normal;
left: 0;
position: absolute;
right: 0;
top: 0;
}
}
/*
-----------------
table styles
-----------------
*/
.table img {
border: none;
margin-left: 0px;
-moz-box-shadow: 0px 0px 0px #fff;
-webkit-box-shadow: 0px 0px 0px #fff;
box-shadow: 0px 0px 0px #fff;
}
/* table with border alternative colours*/
table, pre, .LegaleseLeft {
-moz-border-radius: 7px 7px 7px 7px;
-webkit-border-radius: 7px 7px 7px 7px;
border-radius: 7px 7px 7px 7px;
background-color: #F6F6F6;
border: 1px solid #E6E6E6;
border-collapse: separate;
margin-bottom: 25px;
margin-left: 15px;
font-size: 12px;
line-height: 1.2;
}
table tr.even {
background-color: white;
color: #66666E;
}
table tr.odd {
background-color: #F6F6F6;
color: #66666E;
}
table tr:target {
background-color: #F6F6D6;
}
table thead {
text-align: left;
padding-left: 20px;
background-color: #e1e0e0;
border-left: none;
border-right: none;
}
table thead th {
padding-top: 5px;
padding-left: 10px;
padding-bottom: 5px;
border-bottom: 2px solid #D1D1D1;
padding-right: 10px;
}
table th {
text-align: left;
padding-left: 20px;
}
table td {
padding: 3px 15px 3px 20px;
border-bottom: #CCC dotted 1px;
}
table p {
margin: 0px
}
.LegaleseLeft {
font-family: monospace;
white-space: pre-wrap;
}
/* table bodless & white*/
.borderless {
border-radius: 0px 0px 0px 0px;
background-color: #fff;
border: 1px solid #fff;
}
.borderless tr {
background-color: #FFF;
color: #66666E;
}
.borderless td {
border: none;
border-bottom: #fff dotted 1px;
}
/*
-----------
List
-----------
*/
ul {
margin-top: 10px;
}
li {
margin-bottom: 10px;
padding-left: 8px;
list-style: outside;
text-align: left;
}
ul > li {
list-style-type: square;
}
ol {
margin: 10px;
padding: 0;
}
ol.A > li {
list-style-type: upper-alpha;
}
ol.a > li{
list-style-type: lower-alpha;
}
ol > li {
margin-left: 30px;
padding-left: 8px;
list-style: decimal;
}
.centerAlign {
text-align: left
}
.cpp, .LegaleseLeft {
display: block;
margin: 10px;
overflow: auto;
padding: 20px 20px 20px 20px;
}
.js {
display: block;
margin: 10px;
overflow: auto;
padding: 20px 20px 20px 20px;
}
.memItemLeft {
padding-right: 3px
}
.memItemRight {
padding: 3px 15px 3px 0
}
.qml {
display: block;
margin: 10px;
overflow: auto;
padding: 20px 20px 20px 20px;
}
.qmldefault {
padding-left: 5px;
float: right;
color: red;
}
.qmlreadonly {
padding-left: 5px;
float: right;
color: #254117;
}
.rightAlign {
padding: 3px 5px 3px 10px;
text-align: right;
}
.qmldoc {
margin-left: 15px
}
.flowList {
padding: 25px
}
.flowList dd {
display: inline-block;
margin-left: 10px;
width: 255px;
line-height: 1.15em;
overflow-x: hidden;
text-overflow: ellipsis
}
.alphaChar {
font-size: 2em;
position: relative
}
/*
-----------
Content table
-----------
*/
@media print {
.toc {
float: right;
clear: right;
padding-bottom: 10px;
padding-top: 50px;
width: 100%;
background-image: url(../images/bgrContent.png);
background-position: top;
background-repeat: no-repeat;
}
}
@media screen {
.toc {
float: right;
clear: right;
vertical-align: top;
-moz-border-radius: 7px 7px 7px 7px;
-webkit-border-radius: 7px 7px 7px 7px;
border-radius: 7px 7px 7px 7px;
background: #FFF url('../images/bgrContent.png');
background-position: top;
background-repeat: repeat-x;
border: 1px solid #E6E6E6;
padding-left: 5px;
padding-bottom: 10px;
height: auto;
width: 200px;
text-align: left;
margin-left: 20px;
}
}
.toc h3 {
text-decoration: none
}
.toc h3 {
font: 500 14px/1.2 Arial;
font-weight: 100;
padding: 0px;
margin: 0px;
padding-top: 5px;
padding-left: 5px;
}
.toc ul {
padding-left: 10px;
padding-right: 5px;
}
.toc ul li {
margin-left: 15px;
list-style-image: url(../images/bullet_dn.png);
marker-offset: 0px;
margin-bottom: 8px;
padding-left: 0px;
}
.toc .level1 {
border: none
}
.toc .level2 {
border: none;
margin-left: 25px;
}
.level3 {
border: none;
margin-left: 30px;
}
.clearfix {
clear: both
}
/*
-----------
Landing page
-----------
*/
.col-group {
white-space: nowrap;
vertical-align: top;
}
.landing h2 {
background-color: transparent;
border: none;
margin-bottom: 0px;
font-size: 18px;
}
.landing a, .landing li {
font-size: 13px;
font-weight: bold !important;
}
.col-1 {
display: inline-block;
white-space: normal;
width: 70%;
height: 100%;
float: left;
}
.col-2 {
display: inline-block;
white-space: normal;
width: 20%;
margin-left: 5%;
position: relative;
top: -20px;
}
.col-1 h1 {
margin: 20px 0 0 0;
}
.col-1 h2 {
font-size: 18px;
font-weight: bold !important;
}
.landingicons {
display: inline-block;
width: 100%;
}
.icons1of3 {
display: inline-block;
width: 33.3333%;
float: left;
}
.icons1of3 h2, .doc-column h2 {
font-size: 15px;
margin: 0px;
padding: 0px;
}
div.multi-column {
position: relative;
}
div.multi-column div {
display: -moz-inline-box;
display: inline-block;
vertical-align: top;
margin-top: 1em;
margin-right: 4em;
width: 24em;
}
.mainContent .video {
width:40%;
max-width:640px;
margin: 15px 0 0 15px;
position:relative;
display:table
}
.mainContent .video > .vspan {
padding-top:60%;
display:block
}
.mainContent .video iframe {
width:100%;
height:100%;
position:absolute;
top:0;
left:0
}

View file

@ -1,128 +0,0 @@
#include "expressionrole.h"
#include "qqmlsortfilterproxymodel.h"
#include <QtQml>
namespace qqsfpm {
/*!
\qmltype ExpressionRole
\inherits SingleRole
\inqmlmodule SortFilterProxyModel
\ingroup ProxyRoles
\brief A custom role computed from a javascript expression.
An ExpressionRole is a \l ProxyRole allowing to implement a custom role based on a javascript expression.
In the following example, the \c c role is computed by adding the \c a role and \c b role of the model :
\code
SortFilterProxyModel {
sourceModel: numberModel
proxyRoles: ExpressionRole {
name: "c"
expression: model.a + model.b
}
}
\endcode
*/
/*!
\qmlproperty expression ExpressionRole::expression
An expression to implement a custom role.
It has the same syntax has a \l {http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html} {Property Binding} except it will be evaluated for each of the source model's rows.
The data for this role will be the retuned valued of the expression.
Data for each row is exposed like for a delegate of a QML View.
This expression is reevaluated for a row every time its model data changes.
When an external property (not \c index or in \c model) the expression depends on changes, the expression is reevaluated for every row of the source model.
To capture the properties the expression depends on, the expression is first executed with invalid data and each property access is detected by the QML engine.
This means that if a property is not accessed because of a conditional, it won't be captured and the expression won't be reevaluted when this property changes.
A workaround to this problem is to access all the properties the expressions depends unconditionally at the beggining of the expression.
*/
const QQmlScriptString& ExpressionRole::expression() const
{
return m_scriptString;
}
void ExpressionRole::setExpression(const QQmlScriptString& scriptString)
{
if (m_scriptString == scriptString)
return;
m_scriptString = scriptString;
updateExpression();
Q_EMIT expressionChanged();
invalidate();
}
void ExpressionRole::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
updateContext(proxyModel);
}
QVariant ExpressionRole::data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel)
{
if (!m_scriptString.isEmpty()) {
QVariantMap modelMap;
QHash<int, QByteArray> roles = proxyModel.roleNames();
QQmlContext context(qmlContext(this));
auto addToContext = [&] (const QString &name, const QVariant& value) {
context.setContextProperty(name, value);
modelMap.insert(name, value);
};
for (auto it = roles.cbegin(); it != roles.cend(); ++it)
addToContext(it.value(), proxyModel.sourceData(sourceIndex, it.key()));
addToContext("index", sourceIndex.row());
context.setContextProperty("model", modelMap);
QQmlExpression expression(m_scriptString, &context);
QVariant result = expression.evaluate();
if (expression.hasError()) {
qWarning() << expression.error();
return true;
}
return result;
}
return QVariant();
}
void ExpressionRole::updateContext(const QQmlSortFilterProxyModel& proxyModel)
{
delete m_context;
m_context = new QQmlContext(qmlContext(this), this);
// what about roles changes ?
QVariantMap modelMap;
auto addToContext = [&] (const QString &name, const QVariant& value) {
m_context->setContextProperty(name, value);
modelMap.insert(name, value);
};
for (const QByteArray& roleName : proxyModel.roleNames().values())
addToContext(roleName, QVariant());
addToContext("index", -1);
m_context->setContextProperty("model", modelMap);
updateExpression();
}
void ExpressionRole::updateExpression()
{
if (!m_context)
return;
delete m_expression;
m_expression = new QQmlExpression(m_scriptString, m_context, 0, this);
connect(m_expression, &QQmlExpression::valueChanged, this, &ExpressionRole::invalidate);
m_expression->setNotifyOnValueChanged(true);
m_expression->evaluate();
}
}

View file

@ -1,39 +0,0 @@
#ifndef EXPRESSIONROLE_H
#define EXPRESSIONROLE_H
#include "singlerole.h"
#include <QQmlScriptString>
class QQmlExpression;
namespace qqsfpm {
class ExpressionRole : public SingleRole
{
Q_OBJECT
Q_PROPERTY(QQmlScriptString expression READ expression WRITE setExpression NOTIFY expressionChanged)
public:
using SingleRole::SingleRole;
const QQmlScriptString& expression() const;
void setExpression(const QQmlScriptString& scriptString);
void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override;
Q_SIGNALS:
void expressionChanged();
private:
QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override;
void updateContext(const QQmlSortFilterProxyModel& proxyModel);
void updateExpression();
QQmlScriptString m_scriptString;
QQmlExpression* m_expression = nullptr;
QQmlContext* m_context = nullptr;
};
}
#endif // EXPRESSIONROLE_H

View file

@ -1,65 +0,0 @@
#include "filterrole.h"
#include "filters/filter.h"
namespace qqsfpm {
/*!
\qmltype FilterRole
\inherits SingleRole
\inqmlmodule SortFilterProxyModel
\ingroup ProxyRoles
\ingroup FilterContainer
\brief A role resolving to \c true for rows matching all its filters.
A FilterRole is a \l ProxyRole that returns \c true for rows matching all its filters.
In the following example, the \c isAdult role will be equal to \c true if the \c age role is superior or equal to 18.
\code
SortFilterProxyModel {
sourceModel: personModel
proxyRoles: FilterRole {
name: "isAdult"
RangeFilter { roleName: "age"; minimumValue: 18; minimumInclusive: true }
}
}
\endcode
\sa FilterContainer
*/
/*!
\qmlproperty list<Filter> FilterRole::filters
\default
This property holds the list of filters for this filter role.
The data of this role will be equal to the \c true if all its filters match the model row, \c false otherwise.
\sa Filter, FilterContainer
*/
void FilterRole::onFilterAppended(Filter* filter)
{
connect(filter, &Filter::invalidated, this, &FilterRole::invalidate);
invalidate();
}
void FilterRole::onFilterRemoved(Filter* filter)
{
disconnect(filter, &Filter::invalidated, this, &FilterRole::invalidate);
invalidate();
}
void FilterRole::onFiltersCleared()
{
invalidate();
}
QVariant FilterRole::data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel)
{
return std::all_of(m_filters.begin(), m_filters.end(),
[&] (Filter* filter) {
return filter->filterAcceptsRow(sourceIndex, proxyModel);
}
);
}
}

View file

@ -1,29 +0,0 @@
#ifndef FILTERROLE_H
#define FILTERROLE_H
#include "singlerole.h"
#include "filters/filtercontainer.h"
namespace qqsfpm {
class FilterRole : public SingleRole, public FilterContainer
{
Q_OBJECT
Q_INTERFACES(qqsfpm::FilterContainer)
Q_PROPERTY(QQmlListProperty<qqsfpm::Filter> filters READ filtersListProperty)
Q_CLASSINFO("DefaultProperty", "filters")
public:
using SingleRole::SingleRole;
private:
void onFilterAppended(Filter* filter) override;
void onFilterRemoved(Filter* filter) override;
void onFiltersCleared() override;
QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override;
};
}
#endif // FILTERROLE_H

View file

@ -1,83 +0,0 @@
#include "joinrole.h"
#include "qqmlsortfilterproxymodel.h"
namespace qqsfpm {
/*!
\qmltype JoinRole
\inherits SingleRole
\inqmlmodule SortFilterProxyModel
\ingroup ProxyRoles
\brief a role made from concatenating other roles.
A JoinRole is a simple \l ProxyRole that concatenates other roles.
In the following example, the \c fullName role is computed by the concatenation of the \c firstName role and the \c lastName role separated by a space :
\code
SortFilterProxyModel {
sourceModel: contactModel
proxyRoles: JoinRole {
name: "fullName"
roleNames: ["firstName", "lastName"]
}
}
\endcode
*/
/*!
\qmlproperty list<string> JoinRole::roleNames
This property holds the role names that are joined by this role.
*/
QStringList JoinRole::roleNames() const
{
return m_roleNames;
}
void JoinRole::setRoleNames(const QStringList& roleNames)
{
if (m_roleNames == roleNames)
return;
m_roleNames = roleNames;
Q_EMIT roleNamesChanged();
invalidate();
}
/*!
\qmlproperty string JoinRole::separator
This property holds the separator that is used to join the roles specified in \l roleNames.
By default, it's a space.
*/
QString JoinRole::separator() const
{
return m_separator;
}
void JoinRole::setSeparator(const QString& separator)
{
if (m_separator == separator)
return;
m_separator = separator;
Q_EMIT separatorChanged();
invalidate();
}
QVariant JoinRole::data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel& proxyModel)
{
QString result;
for (const QString& roleName : m_roleNames)
result += proxyModel.sourceData(sourceIndex, roleName).toString() + m_separator;
if (!m_roleNames.isEmpty())
result.chop(m_separator.length());
return result;
}
}

View file

@ -1,36 +0,0 @@
#ifndef JOINROLE_H
#define JOINROLE_H
#include "singlerole.h"
namespace qqsfpm {
class JoinRole : public SingleRole
{
Q_OBJECT
Q_PROPERTY(QStringList roleNames READ roleNames WRITE setRoleNames NOTIFY roleNamesChanged)
Q_PROPERTY(QString separator READ separator WRITE setSeparator NOTIFY separatorChanged)
public:
using SingleRole::SingleRole;
QStringList roleNames() const;
void setRoleNames(const QStringList& roleNames);
QString separator() const;
void setSeparator(const QString& separator);
Q_SIGNALS:
void roleNamesChanged();
void separatorChanged();
private:
QStringList m_roleNames;
QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override;
QString m_separator = " ";
};
}
#endif // JOINROLE_H

View file

@ -1,46 +0,0 @@
#include "proxyrole.h"
#include <QQmlEngine>
#include <QQmlContext>
#include <QQmlExpression>
#include <QCoreApplication>
#include <QDebug>
#include <QQmlInfo>
#include "filters/filter.h"
#include "qqmlsortfilterproxymodel.h"
namespace qqsfpm {
/*!
\qmltype ProxyRole
\inqmlmodule SortFilterProxyModel
\ingroup ProxyRoles
\brief Base type for the \l SortFilterProxyModel proxy roles.
The ProxyRole type cannot be used directly in a QML file.
It exists to provide a set of common properties and methods,
available across all the other proxy role types that inherit from it.
Attempting to use the ProxyRole type directly will result in an error.
*/
QVariant ProxyRole::roleData(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel, const QString &name)
{
if (m_mutex.tryLock()) {
QVariant result = data(sourceIndex, proxyModel, name);
m_mutex.unlock();
return result;
} else {
return {};
}
}
void ProxyRole::proxyModelCompleted(const QQmlSortFilterProxyModel &proxyModel)
{
Q_UNUSED(proxyModel)
}
void ProxyRole::invalidate()
{
Q_EMIT invalidated();
}
}

View file

@ -1,40 +0,0 @@
#ifndef PROXYROLE_H
#define PROXYROLE_H
#include <QObject>
#include <QMutex>
namespace qqsfpm {
class QQmlSortFilterProxyModel;
class ProxyRole : public QObject
{
Q_OBJECT
public:
using QObject::QObject;
virtual ~ProxyRole() = default;
QVariant roleData(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel, const QString& name);
virtual void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel);
virtual QStringList names() = 0;
protected:
void invalidate();
Q_SIGNALS:
void invalidated();
void namesAboutToBeChanged();
void namesChanged();
private:
virtual QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel, const QString& name) = 0;
QMutex m_mutex;
};
}
#endif // PROXYROLE_H

View file

@ -1,64 +0,0 @@
#include "proxyrolecontainer.h"
namespace qqsfpm {
QList<ProxyRole*> ProxyRoleContainer::proxyRoles() const
{
return m_proxyRoles;
}
void ProxyRoleContainer::appendProxyRole(ProxyRole* proxyRole)
{
m_proxyRoles.append(proxyRole);
onProxyRoleAppended(proxyRole);
}
void ProxyRoleContainer::removeProxyRole(ProxyRole* proxyRole)
{
m_proxyRoles.removeOne(proxyRole);
onProxyRoleRemoved(proxyRole);
}
void ProxyRoleContainer::clearProxyRoles()
{
m_proxyRoles.clear();
onProxyRolesCleared();
}
QQmlListProperty<ProxyRole> ProxyRoleContainer::proxyRolesListProperty()
{
return QQmlListProperty<ProxyRole>(reinterpret_cast<QObject*>(this), &m_proxyRoles,
&ProxyRoleContainer::append_proxyRole,
&ProxyRoleContainer::count_proxyRole,
&ProxyRoleContainer::at_proxyRole,
&ProxyRoleContainer::clear_proxyRoles);
}
void ProxyRoleContainer::append_proxyRole(QQmlListProperty<ProxyRole>* list, ProxyRole* proxyRole)
{
if (!proxyRole)
return;
ProxyRoleContainer* that = reinterpret_cast<ProxyRoleContainer*>(list->object);
that->appendProxyRole(proxyRole);
}
int ProxyRoleContainer::count_proxyRole(QQmlListProperty<ProxyRole>* list)
{
QList<ProxyRole*>* ProxyRoles = static_cast<QList<ProxyRole*>*>(list->data);
return ProxyRoles->count();
}
ProxyRole* ProxyRoleContainer::at_proxyRole(QQmlListProperty<ProxyRole>* list, int index)
{
QList<ProxyRole*>* ProxyRoles = static_cast<QList<ProxyRole*>*>(list->data);
return ProxyRoles->at(index);
}
void ProxyRoleContainer::clear_proxyRoles(QQmlListProperty<ProxyRole> *list)
{
ProxyRoleContainer* that = reinterpret_cast<ProxyRoleContainer*>(list->object);
that->clearProxyRoles();
}
}

View file

@ -1,42 +0,0 @@
#ifndef PROXYROLECONTAINER_H
#define PROXYROLECONTAINER_H
#include <QList>
#include <QQmlListProperty>
namespace qqsfpm {
class ProxyRole;
class QQmlSortFilterProxyModel;
class ProxyRoleContainer {
public:
virtual ~ProxyRoleContainer() = default;
QList<ProxyRole*> proxyRoles() const;
void appendProxyRole(ProxyRole* proxyRole);
void removeProxyRole(ProxyRole* proxyRole);
void clearProxyRoles();
QQmlListProperty<ProxyRole> proxyRolesListProperty();
protected:
QList<ProxyRole*> m_proxyRoles;
private:
virtual void onProxyRoleAppended(ProxyRole* proxyRole) = 0;
virtual void onProxyRoleRemoved(ProxyRole* proxyRole) = 0;
virtual void onProxyRolesCleared() = 0;
static void append_proxyRole(QQmlListProperty<ProxyRole>* list, ProxyRole* proxyRole);
static int count_proxyRole(QQmlListProperty<ProxyRole>* list);
static ProxyRole* at_proxyRole(QQmlListProperty<ProxyRole>* list, int index);
static void clear_proxyRoles(QQmlListProperty<ProxyRole>* list);
};
}
#define ProxyRoleContainer_iid "fr.grecko.SortFilterProxyModel.ProxyRoleContainer"
Q_DECLARE_INTERFACE(qqsfpm::ProxyRoleContainer, ProxyRoleContainer_iid)
#endif // PROXYROLECONTAINER_H

View file

@ -1,23 +0,0 @@
#include "proxyrole.h"
#include "joinrole.h"
#include "switchrole.h"
#include "expressionrole.h"
#include "regexprole.h"
#include "filterrole.h"
#include <QQmlEngine>
#include <QCoreApplication>
namespace qqsfpm {
void registerProxyRoleTypes() {
qmlRegisterUncreatableType<ProxyRole>("SortFilterProxyModel", 0, 2, "ProxyRole", "ProxyRole is an abstract class");
qmlRegisterType<JoinRole>("SortFilterProxyModel", 0, 2, "JoinRole");
qmlRegisterType<SwitchRole>("SortFilterProxyModel", 0, 2, "SwitchRole");
qmlRegisterType<ExpressionRole>("SortFilterProxyModel", 0, 2, "ExpressionRole");
qmlRegisterType<RegExpRole>("SortFilterProxyModel", 0, 2, "RegExpRole");
qmlRegisterType<FilterRole>("SortFilterProxyModel", 0, 2, "FilterRole");
}
Q_COREAPP_STARTUP_FUNCTION(registerProxyRoleTypes)
}

View file

@ -1,104 +0,0 @@
#include "regexprole.h"
#include "qqmlsortfilterproxymodel.h"
#include <QDebug>
namespace qqsfpm {
/*!
\qmltype RegExpRole
\inherits ProxyRole
\inqmlmodule SortFilterProxyModel
\ingroup ProxyRoles
\brief A ProxyRole extracting data from a source role via a regular expression.
A RegExpRole is a \l ProxyRole that provides a role for each named capture group of its regular expression \l pattern.
In the following example, the \c date role of the source model will be extracted in 3 roles in the proxy moodel: \c year, \c month and \c day.
\code
SortFilterProxyModel {
sourceModel: eventModel
proxyRoles: RegExpRole {
roleName: "date"
pattern: "(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})"
}
}
\endcode
*/
/*!
\qmlproperty QString RegExpRole::roleName
This property holds the role name that the RegExpRole is using to query the source model's data to extract new roles from.
*/
QString RegExpRole::roleName() const
{
return m_roleName;
}
void RegExpRole::setRoleName(const QString& roleName)
{
if (m_roleName == roleName)
return;
m_roleName = roleName;
Q_EMIT roleNameChanged();
}
/*!
\qmlproperty QString RegExpRole::pattern
This property holds the pattern of the regular expression of this RegExpRole.
The RegExpRole will expose a role for each of the named capture group of the pattern.
*/
QString RegExpRole::pattern() const
{
return m_regularExpression.pattern();
}
void RegExpRole::setPattern(const QString& pattern)
{
if (m_regularExpression.pattern() == pattern)
return;
Q_EMIT namesAboutToBeChanged();
m_regularExpression.setPattern(pattern);
invalidate();
Q_EMIT patternChanged();
Q_EMIT namesChanged();
}
/*!
\qmlproperty Qt::CaseSensitivity RegExpRole::caseSensitivity
This property holds the caseSensitivity of the regular expression.
*/
Qt::CaseSensitivity RegExpRole::caseSensitivity() const
{
return m_regularExpression.patternOptions() & QRegularExpression::CaseInsensitiveOption ?
Qt::CaseInsensitive : Qt::CaseSensitive;
}
void RegExpRole::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity)
{
if (this->caseSensitivity() == caseSensitivity)
return;
m_regularExpression.setPatternOptions(m_regularExpression.patternOptions() ^ QRegularExpression::CaseInsensitiveOption); //toggle the option
Q_EMIT caseSensitivityChanged();
}
QStringList RegExpRole::names()
{
QStringList nameCaptureGroups = m_regularExpression.namedCaptureGroups();
nameCaptureGroups.removeAll("");
return nameCaptureGroups;
}
QVariant RegExpRole::data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel, const QString &name)
{
QString text = proxyModel.sourceData(sourceIndex, m_roleName).toString();
QRegularExpressionMatch match = m_regularExpression.match(text);
return match.hasMatch() ? (match.captured(name)) : QVariant{};
}
}

View file

@ -1,43 +0,0 @@
#ifndef REGEXPROLE_H
#define REGEXPROLE_H
#include "proxyrole.h"
#include <QRegularExpression>
namespace qqsfpm {
class RegExpRole : public ProxyRole
{
Q_OBJECT
Q_PROPERTY(QString roleName READ roleName WRITE setRoleName NOTIFY roleNameChanged)
Q_PROPERTY(QString pattern READ pattern WRITE setPattern NOTIFY patternChanged)
Q_PROPERTY(Qt::CaseSensitivity caseSensitivity READ caseSensitivity WRITE setCaseSensitivity NOTIFY caseSensitivityChanged)
public:
using ProxyRole::ProxyRole;
QString roleName() const;
void setRoleName(const QString& roleName);
QString pattern() const;
void setPattern(const QString& pattern);
Qt::CaseSensitivity caseSensitivity() const;
void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity);
QStringList names() override;
Q_SIGNALS:
void roleNameChanged();
void patternChanged();
void caseSensitivityChanged();
private:
QString m_roleName;
QRegularExpression m_regularExpression;
QVariant data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel, const QString &name) override;
};
}
#endif // REGEXPROLE_H

View file

@ -1,52 +0,0 @@
#include "singlerole.h"
#include <QVariant>
namespace qqsfpm {
/*!
\qmltype SingleRole
\qmlabstract
\inherits ProxyRole
\inqmlmodule SortFilterProxyModel
\ingroup ProxyRoles
\brief Base type for the \l SortFilterProxyModel proxy roles defining a single role.
SingleRole is a convenience base class for proxy roles who define a single role.
It cannot be used directly in a QML file.
It exists to provide a set of common properties and methods,
available across all the other proxy role types that inherit from it.
Attempting to use the SingleRole type directly will result in an error.
*/
/*!
\qmlproperty string SingleRole::name
This property holds the role name of the proxy role.
*/
QString SingleRole::name() const
{
return m_name;
}
void SingleRole::setName(const QString& name)
{
if (m_name == name)
return;
Q_EMIT namesAboutToBeChanged();
m_name = name;
Q_EMIT nameChanged();
Q_EMIT namesChanged();
}
QStringList SingleRole::names()
{
return QStringList { m_name };
}
QVariant SingleRole::data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel, const QString &name)
{
Q_UNUSED(name);
return data(sourceIndex, proxyModel);
}
}

View file

@ -1,34 +0,0 @@
#ifndef SINGLEROLE_H
#define SINGLEROLE_H
#include "proxyrole.h"
namespace qqsfpm {
class SingleRole : public ProxyRole
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
public:
using ProxyRole::ProxyRole;
QString name() const;
void setName(const QString& name);
QStringList names() override;
Q_SIGNALS:
void nameChanged();
private:
QString m_name;
private:
QVariant data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel, const QString &name) final;
virtual QVariant data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel) = 0;
};
}
#endif // SINGLEROLE_H

View file

@ -1,168 +0,0 @@
#include "switchrole.h"
#include "qqmlsortfilterproxymodel.h"
#include "filters/filter.h"
#include <QtQml>
namespace qqsfpm {
/*!
\qmltype SwitchRole
\inherits SingleRole
\inqmlmodule SortFilterProxyModel
\ingroup ProxyRoles
\ingroup FilterContainer
\brief A role using \l Filter to conditionnaly compute its data.
A SwitchRole is a \l ProxyRole that computes its data with the help of \l Filter.
Each top level filters specified in the \l SwitchRole is evaluated on the rows of the model, if a \l Filter evaluates to true, the data of the \l SwitchRole for this row will be the one of the attached \l {value} {SwitchRole.value} property.
If no top level filters evaluate to true, the data will default to the one of the \l defaultRoleName (or the \l defaultValue if no \l defaultRoleName is specified).
In the following example, the \c favoriteOrFirstNameSection role is equal to \c * if the \c favorite role of a row is true, otherwise it's the same as the \c firstName role :
\code
SortFilterProxyModel {
sourceModel: contactModel
proxyRoles: SwitchRole {
name: "favoriteOrFirstNameSection"
filters: ValueFilter {
roleName: "favorite"
value: true
SwitchRole.value: "*"
}
defaultRoleName: "firstName"
}
}
\endcode
\sa FilterContainer
*/
SwitchRoleAttached::SwitchRoleAttached(QObject* parent) : QObject (parent)
{
if (!qobject_cast<Filter*>(parent))
qmlInfo(parent) << "SwitchRole must be attached to a Filter";
}
/*!
\qmlattachedproperty var SwitchRole::value
This property attaches a value to a \l Filter.
*/
QVariant SwitchRoleAttached::value() const
{
return m_value;
}
void SwitchRoleAttached::setValue(QVariant value)
{
if (m_value == value)
return;
m_value = value;
Q_EMIT valueChanged();
}
/*!
\qmlproperty string SwitchRole::defaultRoleName
This property holds the default role name of the role.
If no filter match a row, the data of this role will be the data of the role whose name is \c defaultRoleName.
*/
QString SwitchRole::defaultRoleName() const
{
return m_defaultRoleName;
}
void SwitchRole::setDefaultRoleName(const QString& defaultRoleName)
{
if (m_defaultRoleName == defaultRoleName)
return;
m_defaultRoleName = defaultRoleName;
Q_EMIT defaultRoleNameChanged();
invalidate();
}
/*!
\qmlproperty var SwitchRole::defaultValue
This property holds the default value of the role.
If no filter match a row, and no \l defaultRoleName is set, the data of this role will be \c defaultValue.
*/
QVariant SwitchRole::defaultValue() const
{
return m_defaultValue;
}
void SwitchRole::setDefaultValue(const QVariant& defaultValue)
{
if (m_defaultValue == defaultValue)
return;
m_defaultValue = defaultValue;
Q_EMIT defaultValueChanged();
invalidate();
}
/*!
\qmlproperty list<Filter> SwitchRole::filters
\default
This property holds the list of filters for this proxy role.
The data of this role will be equal to the attached \l {value} {SwitchRole.value} property of the first filter that matches the model row.
\sa Filter, FilterContainer
*/
void SwitchRole::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
for (Filter* filter : m_filters)
filter->proxyModelCompleted(proxyModel);
}
SwitchRoleAttached* SwitchRole::qmlAttachedProperties(QObject* object)
{
return new SwitchRoleAttached(object);
}
QVariant SwitchRole::data(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel)
{
for (auto filter: m_filters) {
if (!filter->enabled())
continue;
if (filter->filterAcceptsRow(sourceIndex, proxyModel)) {
auto attached = static_cast<SwitchRoleAttached*>(qmlAttachedPropertiesObject<SwitchRole>(filter, false));
if (!attached) {
qWarning() << "No SwitchRole.value provided for this filter" << filter;
continue;
}
QVariant value = attached->value();
if (!value.isValid()) {
qWarning() << "No SwitchRole.value provided for this filter" << filter;
continue;
}
return value;
}
}
if (!m_defaultRoleName.isEmpty())
return proxyModel.sourceData(sourceIndex, m_defaultRoleName);
return m_defaultValue;
}
void SwitchRole::onFilterAppended(Filter *filter)
{
connect(filter, &Filter::invalidated, this, &SwitchRole::invalidate);
auto attached = static_cast<SwitchRoleAttached*>(qmlAttachedPropertiesObject<SwitchRole>(filter, true));
connect(attached, &SwitchRoleAttached::valueChanged, this, &SwitchRole::invalidate);
invalidate();
}
void SwitchRole::onFilterRemoved(Filter *filter)
{
Q_UNUSED(filter)
invalidate();
}
void SwitchRole::onFiltersCleared()
{
invalidate();
}
}

View file

@ -1,68 +0,0 @@
#ifndef SWITCHROLE_H
#define SWITCHROLE_H
#include "singlerole.h"
#include "filters/filtercontainer.h"
#include <QtQml>
namespace qqsfpm {
class SwitchRoleAttached : public QObject
{
Q_OBJECT
Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged)
public:
SwitchRoleAttached(QObject* parent);
QVariant value() const;
void setValue(QVariant value);
Q_SIGNALS:
void valueChanged();
private:
QVariant m_value;
};
class SwitchRole : public SingleRole, public FilterContainer
{
Q_OBJECT
Q_INTERFACES(qqsfpm::FilterContainer)
Q_PROPERTY(QString defaultRoleName READ defaultRoleName WRITE setDefaultRoleName NOTIFY defaultRoleNameChanged)
Q_PROPERTY(QVariant defaultValue READ defaultValue WRITE setDefaultValue NOTIFY defaultValueChanged)
Q_PROPERTY(QQmlListProperty<qqsfpm::Filter> filters READ filtersListProperty)
Q_CLASSINFO("DefaultProperty", "filters")
public:
using SingleRole::SingleRole;
QString defaultRoleName() const;
void setDefaultRoleName(const QString& defaultRoleName);
QVariant defaultValue() const;
void setDefaultValue(const QVariant& defaultValue);
void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override;
static SwitchRoleAttached* qmlAttachedProperties(QObject* object);
Q_SIGNALS:
void defaultRoleNameChanged();
void defaultValueChanged();
private:
QVariant data(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) override;
void onFilterAppended(Filter *filter) override;
void onFilterRemoved(Filter *filter) override;
void onFiltersCleared() override;
QString m_defaultRoleName;
QVariant m_defaultValue;
};
}
QML_DECLARE_TYPEINFO(qqsfpm::SwitchRole, QML_HAS_ATTACHED_PROPERTIES)
#endif // SWITCHROLE_H

View file

@ -1,579 +0,0 @@
#include "qqmlsortfilterproxymodel.h"
#include <QtQml>
#include <algorithm>
#include "filters/filter.h"
#include "sorters/sorter.h"
#include "proxyroles/proxyrole.h"
namespace qqsfpm {
/*!
\qmltype SortFilterProxyModel
\inqmlmodule SortFilterProxyModel
\ingroup SortFilterProxyModel
\ingroup FilterContainer
\ingroup SorterContainer
\brief Filters and sorts data coming from a source \l {http://doc.qt.io/qt-5/qabstractitemmodel.html} {QAbstractItemModel}.
The SortFilterProxyModel type provides support for filtering and sorting data coming from a source model.
\sa FilterContainer, SorterContainer
*/
QQmlSortFilterProxyModel::QQmlSortFilterProxyModel(QObject *parent) :
QSortFilterProxyModel(parent),
#ifdef SFPM_DELAYED
m_delayed(true)
#else
m_delayed(false)
#endif
{
connect(this, &QAbstractProxyModel::sourceModelChanged, this, &QQmlSortFilterProxyModel::updateRoles);
connect(this, &QAbstractItemModel::modelReset, this, &QQmlSortFilterProxyModel::updateRoles);
connect(this, &QAbstractItemModel::rowsInserted, this, &QQmlSortFilterProxyModel::countChanged);
connect(this, &QAbstractItemModel::rowsRemoved, this, &QQmlSortFilterProxyModel::countChanged);
connect(this, &QAbstractItemModel::modelReset, this, &QQmlSortFilterProxyModel::countChanged);
connect(this, &QAbstractItemModel::layoutChanged, this, &QQmlSortFilterProxyModel::countChanged);
connect(this, &QAbstractItemModel::dataChanged, this, &QQmlSortFilterProxyModel::onDataChanged);
setDynamicSortFilter(true);
}
/*!
\qmlproperty QAbstractItemModel* SortFilterProxyModel::sourceModel
The source model of this proxy model
*/
/*!
\qmlproperty int SortFilterProxyModel::count
The number of rows in the proxy model (not filtered out the source model)
*/
int QQmlSortFilterProxyModel::count() const
{
return rowCount();
}
/*!
\qmlproperty bool SortFilterProxyModel::delayed
Delay the execution of filters, sorters and proxyRoles until the next event loop.
This can be used as an optimization when multiple filters, sorters or proxyRoles are changed in a single event loop.
They will be executed once in a single batch at the next event loop instead of being executed in multiple sequential batches.
By default, the SortFilterProxyModel is not delayed, unless the \c SFPM_DELAYED environment variable is defined at compile time.
*/
bool QQmlSortFilterProxyModel::delayed() const
{
return m_delayed;
}
void QQmlSortFilterProxyModel::setDelayed(bool delayed)
{
if (m_delayed == delayed)
return;
m_delayed = delayed;
Q_EMIT delayedChanged();
}
const QString& QQmlSortFilterProxyModel::filterRoleName() const
{
return m_filterRoleName;
}
void QQmlSortFilterProxyModel::setFilterRoleName(const QString& filterRoleName)
{
if (m_filterRoleName == filterRoleName)
return;
m_filterRoleName = filterRoleName;
updateFilterRole();
Q_EMIT filterRoleNameChanged();
}
QString QQmlSortFilterProxyModel::filterPattern() const
{
return filterRegExp().pattern();
}
void QQmlSortFilterProxyModel::setFilterPattern(const QString& filterPattern)
{
QRegExp regExp = filterRegExp();
if (regExp.pattern() == filterPattern)
return;
regExp.setPattern(filterPattern);
QSortFilterProxyModel::setFilterRegExp(regExp);
Q_EMIT filterPatternChanged();
}
QQmlSortFilterProxyModel::PatternSyntax QQmlSortFilterProxyModel::filterPatternSyntax() const
{
return static_cast<PatternSyntax>(filterRegExp().patternSyntax());
}
void QQmlSortFilterProxyModel::setFilterPatternSyntax(QQmlSortFilterProxyModel::PatternSyntax patternSyntax)
{
QRegExp regExp = filterRegExp();
QRegExp::PatternSyntax patternSyntaxTmp = static_cast<QRegExp::PatternSyntax>(patternSyntax);
if (regExp.patternSyntax() == patternSyntaxTmp)
return;
regExp.setPatternSyntax(patternSyntaxTmp);
QSortFilterProxyModel::setFilterRegExp(regExp);
Q_EMIT filterPatternSyntaxChanged();
}
const QVariant& QQmlSortFilterProxyModel::filterValue() const
{
return m_filterValue;
}
void QQmlSortFilterProxyModel::setFilterValue(const QVariant& filterValue)
{
if (m_filterValue == filterValue)
return;
m_filterValue = filterValue;
queueInvalidateFilter();
Q_EMIT filterValueChanged();
}
/*!
\qmlproperty string SortFilterProxyModel::sortRoleName
The role name of the source model's data used for the sorting.
\sa {http://doc.qt.io/qt-5/qsortfilterproxymodel.html#sortRole-prop} {sortRole}, roleForName
*/
const QString& QQmlSortFilterProxyModel::sortRoleName() const
{
return m_sortRoleName;
}
void QQmlSortFilterProxyModel::setSortRoleName(const QString& sortRoleName)
{
if (m_sortRoleName == sortRoleName)
return;
m_sortRoleName = sortRoleName;
updateSortRole();
Q_EMIT sortRoleNameChanged();
}
bool QQmlSortFilterProxyModel::ascendingSortOrder() const
{
return m_ascendingSortOrder;
}
void QQmlSortFilterProxyModel::setAscendingSortOrder(bool ascendingSortOrder)
{
if (m_ascendingSortOrder == ascendingSortOrder)
return;
m_ascendingSortOrder = ascendingSortOrder;
Q_EMIT ascendingSortOrderChanged();
queueInvalidate();
}
/*!
\qmlproperty list<Filter> SortFilterProxyModel::filters
This property holds the list of filters for this proxy model. To be included in the model, a row of the source model has to be accepted by all the top level filters of this list.
\sa Filter, FilterContainer
*/
/*!
\qmlproperty list<Sorter> SortFilterProxyModel::sorters
This property holds the list of sorters for this proxy model. The rows of the source model are sorted by the sorters of this list, in their order of insertion.
\sa Sorter, SorterContainer
*/
/*!
\qmlproperty list<ProxyRole> SortFilterProxyModel::proxyRoles
This property holds the list of proxy roles for this proxy model. Each proxy role adds a new custom role to the model.
\sa ProxyRole
*/
void QQmlSortFilterProxyModel::classBegin()
{
}
void QQmlSortFilterProxyModel::componentComplete()
{
m_completed = true;
for (const auto& filter : m_filters)
filter->proxyModelCompleted(*this);
for (const auto& sorter : m_sorters)
sorter->proxyModelCompleted(*this);
for (const auto& proxyRole : m_proxyRoles)
proxyRole->proxyModelCompleted(*this);
invalidate();
sort(0);
}
QVariant QQmlSortFilterProxyModel::sourceData(const QModelIndex& sourceIndex, const QString& roleName) const
{
int role = roleNames().key(roleName.toUtf8());
return sourceData(sourceIndex, role);
}
QVariant QQmlSortFilterProxyModel::sourceData(const QModelIndex &sourceIndex, int role) const
{
QPair<ProxyRole*, QString> proxyRolePair = m_proxyRoleMap[role];
if (ProxyRole* proxyRole = proxyRolePair.first)
return proxyRole->roleData(sourceIndex, *this, proxyRolePair.second);
else
return sourceModel()->data(sourceIndex, role);
}
QVariant QQmlSortFilterProxyModel::data(const QModelIndex &index, int role) const
{
return sourceData(mapToSource(index), role);
}
QHash<int, QByteArray> QQmlSortFilterProxyModel::roleNames() const
{
return m_roleNames.isEmpty() && sourceModel() ? sourceModel()->roleNames() : m_roleNames;
}
/*!
\qmlmethod int SortFilterProxyModel::roleForName(string roleName)
Returns the role number for the given \a roleName.
If no role is found for this \a roleName, \c -1 is returned.
*/
int QQmlSortFilterProxyModel::roleForName(const QString& roleName) const
{
return m_roleNames.key(roleName.toUtf8(), -1);
}
/*!
\qmlmethod object SortFilterProxyModel::get(int row)
Return the item at \a row in the proxy model as a map of all its roles. This allows the item data to be read (not modified) from JavaScript.
*/
QVariantMap QQmlSortFilterProxyModel::get(int row) const
{
QVariantMap map;
QModelIndex modelIndex = index(row, 0);
QHash<int, QByteArray> roles = roleNames();
for (QHash<int, QByteArray>::const_iterator it = roles.begin(); it != roles.end(); ++it)
map.insert(it.value(), data(modelIndex, it.key()));
return map;
}
/*!
\qmlmethod variant SortFilterProxyModel::get(int row, string roleName)
Return the data for the given \a roleName of the item at \a row in the proxy model. This allows the role data to be read (not modified) from JavaScript.
This equivalent to calling \c {data(index(row, 0), roleForName(roleName))}.
*/
QVariant QQmlSortFilterProxyModel::get(int row, const QString& roleName) const
{
return data(index(row, 0), roleForName(roleName));
}
/*!
\qmlmethod index SortFilterProxyModel::mapToSource(index proxyIndex)
Returns the source model index corresponding to the given \a proxyIndex from the SortFilterProxyModel.
*/
QModelIndex QQmlSortFilterProxyModel::mapToSource(const QModelIndex& proxyIndex) const
{
return QSortFilterProxyModel::mapToSource(proxyIndex);
}
/*!
\qmlmethod int SortFilterProxyModel::mapToSource(int proxyRow)
Returns the source model row corresponding to the given \a proxyRow from the SortFilterProxyModel.
Returns -1 if there is no corresponding row.
*/
int QQmlSortFilterProxyModel::mapToSource(int proxyRow) const
{
QModelIndex proxyIndex = index(proxyRow, 0);
QModelIndex sourceIndex = mapToSource(proxyIndex);
return sourceIndex.isValid() ? sourceIndex.row() : -1;
}
/*!
\qmlmethod QModelIndex SortFilterProxyModel::mapFromSource(QModelIndex sourceIndex)
Returns the model index in the SortFilterProxyModel given the \a sourceIndex from the source model.
*/
QModelIndex QQmlSortFilterProxyModel::mapFromSource(const QModelIndex& sourceIndex) const
{
return QSortFilterProxyModel::mapFromSource(sourceIndex);
}
/*!
\qmlmethod int SortFilterProxyModel::mapFromSource(int sourceRow)
Returns the row in the SortFilterProxyModel given the \a sourceRow from the source model.
Returns -1 if there is no corresponding row.
*/
int QQmlSortFilterProxyModel::mapFromSource(int sourceRow) const
{
QModelIndex proxyIndex;
if (QAbstractItemModel* source = sourceModel()) {
QModelIndex sourceIndex = source->index(sourceRow, 0);
proxyIndex = mapFromSource(sourceIndex);
}
return proxyIndex.isValid() ? proxyIndex.row() : -1;
}
bool QQmlSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
{
if (!m_completed)
return true;
QModelIndex sourceIndex = sourceModel()->index(source_row, 0, source_parent);
bool valueAccepted = !m_filterValue.isValid() || ( m_filterValue == sourceModel()->data(sourceIndex, filterRole()) );
bool baseAcceptsRow = valueAccepted && QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
baseAcceptsRow = baseAcceptsRow && std::all_of(m_filters.begin(), m_filters.end(),
[=, &source_parent] (Filter* filter) {
return filter->filterAcceptsRow(sourceIndex, *this);
}
);
return baseAcceptsRow;
}
bool QQmlSortFilterProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const
{
if (m_completed) {
if (!m_sortRoleName.isEmpty()) {
if (QSortFilterProxyModel::lessThan(source_left, source_right))
return m_ascendingSortOrder;
if (QSortFilterProxyModel::lessThan(source_right, source_left))
return !m_ascendingSortOrder;
}
auto sortedSorters = m_sorters;
std::stable_sort(sortedSorters.begin(),
sortedSorters.end(),
[] (Sorter* a, Sorter* b) {
return a->priority() > b->priority();
});
for(auto sorter : sortedSorters) {
if (sorter->enabled()) {
int comparison = sorter->compareRows(source_left, source_right, *this);
if (comparison != 0)
return comparison < 0;
}
}
}
return source_left.row() < source_right.row();
}
void QQmlSortFilterProxyModel::resetInternalData()
{
QSortFilterProxyModel::resetInternalData();
updateRoleNames();
}
void QQmlSortFilterProxyModel::setSourceModel(QAbstractItemModel *sourceModel)
{
if (sourceModel && sourceModel->roleNames().isEmpty()) { // workaround for when a model has no roles and roles are added when the model is populated (ListModel)
// QTBUG-57971
connect(sourceModel, &QAbstractItemModel::rowsInserted, this, &QQmlSortFilterProxyModel::initRoles);
}
QSortFilterProxyModel::setSourceModel(sourceModel);
}
void QQmlSortFilterProxyModel::queueInvalidateFilter()
{
if (m_delayed) {
if (!m_invalidateFilterQueued && !m_invalidateQueued) {
m_invalidateFilterQueued = true;
QMetaObject::invokeMethod(this, "invalidateFilter", Qt::QueuedConnection);
}
} else {
invalidateFilter();
}
}
void QQmlSortFilterProxyModel::invalidateFilter()
{
m_invalidateFilterQueued = false;
if (m_completed && !m_invalidateQueued)
QSortFilterProxyModel::invalidateFilter();
}
void QQmlSortFilterProxyModel::queueInvalidate()
{
if (m_delayed) {
if (!m_invalidateQueued) {
m_invalidateQueued = true;
QMetaObject::invokeMethod(this, "invalidate", Qt::QueuedConnection);
}
} else {
invalidate();
}
}
void QQmlSortFilterProxyModel::invalidate()
{
m_invalidateQueued = false;
if (m_completed)
QSortFilterProxyModel::invalidate();
}
void QQmlSortFilterProxyModel::updateRoleNames()
{
if (!sourceModel())
return;
m_roleNames = sourceModel()->roleNames();
m_proxyRoleMap.clear();
m_proxyRoleNumbers.clear();
auto roles = m_roleNames.keys();
auto maxIt = std::max_element(roles.cbegin(), roles.cend());
int maxRole = maxIt != roles.cend() ? *maxIt : -1;
for (auto proxyRole : m_proxyRoles) {
for (auto roleName : proxyRole->names()) {
++maxRole;
m_roleNames[maxRole] = roleName.toUtf8();
m_proxyRoleMap[maxRole] = {proxyRole, roleName};
m_proxyRoleNumbers.append(maxRole);
}
}
}
void QQmlSortFilterProxyModel::updateFilterRole()
{
QList<int> filterRoles = roleNames().keys(m_filterRoleName.toUtf8());
if (!filterRoles.empty())
{
setFilterRole(filterRoles.first());
}
}
void QQmlSortFilterProxyModel::updateSortRole()
{
QList<int> sortRoles = roleNames().keys(m_sortRoleName.toUtf8());
if (!sortRoles.empty())
{
setSortRole(sortRoles.first());
queueInvalidate();
}
}
void QQmlSortFilterProxyModel::updateRoles()
{
updateFilterRole();
updateSortRole();
}
void QQmlSortFilterProxyModel::initRoles()
{
disconnect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &QQmlSortFilterProxyModel::initRoles);
resetInternalData();
updateRoles();
}
void QQmlSortFilterProxyModel::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
{
Q_UNUSED(roles);
if (!roles.isEmpty() && !m_proxyRoleNumbers.empty() && roles != m_proxyRoleNumbers)
Q_EMIT dataChanged(topLeft, bottomRight, m_proxyRoleNumbers);
}
void QQmlSortFilterProxyModel::queueInvalidateProxyRoles()
{
queueInvalidate();
if (m_delayed) {
if (!m_invalidateProxyRolesQueued) {
m_invalidateProxyRolesQueued = true;
QMetaObject::invokeMethod(this, "invalidateProxyRoles", Qt::QueuedConnection);
}
} else {
invalidateProxyRoles();
}
}
void QQmlSortFilterProxyModel::invalidateProxyRoles()
{
m_invalidateProxyRolesQueued = false;
if (m_completed)
Q_EMIT dataChanged(index(0,0), index(rowCount() - 1, columnCount() - 1), m_proxyRoleNumbers);
}
QVariantMap QQmlSortFilterProxyModel::modelDataMap(const QModelIndex& modelIndex) const
{
QVariantMap map;
QHash<int, QByteArray> roles = roleNames();
for (QHash<int, QByteArray>::const_iterator it = roles.begin(); it != roles.end(); ++it)
map.insert(it.value(), sourceModel()->data(modelIndex, it.key()));
return map;
}
void QQmlSortFilterProxyModel::onFilterAppended(Filter* filter)
{
connect(filter, &Filter::invalidated, this, &QQmlSortFilterProxyModel::queueInvalidateFilter);
queueInvalidateFilter();
}
void QQmlSortFilterProxyModel::onFilterRemoved(Filter* filter)
{
Q_UNUSED(filter)
queueInvalidateFilter();
}
void QQmlSortFilterProxyModel::onFiltersCleared()
{
queueInvalidateFilter();
}
void QQmlSortFilterProxyModel::onSorterAppended(Sorter* sorter)
{
connect(sorter, &Sorter::invalidated, this, &QQmlSortFilterProxyModel::queueInvalidate);
queueInvalidate();
}
void QQmlSortFilterProxyModel::onSorterRemoved(Sorter* sorter)
{
Q_UNUSED(sorter)
queueInvalidate();
}
void QQmlSortFilterProxyModel::onSortersCleared()
{
queueInvalidate();
}
void QQmlSortFilterProxyModel::onProxyRoleAppended(ProxyRole *proxyRole)
{
beginResetModel();
connect(proxyRole, &ProxyRole::invalidated, this, &QQmlSortFilterProxyModel::queueInvalidateProxyRoles);
connect(proxyRole, &ProxyRole::namesAboutToBeChanged, this, &QQmlSortFilterProxyModel::beginResetModel);
connect(proxyRole, &ProxyRole::namesChanged, this, &QQmlSortFilterProxyModel::endResetModel);
endResetModel();
}
void QQmlSortFilterProxyModel::onProxyRoleRemoved(ProxyRole *proxyRole)
{
Q_UNUSED(proxyRole)
beginResetModel();
endResetModel();
}
void QQmlSortFilterProxyModel::onProxyRolesCleared()
{
beginResetModel();
endResetModel();
}
void registerQQmlSortFilterProxyModelTypes() {
qmlRegisterType<QQmlSortFilterProxyModel>("SortFilterProxyModel", 0, 2, "SortFilterProxyModel");
}
Q_COREAPP_STARTUP_FUNCTION(registerQQmlSortFilterProxyModelTypes)
}

View file

@ -1,160 +0,0 @@
#ifndef QQMLSORTFILTERPROXYMODEL_H
#define QQMLSORTFILTERPROXYMODEL_H
#include <QSortFilterProxyModel>
#include <QQmlParserStatus>
#include "filters/filtercontainer.h"
#include "sorters/sortercontainer.h"
#include "proxyroles/proxyrolecontainer.h"
namespace qqsfpm {
class QQmlSortFilterProxyModel : public QSortFilterProxyModel,
public QQmlParserStatus,
public FilterContainer,
public SorterContainer,
public ProxyRoleContainer
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
Q_INTERFACES(qqsfpm::FilterContainer)
Q_INTERFACES(qqsfpm::SorterContainer)
Q_INTERFACES(qqsfpm::ProxyRoleContainer)
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(bool delayed READ delayed WRITE setDelayed NOTIFY delayedChanged)
Q_PROPERTY(QString filterRoleName READ filterRoleName WRITE setFilterRoleName NOTIFY filterRoleNameChanged)
Q_PROPERTY(QString filterPattern READ filterPattern WRITE setFilterPattern NOTIFY filterPatternChanged)
Q_PROPERTY(PatternSyntax filterPatternSyntax READ filterPatternSyntax WRITE setFilterPatternSyntax NOTIFY filterPatternSyntaxChanged)
Q_PROPERTY(QVariant filterValue READ filterValue WRITE setFilterValue NOTIFY filterValueChanged)
Q_PROPERTY(QString sortRoleName READ sortRoleName WRITE setSortRoleName NOTIFY sortRoleNameChanged)
Q_PROPERTY(bool ascendingSortOrder READ ascendingSortOrder WRITE setAscendingSortOrder NOTIFY ascendingSortOrderChanged)
Q_PROPERTY(QQmlListProperty<qqsfpm::Filter> filters READ filtersListProperty)
Q_PROPERTY(QQmlListProperty<qqsfpm::Sorter> sorters READ sortersListProperty)
Q_PROPERTY(QQmlListProperty<qqsfpm::ProxyRole> proxyRoles READ proxyRolesListProperty)
public:
enum PatternSyntax {
RegExp = QRegExp::RegExp,
Wildcard = QRegExp::Wildcard,
FixedString = QRegExp::FixedString,
RegExp2 = QRegExp::RegExp2,
WildcardUnix = QRegExp::WildcardUnix,
W3CXmlSchema11 = QRegExp::W3CXmlSchema11 };
Q_ENUMS(PatternSyntax)
QQmlSortFilterProxyModel(QObject* parent = 0);
int count() const;
bool delayed() const;
void setDelayed(bool delayed);
const QString& filterRoleName() const;
void setFilterRoleName(const QString& filterRoleName);
QString filterPattern() const;
void setFilterPattern(const QString& filterPattern);
PatternSyntax filterPatternSyntax() const;
void setFilterPatternSyntax(PatternSyntax patternSyntax);
const QVariant& filterValue() const;
void setFilterValue(const QVariant& filterValue);
const QString& sortRoleName() const;
void setSortRoleName(const QString& sortRoleName);
bool ascendingSortOrder() const;
void setAscendingSortOrder(bool ascendingSortOrder);
void classBegin() override;
void componentComplete() override;
QVariant sourceData(const QModelIndex& sourceIndex, const QString& roleName) const;
QVariant sourceData(const QModelIndex& sourceIndex, int role) const;
QVariant data(const QModelIndex& index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE int roleForName(const QString& roleName) const;
Q_INVOKABLE QVariantMap get(int row) const;
Q_INVOKABLE QVariant get(int row, const QString& roleName) const;
Q_INVOKABLE QModelIndex mapToSource(const QModelIndex& proxyIndex) const override;
Q_INVOKABLE int mapToSource(int proxyRow) const;
Q_INVOKABLE QModelIndex mapFromSource(const QModelIndex& sourceIndex) const override;
Q_INVOKABLE int mapFromSource(int sourceRow) const;
void setSourceModel(QAbstractItemModel *sourceModel) override;
Q_SIGNALS:
void countChanged();
void delayedChanged();
void filterRoleNameChanged();
void filterPatternSyntaxChanged();
void filterPatternChanged();
void filterValueChanged();
void sortRoleNameChanged();
void ascendingSortOrderChanged();
protected:
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override;
protected Q_SLOTS:
void resetInternalData();
private Q_SLOTS:
void queueInvalidateFilter();
void invalidateFilter();
void queueInvalidate();
void invalidate();
void updateRoleNames();
void updateFilterRole();
void updateSortRole();
void updateRoles();
void initRoles();
void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles);
void queueInvalidateProxyRoles();
void invalidateProxyRoles();
private:
QVariantMap modelDataMap(const QModelIndex& modelIndex) const;
void onFilterAppended(Filter* filter) override;
void onFilterRemoved(Filter* filter) override;
void onFiltersCleared() override;
void onSorterAppended(Sorter* sorter) override;
void onSorterRemoved(Sorter* sorter) override;
void onSortersCleared() override;
void onProxyRoleAppended(ProxyRole *proxyRole) override;
void onProxyRoleRemoved(ProxyRole *proxyRole) override;
void onProxyRolesCleared() override;
bool m_delayed;
QString m_filterRoleName;
QVariant m_filterValue;
QString m_sortRoleName;
bool m_ascendingSortOrder = true;
bool m_completed = false;
QHash<int, QByteArray> m_roleNames;
QHash<int, QPair<ProxyRole*, QString>> m_proxyRoleMap;
QVector<int> m_proxyRoleNumbers;
bool m_invalidateFilterQueued = false;
bool m_invalidateQueued = false;
bool m_invalidateProxyRolesQueued = false;
};
}
#endif // QQMLSORTFILTERPROXYMODEL_H

View file

@ -1,146 +0,0 @@
#include "expressionsorter.h"
#include "qqmlsortfilterproxymodel.h"
#include <QtQml>
namespace qqsfpm {
/*!
\qmltype ExpressionSorter
\inherits Sorter
\inqmlmodule SortFilterProxyModel
\ingroup Sorters
\brief Sorts row with a custom javascript expression.
An ExpressionSorter is a \l Sorter allowing to implement custom sorting based on a javascript expression.
*/
/*!
\qmlproperty expression ExpressionSorter::expression
An expression to implement custom sorting. It must evaluate to a bool.
It has the same syntax has a \l {http://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html} {Property Binding}, except that it will be evaluated for each of the source model's rows.
Model data is accessible for both rows with the \c modelLeft, and \c modelRight properties:
\code
sorters: ExpressionSorter {
expression: {
return modelLeft.someRole < modelRight.someRole;
}
}
\endcode
The \c index of the row is also available through \c modelLeft and \c modelRight.
The expression should return \c true if the value of the left item is less than the value of the right item, otherwise returns false.
This expression is reevaluated for a row every time its model data changes.
When an external property (not \c index* or in \c model*) the expression depends on changes, the expression is reevaluated for every row of the source model.
To capture the properties the expression depends on, the expression is first executed with invalid data and each property access is detected by the QML engine.
This means that if a property is not accessed because of a conditional, it won't be captured and the expression won't be reevaluted when this property changes.
A workaround to this problem is to access all the properties the expressions depends unconditionally at the beggining of the expression.
*/
const QQmlScriptString& ExpressionSorter::expression() const
{
return m_scriptString;
}
void ExpressionSorter::setExpression(const QQmlScriptString& scriptString)
{
if (m_scriptString == scriptString)
return;
m_scriptString = scriptString;
updateExpression();
Q_EMIT expressionChanged();
invalidate();
}
void ExpressionSorter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
updateContext(proxyModel);
}
bool evaluateBoolExpression(QQmlExpression& expression)
{
QVariant variantResult = expression.evaluate();
if (expression.hasError()) {
qWarning() << expression.error();
return false;
}
if (variantResult.canConvert<bool>()) {
return variantResult.toBool();
} else {
qWarning("%s:%i:%i : Can't convert result to bool",
expression.sourceFile().toUtf8().data(),
expression.lineNumber(),
expression.columnNumber());
return false;
}
}
int ExpressionSorter::compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
if (!m_scriptString.isEmpty()) {
QVariantMap modelLeftMap, modelRightMap;
QHash<int, QByteArray> roles = proxyModel.roleNames();
QQmlContext context(qmlContext(this));
for (auto it = roles.cbegin(); it != roles.cend(); ++it) {
modelLeftMap.insert(it.value(), proxyModel.sourceData(sourceLeft, it.key()));
modelRightMap.insert(it.value(), proxyModel.sourceData(sourceRight, it.key()));
}
modelLeftMap.insert("index", sourceLeft.row());
modelRightMap.insert("index", sourceRight.row());
QQmlExpression expression(m_scriptString, &context);
context.setContextProperty("modelLeft", modelLeftMap);
context.setContextProperty("modelRight", modelRightMap);
if (evaluateBoolExpression(expression))
return -1;
context.setContextProperty("modelLeft", modelRightMap);
context.setContextProperty("modelRight", modelLeftMap);
if (evaluateBoolExpression(expression))
return 1;
}
return 0;
}
void ExpressionSorter::updateContext(const QQmlSortFilterProxyModel& proxyModel)
{
delete m_context;
m_context = new QQmlContext(qmlContext(this), this);
QVariantMap modelLeftMap, modelRightMap;
// what about roles changes ?
for (const QByteArray& roleName : proxyModel.roleNames().values()) {
modelLeftMap.insert(roleName, QVariant());
modelRightMap.insert(roleName, QVariant());
}
modelLeftMap.insert("index", -1);
modelRightMap.insert("index", -1);
m_context->setContextProperty("modelLeft", modelLeftMap);
m_context->setContextProperty("modelRight", modelRightMap);
updateExpression();
}
void ExpressionSorter::updateExpression()
{
if (!m_context)
return;
delete m_expression;
m_expression = new QQmlExpression(m_scriptString, m_context, 0, this);
connect(m_expression, &QQmlExpression::valueChanged, this, &ExpressionSorter::invalidate);
m_expression->setNotifyOnValueChanged(true);
m_expression->evaluate();
}
}

View file

@ -1,43 +0,0 @@
#ifndef EXPRESSIONSORTER_H
#define EXPRESSIONSORTER_H
#include "sorter.h"
#include <QQmlScriptString>
class QQmlExpression;
namespace qqsfpm {
class QQmlSortFilterProxyModel;
class ExpressionSorter : public Sorter
{
Q_OBJECT
Q_PROPERTY(QQmlScriptString expression READ expression WRITE setExpression NOTIFY expressionChanged)
public:
using Sorter::Sorter;
const QQmlScriptString& expression() const;
void setExpression(const QQmlScriptString& scriptString);
void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override;
Q_SIGNALS:
void expressionChanged();
protected:
int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const override;
private:
void updateContext(const QQmlSortFilterProxyModel& proxyModel);
void updateExpression();
QQmlScriptString m_scriptString;
QQmlExpression* m_expression = nullptr;
QQmlContext* m_context = nullptr;
};
}
#endif // EXPRESSIONSORTER_H

View file

@ -1,81 +0,0 @@
#include "filtersorter.h"
#include "filters/filter.h"
namespace qqsfpm {
/*!
\qmltype FilterSorter
\inherits Sorter
\inqmlmodule SortFilterProxyModel
\ingroup Sorters
\ingroup FilterContainer
\brief Sorts rows based on if they match filters.
A FilterSorter is a \l Sorter that orders row matching its filters before the rows not matching the filters.
In the following example, rows with their \c favorite role set to \c true will be ordered at the beginning :
\code
SortFilterProxyModel {
sourceModel: contactModel
sorters: FilterSorter {
ValueFilter { roleName: "favorite"; value: true }
}
}
\endcode
\sa FilterContainer
*/
/*!
\qmlproperty list<Filter> FilterSorter::filters
\default
This property holds the list of filters for this filter sorter.
If a row match all this FilterSorter's filters, it will be ordered before rows not matching all the filters.
\sa Filter, FilterContainer
*/
int FilterSorter::compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel &proxyModel) const
{
bool leftIsAccepted = indexIsAccepted(sourceLeft, proxyModel);
bool rightIsAccepted = indexIsAccepted(sourceRight, proxyModel);
if (leftIsAccepted == rightIsAccepted)
return 0;
return leftIsAccepted ? -1 : 1;
}
void FilterSorter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
for (Filter* filter : m_filters)
filter->proxyModelCompleted(proxyModel);
}
void FilterSorter::onFilterAppended(Filter* filter)
{
connect(filter, &Filter::invalidated, this, &FilterSorter::invalidate);
invalidate();
}
void FilterSorter::onFilterRemoved(Filter* filter)
{
disconnect(filter, &Filter::invalidated, this, &FilterSorter::invalidate);
invalidate();
}
void FilterSorter::onFiltersCleared()
{
invalidate();
}
bool FilterSorter::indexIsAccepted(const QModelIndex& sourceIndex, const QQmlSortFilterProxyModel& proxyModel) const
{
return std::all_of(m_filters.begin(), m_filters.end(),
[&] (Filter* filter) {
return filter->filterAcceptsRow(sourceIndex, proxyModel);
}
);
}
}

View file

@ -1,33 +0,0 @@
#ifndef FILTERSORTER_H
#define FILTERSORTER_H
#include "sorter.h"
#include "filters/filtercontainer.h"
namespace qqsfpm {
class FilterSorter : public Sorter, public FilterContainer
{
Q_OBJECT
Q_INTERFACES(qqsfpm::FilterContainer)
Q_PROPERTY(QQmlListProperty<qqsfpm::Filter> filters READ filtersListProperty)
Q_CLASSINFO("DefaultProperty", "filters")
public:
using Sorter::Sorter;
protected:
int compare(const QModelIndex &sourceLeft, const QModelIndex &sourceRight, const QQmlSortFilterProxyModel &proxyModel) const override;
private:
void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel) override;
void onFilterAppended(Filter *filter) override;
void onFilterRemoved(Filter *filter) override;
void onFiltersCleared() override;
bool indexIsAccepted(const QModelIndex &sourceIndex, const QQmlSortFilterProxyModel &proxyModel) const;
};
}
#endif // FILTERSORTER_H

View file

@ -1,69 +0,0 @@
#include "rolesorter.h"
#include "qqmlsortfilterproxymodel.h"
namespace qqsfpm {
/*!
\qmltype RoleSorter
\inherits Sorter
\inqmlmodule SortFilterProxyModel
\ingroup Sorters
\brief Sorts rows based on a source model role.
A RoleSorter is a simple \l Sorter that sorts rows based on a source model role.
In the following example, rows with be sorted by their \c lastName role :
\code
SortFilterProxyModel {
sourceModel: contactModel
sorters: RoleSorter { roleName: "lastName" }
}
\endcode
*/
/*!
\qmlproperty string RoleSorter::roleName
This property holds the role name that the sorter is using to query the source model's data when sorting items.
*/
const QString& RoleSorter::roleName() const
{
return m_roleName;
}
void RoleSorter::setRoleName(const QString& roleName)
{
if (m_roleName == roleName)
return;
m_roleName = roleName;
Q_EMIT roleNameChanged();
invalidate();
}
QPair<QVariant, QVariant> RoleSorter::sourceData(const QModelIndex &sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
QPair<QVariant, QVariant> pair;
int role = proxyModel.roleForName(m_roleName);
if (role == -1)
return pair;
pair.first = proxyModel.sourceData(sourceLeft, role);
pair.second = proxyModel.sourceData(sourceRight, role);
return pair;
}
int RoleSorter::compare(const QModelIndex &sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
QPair<QVariant, QVariant> pair = sourceData(sourceLeft, sourceRight, proxyModel);
QVariant leftValue = pair.first;
QVariant rightValue = pair.second;
if (leftValue < rightValue)
return -1;
if (leftValue > rightValue)
return 1;
return 0;
}
}

View file

@ -1,32 +0,0 @@
#ifndef ROLESORTER_H
#define ROLESORTER_H
#include "sorter.h"
namespace qqsfpm {
class RoleSorter : public Sorter
{
Q_OBJECT
Q_PROPERTY(QString roleName READ roleName WRITE setRoleName NOTIFY roleNameChanged)
public:
using Sorter::Sorter;
const QString& roleName() const;
void setRoleName(const QString& roleName);
Q_SIGNALS:
void roleNameChanged();
protected:
QPair<QVariant, QVariant> sourceData(const QModelIndex &sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const;
int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const override;
private:
QString m_roleName;
};
}
#endif // ROLESORTER_H

View file

@ -1,142 +0,0 @@
#include "sorter.h"
#include "qqmlsortfilterproxymodel.h"
namespace qqsfpm {
/*!
\qmltype Sorter
\qmlabstract
\inqmlmodule SortFilterProxyModel
\ingroup Sorters
\brief Base type for the \l SortFilterProxyModel sorters.
The Sorter type cannot be used directly in a QML file.
It exists to provide a set of common properties and methods,
available across all the other sorters types that inherit from it.
Attempting to use the Sorter type directly will result in an error.
*/
Sorter::Sorter(QObject *parent) : QObject(parent)
{
}
Sorter::~Sorter() = default;
/*!
\qmlproperty bool Sorter::enabled
This property holds whether the sorter is enabled.
A disabled sorter will not change the order of the rows.
By default, sorters are enabled.
*/
bool Sorter::enabled() const
{
return m_enabled;
}
void Sorter::setEnabled(bool enabled)
{
if (m_enabled == enabled)
return;
m_enabled = enabled;
Q_EMIT enabledChanged();
Q_EMIT invalidated();
}
bool Sorter::ascendingOrder() const
{
return sortOrder() == Qt::AscendingOrder;
}
void Sorter::setAscendingOrder(bool ascendingOrder)
{
setSortOrder(ascendingOrder ? Qt::AscendingOrder : Qt::DescendingOrder);
}
/*!
\qmlproperty Qt::SortOrder Sorter::sortOrder
This property holds the sort order of this sorter.
\value Qt.AscendingOrder The items are sorted ascending e.g. starts with 'AAA' ends with 'ZZZ' in Latin-1 locales
\value Qt.DescendingOrder The items are sorted descending e.g. starts with 'ZZZ' ends with 'AAA' in Latin-1 locales
By default, sorting is in ascending order.
*/
Qt::SortOrder Sorter::sortOrder() const
{
return m_sortOrder;
}
void Sorter::setSortOrder(Qt::SortOrder sortOrder)
{
if (m_sortOrder == sortOrder)
return;
m_sortOrder = sortOrder;
Q_EMIT sortOrderChanged();
invalidate();
}
/*!
\qmlproperty int Sorter::priority
This property holds the sort priority of this sorter.
Sorters with a higher priority are applied first.
In case of equal priority, Sorters are ordered by their insertion order.
By default, the priority is 0.
*/
int Sorter::priority() const
{
return m_priority;
}
void Sorter::setPriority(int priority)
{
if (m_priority == priority)
return;
m_priority = priority;
Q_EMIT priorityChanged();
invalidate();
}
int Sorter::compareRows(const QModelIndex &source_left, const QModelIndex &source_right, const QQmlSortFilterProxyModel& proxyModel) const
{
int comparison = compare(source_left, source_right, proxyModel);
return (m_sortOrder == Qt::AscendingOrder) ? comparison : -comparison;
}
int Sorter::compare(const QModelIndex &sourceLeft, const QModelIndex &sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
if (lessThan(sourceLeft, sourceRight, proxyModel))
return -1;
if (lessThan(sourceRight, sourceLeft, proxyModel))
return 1;
return 0;
}
void Sorter::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
{
Q_UNUSED(proxyModel)
}
bool Sorter::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
Q_UNUSED(sourceLeft)
Q_UNUSED(sourceRight)
Q_UNUSED(proxyModel)
return false;
}
void Sorter::invalidate()
{
if (m_enabled)
Q_EMIT invalidated();
}
}

View file

@ -1,58 +0,0 @@
#ifndef SORTER_H
#define SORTER_H
#include <QObject>
namespace qqsfpm {
class QQmlSortFilterProxyModel;
class Sorter : public QObject
{
Q_OBJECT
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
Q_PROPERTY(bool ascendingOrder READ ascendingOrder WRITE setAscendingOrder NOTIFY sortOrderChanged)
Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder NOTIFY sortOrderChanged)
Q_PROPERTY(int priority READ priority WRITE setPriority NOTIFY priorityChanged)
public:
Sorter(QObject* parent = nullptr);
virtual ~Sorter() = 0;
bool enabled() const;
void setEnabled(bool enabled);
bool ascendingOrder() const;
void setAscendingOrder(bool ascendingOrder);
Qt::SortOrder sortOrder() const;
void setSortOrder(Qt::SortOrder sortOrder);
int priority() const;
void setPriority(int priority);
int compareRows(const QModelIndex& source_left, const QModelIndex& source_right, const QQmlSortFilterProxyModel& proxyModel) const;
virtual void proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel);
Q_SIGNALS:
void enabledChanged();
void sortOrderChanged();
void priorityChanged();
void invalidated();
protected:
virtual int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const;
virtual bool lessThan(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const;
void invalidate();
private:
bool m_enabled = true;
Qt::SortOrder m_sortOrder = Qt::AscendingOrder;
int m_priority = 0;
};
}
#endif // SORTER_H

View file

@ -1,126 +0,0 @@
#include "sortercontainer.h"
#include "sorter.h"
#include <QtQml>
namespace qqsfpm {
/*!
\qmltype SorterContainer
\qmlabstract
\inqmlmodule SortFilterProxyModel
\ingroup SorterAttached
\brief Abstract interface for types containing \l {Sorter}{Sorters}.
\section2 Types implementing this interface:
\annotatedlist SorterContainer
*/
QList<Sorter*> SorterContainer::sorters() const
{
return m_sorters;
}
void SorterContainer::appendSorter(Sorter* sorter)
{
m_sorters.append(sorter);
onSorterAppended(sorter);
}
void SorterContainer::removeSorter(Sorter *sorter)
{
m_sorters.removeOne(sorter);
onSorterRemoved(sorter);
}
void SorterContainer::clearSorters()
{
m_sorters.clear();
onSortersCleared();
}
QQmlListProperty<Sorter> SorterContainer::sortersListProperty()
{
return QQmlListProperty<Sorter>(reinterpret_cast<QObject*>(this), &m_sorters,
&SorterContainer::append_sorter,
&SorterContainer::count_sorter,
&SorterContainer::at_sorter,
&SorterContainer::clear_sorters);
}
void SorterContainer::append_sorter(QQmlListProperty<Sorter>* list, Sorter* sorter)
{
if (!sorter)
return;
SorterContainer* that = reinterpret_cast<SorterContainer*>(list->object);
that->appendSorter(sorter);
}
int SorterContainer::count_sorter(QQmlListProperty<Sorter>* list)
{
QList<Sorter*>* sorters = static_cast<QList<Sorter*>*>(list->data);
return sorters->count();
}
Sorter* SorterContainer::at_sorter(QQmlListProperty<Sorter>* list, int index)
{
QList<Sorter*>* sorters = static_cast<QList<Sorter*>*>(list->data);
return sorters->at(index);
}
void SorterContainer::clear_sorters(QQmlListProperty<Sorter> *list)
{
SorterContainer* that = reinterpret_cast<SorterContainer*>(list->object);
that->clearSorters();
}
SorterContainerAttached::SorterContainerAttached(QObject* object) : QObject(object),
m_sorter(qobject_cast<Sorter*>(object))
{
if (!m_sorter)
qmlWarning(object) << "SorterContainerAttached must be attached to a Sorter";
}
SorterContainerAttached::~SorterContainerAttached()
{
if (m_sorter && m_container) {
SorterContainer* container = qobject_cast<SorterContainer*>(m_container.data());
container->removeSorter(m_sorter);
}
}
/*!
\qmlattachedproperty bool SorterContainer::container
This attached property allows you to include in a \l SorterContainer a \l Sorter that
has been instantiated outside of the \l SorterContainer, for example in an Instantiator.
*/
QObject* SorterContainerAttached::container() const
{
return m_container;
}
void SorterContainerAttached::setContainer(QObject* object)
{
if (m_container == object)
return;
SorterContainer* container = qobject_cast<SorterContainer*>(object);
if (object && !container)
qmlWarning(parent()) << "container must inherits from SorterContainer, " << object->metaObject()->className() << " provided";
if (m_container && m_sorter)
qobject_cast<SorterContainer*>(m_container.data())->removeSorter(m_sorter);
m_container = container ? object : nullptr;
if (container && m_sorter)
container->appendSorter(m_sorter);
Q_EMIT containerChanged();
}
SorterContainerAttached* SorterContainerAttached::qmlAttachedProperties(QObject* object)
{
return new SorterContainerAttached(object);
}
}

View file

@ -1,68 +0,0 @@
#ifndef SORTERSSORTERCONTAINER_H
#define SORTERSSORTERCONTAINER_H
#include <QList>
#include <QQmlListProperty>
#include <qqml.h>
#include <QPointer>
namespace qqsfpm {
class Sorter;
class QQmlSortFilterProxyModel;
class SorterContainer {
public:
virtual ~SorterContainer() = default;
QList<Sorter*> sorters() const;
void appendSorter(Sorter* sorter);
void removeSorter(Sorter* sorter);
void clearSorters();
QQmlListProperty<Sorter> sortersListProperty();
protected:
QList<Sorter*> m_sorters;
private:
virtual void onSorterAppended(Sorter* sorter) = 0;
virtual void onSorterRemoved(Sorter* sorter) = 0;
virtual void onSortersCleared() = 0;
static void append_sorter(QQmlListProperty<Sorter>* list, Sorter* sorter);
static int count_sorter(QQmlListProperty<Sorter>* list);
static Sorter* at_sorter(QQmlListProperty<Sorter>* list, int index);
static void clear_sorters(QQmlListProperty<Sorter>* list);
};
class SorterContainerAttached : public QObject
{
Q_OBJECT
Q_PROPERTY(QObject* container READ container WRITE setContainer NOTIFY containerChanged)
public:
SorterContainerAttached(QObject* object);
~SorterContainerAttached();
QObject* container() const;
void setContainer(QObject* object);
static SorterContainerAttached* qmlAttachedProperties(QObject* object);
Q_SIGNALS:
void containerChanged();
private:
QPointer<QObject> m_container = nullptr;
Sorter* m_sorter = nullptr;
};
}
#define SorterContainer_iid "fr.grecko.SortFilterProxyModel.SorterContainer"
Q_DECLARE_INTERFACE(qqsfpm::SorterContainer, SorterContainer_iid)
QML_DECLARE_TYPEINFO(qqsfpm::SorterContainerAttached, QML_HAS_ATTACHED_PROPERTIES)
#endif // SORTERSSORTERCONTAINER_H

View file

@ -1,23 +0,0 @@
#include "sorter.h"
#include "rolesorter.h"
#include "stringsorter.h"
#include "filtersorter.h"
#include "expressionsorter.h"
#include "sortercontainer.h"
#include <QQmlEngine>
#include <QCoreApplication>
namespace qqsfpm {
void registerSorterTypes() {
qmlRegisterUncreatableType<Sorter>("SortFilterProxyModel", 0, 2, "Sorter", "Sorter is an abstract class");
qmlRegisterType<RoleSorter>("SortFilterProxyModel", 0, 2, "RoleSorter");
qmlRegisterType<StringSorter>("SortFilterProxyModel", 0, 2, "StringSorter");
qmlRegisterType<FilterSorter>("SortFilterProxyModel", 0, 2, "FilterSorter");
qmlRegisterType<ExpressionSorter>("SortFilterProxyModel", 0, 2, "ExpressionSorter");
qmlRegisterUncreatableType<SorterContainerAttached>("SortFilterProxyModel", 0, 2, "SorterContainer", "SorterContainer can only be used as an attaching type");
}
Q_COREAPP_STARTUP_FUNCTION(registerSorterTypes)
}

View file

@ -1,117 +0,0 @@
#include "stringsorter.h"
namespace qqsfpm {
/*!
\qmltype StringSorter
\inherits RoleSorter
\inqmlmodule SortFilterProxyModel
\ingroup Sorters
\brief Sorts rows based on a source model string role.
\l StringSorter is a specialized \l RoleSorter that sorts rows based on a source model string role.
\l StringSorter compares strings according to a localized collation algorithm.
In the following example, rows with be sorted by their \c lastName role :
\code
SortFilterProxyModel {
sourceModel: contactModel
sorters: StringSorter { roleName: "lastName" }
}
\endcode
*/
/*!
\qmlproperty Qt.CaseSensitivity StringSorter::caseSensitivity
This property holds the case sensitivity of the sorter.
*/
Qt::CaseSensitivity StringSorter::caseSensitivity() const
{
return m_collator.caseSensitivity();
}
void StringSorter::setCaseSensitivity(Qt::CaseSensitivity caseSensitivity)
{
if (m_collator.caseSensitivity() == caseSensitivity)
return;
m_collator.setCaseSensitivity(caseSensitivity);
Q_EMIT caseSensitivityChanged();
invalidate();
}
/*!
\qmlproperty bool StringSorter::ignorePunctation
This property holds whether the sorter ignores punctation.
if \c ignorePunctuation is \c true, punctuation characters and symbols are ignored when determining sort order.
\note This property is not currently supported on Apple platforms or if Qt is configured to not use ICU on Linux.
*/
bool StringSorter::ignorePunctation() const
{
return m_collator.ignorePunctuation();
}
void StringSorter::setIgnorePunctation(bool ignorePunctation)
{
if (m_collator.ignorePunctuation() == ignorePunctation)
return;
m_collator.setIgnorePunctuation(ignorePunctation);
Q_EMIT ignorePunctationChanged();
invalidate();
}
/*!
\qmlproperty Locale StringSorter::locale
This property holds the locale of the sorter.
*/
QLocale StringSorter::locale() const
{
return m_collator.locale();
}
void StringSorter::setLocale(const QLocale &locale)
{
if (m_collator.locale() == locale)
return;
m_collator.setLocale(locale);
Q_EMIT localeChanged();
invalidate();
}
/*!
\qmlproperty bool StringSorter::numericMode
This property holds whether the numeric mode of the sorter is enabled.
This will enable proper sorting of numeric digits, so that e.g. 100 sorts after 99.
By default this mode is off.
*/
bool StringSorter::numericMode() const
{
return m_collator.numericMode();
}
void StringSorter::setNumericMode(bool numericMode)
{
if (m_collator.numericMode() == numericMode)
return;
m_collator.setNumericMode(numericMode);
Q_EMIT numericModeChanged();
invalidate();
}
int StringSorter::compare(const QModelIndex &sourceLeft, const QModelIndex &sourceRight, const QQmlSortFilterProxyModel& proxyModel) const
{
QPair<QVariant, QVariant> pair = sourceData(sourceLeft, sourceRight, proxyModel);
QString leftValue = pair.first.toString();
QString rightValue = pair.second.toString();
return m_collator.compare(leftValue, rightValue);
}
}

View file

@ -1,47 +0,0 @@
#ifndef STRINGSORTER_H
#define STRINGSORTER_H
#include "rolesorter.h"
#include <QCollator>
namespace qqsfpm {
class StringSorter : public RoleSorter
{
Q_OBJECT
Q_PROPERTY(Qt::CaseSensitivity caseSensitivity READ caseSensitivity WRITE setCaseSensitivity NOTIFY caseSensitivityChanged)
Q_PROPERTY(bool ignorePunctation READ ignorePunctation WRITE setIgnorePunctation NOTIFY ignorePunctationChanged)
Q_PROPERTY(QLocale locale READ locale WRITE setLocale NOTIFY localeChanged)
Q_PROPERTY(bool numericMode READ numericMode WRITE setNumericMode NOTIFY numericModeChanged)
public:
using RoleSorter::RoleSorter;
Qt::CaseSensitivity caseSensitivity() const;
void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity);
bool ignorePunctation() const;
void setIgnorePunctation(bool ignorePunctation);
QLocale locale() const;
void setLocale(const QLocale& locale);
bool numericMode() const;
void setNumericMode(bool numericMode);
Q_SIGNALS:
void caseSensitivityChanged();
void ignorePunctationChanged();
void localeChanged();
void numericModeChanged();
protected:
int compare(const QModelIndex& sourceLeft, const QModelIndex& sourceRight, const QQmlSortFilterProxyModel& proxyModel) const override;
private:
QCollator m_collator;
};
}
#endif // STRINGSORTER_H

View file

@ -1,13 +0,0 @@
project = SortFilterProxyModel
sourcedirs = .
sources.fileextensions = "*.cpp *.qdoc *.qml"
headers.fileextensions = "*.h"
outputdir = docs/
HTML.templatedir = .
HTML.stylesheets = "offline.css"
HTML.headerstyles = " <link rel=\"stylesheet\" type=\"text/css\" href=\"style/offline.css\" />\n"

View file

@ -0,0 +1,4 @@
include_directories(${CMAKE_CURRENT_LIST_DIR})
set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/qrcodegen.hpp)
set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/qrcodegen.cpp)

@ -1 +1 @@
Subproject commit f197cdb935b0cfd9881fdc6860874cb8379d1238
Subproject commit c6f0b66318f8da6917fb4681103f7303b1836194

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.amnezia.AmneziaVPN</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)group.org.amnezia.AmneziaVPN</string>
</array>
</dict>
</plist>

611
client/CMakeLists.txt Normal file
View file

@ -0,0 +1,611 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT} VERSION 2.1.2)
set(BUILD_ID 1)
SET(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.17)
cmake_policy(SET CMP0099 OLD)
endif()
if(CMAKE_XCODE_BUILD_SYSTEM VERSION_GREATER_EQUAL 12)
cmake_policy(SET CMP0114 NEW)
endif()
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "Autogen")
find_package(Qt6 REQUIRED COMPONENTS
Widgets Core Gui Network Xml
RemoteObjects Quick Svg QuickControls2
Core5Compat Concurrent
)
set(LIBS ${LIBS}
Qt6::Widgets Qt6::Core Qt6::Gui
Qt6::Network Qt6::Xml Qt6::RemoteObjects
Qt6::Quick Qt6::Svg Qt6::QuickControls2
Qt6::Core5Compat Qt6::Concurrent
)
qt_standard_project_setup()
if(IOS)
execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/scripts/openvpn.sh args
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
endif()
set(IS_CI ${CI})
if(IS_CI)
message("Detected CI env")
find_program(CCACHE "ccache")
if(CCACHE)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE}")
endif()
endif()
include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
include_directories(
${CMAKE_CURRENT_LIST_DIR}/../ipc
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/migrations.h
${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc.h
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.h
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.h
${CMAKE_CURRENT_LIST_DIR}/core/defs.h
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h
${CMAKE_CURRENT_LIST_DIR}/core/servercontroller.h
${CMAKE_CURRENT_LIST_DIR}/platforms/linux/leakdetector.h
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
${CMAKE_CURRENT_LIST_DIR}/ui/pages.h
${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h
${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.h
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
)
if(NOT IOS)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.h
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
)
endif()
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/migrations.cpp
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/core/servercontroller.cpp
${CMAKE_CURRENT_LIST_DIR}/platforms/linux/leakdetector.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
)
if(NOT IOS)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.cpp
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
)
endif()
file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.h)
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.h)
file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.cpp)
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h)
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp)
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h)
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp)
set(HEADERS ${HEADERS}
${COMMON_FILES_H}
${PAGE_LOGIC_H}
${CONFIGURATORS_H}
${UI_MODELS_H}
)
set(SOURCES ${SOURCES}
${COMMON_FILES_CPP}
${PAGE_LOGIC_CPP}
${CONFIGURATORS_CPP}
${UI_MODELS_CPP}
)
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
if(WIN32)
add_compile_definitions(MVPN_WINDOWS)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.cpp
)
set(RESOURCES ${RESOURCES}
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc
)
set(LIBS ${LIBS}
user32
rasapi32
shlwapi
iphlpapi
ws2_32
gdi32
)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
message("Windows x86_64 build")
link_directories(${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/windows/x86_64)
set(LIBS ${LIBS}
libssl
libcrypto
)
else()
message("Windows x86 build")
link_directories(${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/windows/x86)
set(LIBS ${LIBS}
libssl
libcrypto
)
endif()
endif()
if(APPLE)
if(NOT BUILD_OSX_APP_IDENTIFIER)
set(BUILD_OSX_APP_IDENTIFIER org.amnezia.AmneziaVPN CACHE STRING "OSX Application identifier")
endif()
if(NOT BUILD_IOS_APP_IDENTIFIER)
set(BUILD_IOS_APP_IDENTIFIER org.amnezia.AmneziaVPN CACHE STRING "iOS Application identifier")
endif()
if(NOT BUILD_IOS_GROUP_IDENTIFIER)
set(BUILD_IOS_GROUP_IDENTIFIER group.org.amnezia.AmneziaVPN.Guardian CACHE STRING "iOS Group identifier")
endif()
if(NOT BUILD_VPN_DEVELOPMENT_TEAM)
set(BUILD_VPN_DEVELOPMENT_TEAM X7UJ388FXK CACHE STRING "Amnezia VPN Development Team")
endif()
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../build)
if(NOT IOS)
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
message("MAC build")
set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/ui/macos_util.h)
set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/ui/macos_util.mm)
# set(CMAKE_OSX_DEPLOYMENT_TARGET 10.14)
add_compile_definitions(MVPN_MACOS)
# ICON = $$PWD/images/app.icns
find_library(LIB_LIBCRYPTO NAMES "libcrypto.a"
PATHS ${PROJECT_SOURCE_DIR}/3rd/OpenSSL/lib/macos/x86_64/ NO_DEFAULT_PATH)
find_library(LIB_SSL NAMES "libssl.a"
PATHS ${PROJECT_SOURCE_DIR}/3rd/OpenSSL/lib/macos/x86_64/ NO_DEFAULT_PATH)
find_library(FW_COCOA Cocoa)
find_library(FW_APPLICATIONSERVICES ApplicationServices)
find_library(FW_CORESERVICES CoreServices)
find_library(FW_FOUNDATION Foundation)
find_library(FW_APPKIT AppKit)
find_library(FW_SECURITY Security)
set(LIBS ${LIBS}
${FW_COCOA} ${FW_APPLICATIONSERVICES}
${FW_FOUNDATION} ${FW_APPKIT}
${FW_SECURITY} ${FW_CORESERVICES}
${LIB_LIBCRYPTO} ${LIB_SSL}
)
endif()
endif()
if(LINUX AND NOT ANDROID)
add_compile_definitions(MVPN_LINUX)
set(OPENSSL_USE_STATIC_LIBS TRUE)
find_package(OpenSSL REQUIRED)
set(LIBS ${LIBS}
OpenSSL::Crypto
OpenSSL::SSL
)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.h
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.h
${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.h
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.cpp
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
)
endif()
if(ANDROID)
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
# We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api
set(LIBS ${LIBS} Qt6::CorePrivate)
add_compile_definitions(MVPN_ANDROID)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/android)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_controller.h
${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_notificationhandler.h
${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidutils.h
${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidvpnactivity.h
${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_controller.cpp
${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_notificationhandler.cpp
${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidutils.cpp
${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidvpnactivity.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.cpp
)
endif()
if(IOS)
message("Client iOS build")
find_package(Qt6 REQUIRED COMPONENTS ShaderTools)
set(LIBS ${LIBS} Qt6::ShaderTools)
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
find_library(FW_UIKIT UIKit)
find_library(FW_AVFOUNDATION AVFoundation)
find_library(FW_FOUNDATION Foundation)
find_library(FW_STOREKIT StoreKit)
find_library(FW_USERNOTIFICATIONS UserNotifications)
set(LIBS ${LIBS}
${FW_AUTHENTICATIONSERVICES} ${FW_UIKIT}
${FW_AVFOUNDATION} ${FW_FOUNDATION} ${FW_STOREKIT}
${FW_USERNOTIFICATIONS}
)
add_compile_definitions(MVPN_IOS)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/protocols/ios_vpnprotocol.h
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/json.h
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/bigint.h
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/bigintipv6addr.h
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/ipaddress.h
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/ipaddressrange.h
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/protocols/ios_vpnprotocol.mm
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/iosnotificationhandler.mm
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/json.cpp
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/ipaddress.cpp
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/ipaddressrange.cpp
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QtAppDelegate.mm
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.mm
)
endif()
if(CMAKE_OSX_SYSROOT STREQUAL "iphoneos")
message("Building for iPhone OS")
set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0)
endif()
qt_add_executable(${PROJECT} ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC})
qt_add_translations(${PROJECT} TS_FILES
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts)
if(APPLE AND NOT IOS)
set_target_properties(AmneziaVPN PROPERTIES
MACOSX_BUNDLE TRUE
)
endif()
if(IOS)
enable_language(OBJC)
enable_language(OBJCXX)
enable_language(Swift)
#disbale in cicd
include(cmake/osxtools.cmake)
# set(CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY TRUE)
set_target_properties(${PROJECT} PROPERTIES XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
set_target_properties(${PROJECT} PROPERTIES XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon")
set_target_properties(${PROJECT} PROPERTIES XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION)
set(CMAKE_XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks")
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_LIST_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos)
#need to change for debug and relase
set_target_properties(${PROJECT}
PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.amnezia.${PROJECT}"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1"
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
)
set_target_properties(${PROJECT}
PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.amnezia.${PROJECT}"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1"
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
)
set(LIBS ${LIBS}
${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/ios/iphone/libcrypto.a
${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/ios/iphone/libssl.a
)
target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_LIST_DIR}/platforms/ios/WireGuard-Bridging-Header.h"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
XCODE_ATTRIBUTE_SWIFT_OPTIMIZATION_LEVEL "-Onone"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
)
set_target_properties(${PROJECT} PROPERTIES
OUTPUT_NAME "AmneziaVPN"
MACOSX_BUNDLE ON
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_LIST_DIR}/ios/app/Info.plist.in
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_VERSION "${BUILD_ID}"
MACOSX_BUNDLE_COPYRIGHT "MPL-2.0"
MACOSX_BUNDLE_GUI_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
MACOSX_BUNDLE_LONG_VERSION_STRING "${CMAKE_PROJECT_VERSION}-${BUILD_ID}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_LIST_DIR}/ios/app/main.entitlements"
XCODE_ATTRIBUTE_MARKETING_VERSION "${CMAKE_PROJECT_VERSION}"
XCODE_GENERATE_SCHEME TRUE
MACOSX_BUNDLE_ICON_FILE "AppIcon"
)
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_compile_options(${PROJECT} PRIVATE
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
)
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/Keychain.swift
${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/IPAddressRange.swift
${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/InterfaceConfiguration.swift
${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/Model/NETunnelProviderProtocol+Extension.swift
${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/TunnelConfiguration.swift
${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift
${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/Endpoint.swift
${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/Model/String+ArrayConversion.swift
${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PeerConfiguration.swift
${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/DNSServer.swift
${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardApp/LocalizationHelper.swift
${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/FileManager+Extension.swift
${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKitC/x25519.c
${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PrivateKey.swift
)
target_sources(${PROJECT} PRIVATE
platforms/ios/iosvpnprotocol.swift
platforms/ios/ioslogger.swift
)
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_LIST_DIR}/ios/app/launch.png
${CMAKE_CURRENT_LIST_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
)
set_source_files_properties(
${CMAKE_CURRENT_LIST_DIR}/ios/app/launch.png
${CMAKE_CURRENT_LIST_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources"
)
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_LIST_DIR}/ios/Media.xcassets
)
set_source_files_properties(
${CMAKE_CURRENT_LIST_DIR}/ios/Media.xcassets
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources"
)
add_subdirectory(ios/networkextension)
add_dependencies(${PROJECT} networkextension)
set_target_properties(${PROJECT} PROPERTIES XCODE_EMBED_APP_EXTENSIONS networkextension)
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
"${CMAKE_CURRENT_LIST_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework"
)
set_target_properties(${PROJECT} PROPERTIES XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON)
set_target_properties(${PROJECT} PROPERTIES XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION)
set_target_properties("networkextension"
PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.amnezia.${PROJECT}.network-extension"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1"
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
)
set_target_properties("networkextension"
PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "org.amnezia.${PROJECT}.network-extension"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1"
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
)
set_target_properties (${PROJECT} PROPERTIES XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual)
set_target_properties(${PROJECT} PROPERTIES XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN")
set_target_properties(${PROJECT} PROPERTIES XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN")
set_target_properties("networkextension" PROPERTIES XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual)
set_target_properties("networkextension" PROPERTIES XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN.network-extension")
set_target_properties("networkextension" PROPERTIES XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN.network-extension")
endif()
if(ANDROID)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_LIST_DIR}/android/AndroidManifest.xml
${CMAKE_CURRENT_LIST_DIR}/android/build.gradle
${CMAKE_CURRENT_LIST_DIR}/android/gradle/wrapper/gradle-wrapper.jar
${CMAKE_CURRENT_LIST_DIR}/android/gradle/wrapper/gradle-wrapper.properties
${CMAKE_CURRENT_LIST_DIR}/android/gradlew
${CMAKE_CURRENT_LIST_DIR}/android/gradlew.bat
${CMAKE_CURRENT_LIST_DIR}/android/gradle.properties
${CMAKE_CURRENT_LIST_DIR}/android/res/values/libs.xml
${CMAKE_CURRENT_LIST_DIR}/android/res/xml/fileprovider.xml
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/AuthHelper.java
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/IPCContract.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/NotificationUtil.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/OpenVPNThreadv3.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/Prefs.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VPNLogger.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VPNService.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/VPNServiceBinder.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/AmneziaApp.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/PackageManagerHelper.java
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNActivity.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNApplication.java
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNClientBinder.kt
${CMAKE_CURRENT_LIST_DIR}/android/src/org/amnezia/vpn/qt/VPNPermissionHelper.kt
${CMAKE_CURRENT_BINARY_DIR}
)
set_property(TARGET ${PROJECT} PROPERTY
QT_ANDROID_PACKAGE_SOURCE_DIR
${CMAKE_CURRENT_LIST_DIR}/android
)
foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
if(CMAKE_ANDROID_ARCH_ABI STREQUAL ${abi})
set(LIBS ${LIBS}
${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/android/${abi}/libcrypto.a
${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/android/${abi}/libssl.a
)
endif()
set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_EXTRA_LIBS
${CMAKE_CURRENT_LIST_DIR}/android/lib/wireguard/${abi}/libwg.so
${CMAKE_CURRENT_LIST_DIR}/android/lib/wireguard/${abi}/libwg-go.so
${CMAKE_CURRENT_LIST_DIR}/android/lib/wireguard/${abi}/libwg-quick.so
${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libjbcrypto.so
${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libopenvpn.so
${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libopvpnutil.so
${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libovpn3.so
${CMAKE_CURRENT_LIST_DIR}/android/lib/openvpn/${abi}/libovpnexec.so
)
endforeach()
endif()
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
endif()
# deploy artifacts required to run the application to the debug build folder
if(WIN32)
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
set(DEPLOY_ARTIFACT_PATH "windows/x64")
else()
set(DEPLOY_ARTIFACT_PATH "windows/x32")
endif()
elseif(LINUX)
set(DEPLOY_ARTIFACT_PATH "linux/client")
elseif(APPLE AND NOT IOS)
set(DEPLOY_ARTIFACT_PATH "macos")
endif()
if(NOT IOS)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
${CMAKE_SOURCE_DIR}/deploy/data/${DEPLOY_ARTIFACT_PATH}
$<TARGET_FILE_DIR:${PROJECT}>
COMMAND_EXPAND_LISTS
)
endif()
if(WIN32)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy,true>
$<TARGET_FILE_DIR:${PROJECT}>/../service/wireguard-service/wireguard-service.exe
$<TARGET_FILE_DIR:${PROJECT}>/wireguard/wireguard-service.exe
COMMAND_EXPAND_LISTS
)
endif()
if(IOS)
#include(cmake/ios-arch-fixup.cmake)
endif()

View file

@ -7,8 +7,9 @@
#include "core/servercontroller.h"
#include "debug.h"
#include "logger.h"
#include "defines.h"
#include <QQuickStyle>
#include "platforms/ios/QRCodeReaderBase.h"
@ -98,7 +99,7 @@ void AmneziaApplication::init()
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Debug::Instance());
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
m_uiLogic->registerPagesLogic();
#if defined(Q_OS_IOS)
@ -112,7 +113,7 @@ void AmneziaApplication::init()
}
if (m_settings->isSaveLogs()) {
if (!Debug::init()) {
if (!Logger::init()) {
qWarning() << "Initialization of debug subsystem failed";
}
}
@ -168,6 +169,8 @@ void AmneziaApplication::registerTypes()
void AmneziaApplication::loadFonts()
{
QQuickStyle::setStyle("Basic");
QFontDatabase::addApplicationFont(":/fonts/Lato-Black.ttf");
QFontDatabase::addApplicationFont(":/fonts/Lato-BlackItalic.ttf");
QFontDatabase::addApplicationFont(":/fonts/Lato-Bold.ttf");
@ -203,7 +206,7 @@ bool AmneziaApplication::parseCommands()
m_parser.process(*this);
if (m_parser.isSet(c_cleanup)) {
Debug::cleanUp();
Logger::cleanUp();
QTimer::singleShot(100, this, [this]{
quit();
});

View file

@ -21,13 +21,20 @@
Remove the comment if you do not require these default features. -->
<!-- %%INSERT_FEATURES -->
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:anyDensity="true"
android:smallScreens="true"/>
<application
android:name=".qt.AmneziaApp"
android:hardwareAccelerated="true"
android:label="-- %%INSERT_APP_NAME%% --"
android:extractNativeLibs="true"
android:requestLegacyExternalStorage="true"
android:allowNativeHeapPointerTagging="false"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:icon="@drawable/icon">
<activity
@ -36,21 +43,53 @@
android:label="-- %%INSERT_APP_NAME%% --"
android:screenOrientation="unspecified"
android:launchMode="singleInstance"
android:taskAffinity=""
android:theme="@style/splashScreenTheme">
android:exported="true">
<!-- android:theme="@style/splashScreenTheme"-->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter android:label="AmneziaVPN">
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.VIEW" />
<intent-filter>
<action android:name="org.amnezia.vpn.qt.IMPORT_CONFIG" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data
android:name="android.app.extract_android_style"
android:value="minimal" />
<meta-data
android:name="android.app.background_running"
android:value="false"/>
<meta-data
android:name="android.app.arguments"
android:value="-- %%INSERT_APP_ARGUMENTS%% --" />
</activity>
<activity
android:name=".qt.CameraActivity"
android:exported="false" />
<activity
android:name=".qt.ImportConfigActivity"
android:exported="true" >
<intent-filter android:label="AmneziaVPN">
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:mimeType= "*/*"/>
<data android:mimeType="*/*"/>
<data android:host="*"/>
<data android:pathPattern=".*\\.vpn"/>
<data android:pathPattern=".*\\..*\\.vpn"/>
@ -58,14 +97,14 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\.vpn"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.vpn"/>
</intent-filter>
<intent-filter android:label="AmneziaVPN">
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:mimeType= "*/*"/>
<data android:mimeType="*/*"/>
<data android:host="*"/>
<data android:pathPattern=".*\\.cfg"/>
<data android:pathPattern=".*\\..*\\.cfg"/>
@ -73,14 +112,14 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\.cfg"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.cfg"/>
</intent-filter>
<intent-filter android:label="AmneziaVPN">
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:mimeType= "*/*"/>
<data android:mimeType="*/*"/>
<data android:host="*"/>
<data android:pathPattern=".*\\.conf"/>
<data android:pathPattern=".*\\..*\\.conf"/>
@ -88,102 +127,34 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\.conf"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.conf"/>
</intent-filter>
<!-- Application arguments -->
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments -->
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<!-- Used to specify custom system library path to run with local system libs -->
<!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
use hideSplashScreen() with a fade-out animation from Qt Android Extras to hide the splash screen when you
are done populating your window with content. -->
<!-- meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
<!-- Splash screen -->
<!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after
"applicationStateChanged(Qt::ApplicationSuspended)"
signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running -->
<!-- auto screen scale factor -->
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
<!-- auto screen scale factor -->
<!-- extract android style -->
<!-- available android:values :
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
* full - useful QWidget & Quick Controls 1 apps
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
* none - useful for apps that don't use any of the above Qt modules
-->
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
<!-- extract android style -->
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splashscreen"/>
</activity>
<service
android:name=".VPNService"
android:permission="android.permission.BIND_VPN_SERVICE"
android:process=":QtOnlyProcess"
android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
<meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>
<service
android:name=".qt.VPNPermissionHelper"
android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="true">
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
</service>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="org.amnezia.vpn.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/fileprovider"/>
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/fileprovider"/>
</provider>
</application>

View file

@ -2,7 +2,7 @@ apply plugin: 'com.github.ben-manes.versions'
buildscript {
ext{
kotlin_version = "1.4.30-M1"
kotlin_version = "1.7.22"
// for libwg
appcompatVersion = '1.1.0'
annotationsVersion = '1.0.1'
@ -20,7 +20,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.android.tools.build:gradle:7.2.1'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.21.0'
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.8.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
@ -28,12 +28,6 @@ buildscript {
}
}
repositories {
google()
jcenter()
mavenCentral()
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
@ -42,14 +36,34 @@ apply plugin: 'kotlin-kapt'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation "androidx.security:security-crypto:1.1.0-alpha03"
implementation "androidx.security:security-identity-credential:1.0.0-alpha02"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0"
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5"
implementation project(path: ':shadowsocks')
// CameraX core library using the camera2 implementation
def camerax_version = "1.2.1"
implementation("androidx.camera:camera-core:${camerax_version}")
implementation("androidx.camera:camera-camera2:${camerax_version}")
implementation("androidx.camera:camera-lifecycle:${camerax_version}")
implementation("androidx.camera:camera-view:${camerax_version}")
implementation("androidx.camera:camera-extensions:${camerax_version}")
def camerax_ml_version = "1.2.0-beta02"
def ml_kit_version = "17.0.3"
implementation("androidx.camera:camera-mlkit-vision:${camerax_ml_version}")
implementation("com.google.mlkit:barcode-scanning:${ml_kit_version}")
}
androidExtensions {
@ -61,7 +75,7 @@ android {
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qt5AndroidDir - holds the path to qt android files
* - qtAndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
*
@ -72,7 +86,11 @@ android {
compileSdkVersion androidCompileSdkVersion.toInteger()
//buildToolsVersion '28.0.3'
buildToolsVersion androidBuildToolsVersion
ndkVersion androidNdkVersion
// Extract native libraries from the APK
packagingOptions.jniLibs.useLegacyPackaging true
dexOptions {
javaMaxHeapSize "3g"
@ -81,14 +99,14 @@ android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qt5AndroidDir + '/res', 'res']
java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qtAndroidDir + '/res', 'res']
resources.srcDirs = ['resources']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
androidTest.assets.srcDirs += files("${qt5AndroidDir}/schemas".toString())
androidTest.assets.srcDirs += files("${qtAndroidDir}/schemas".toString())
}
}
@ -117,12 +135,12 @@ android {
defaultConfig {
resConfig "en"
minSdkVersion = 24
targetSdkVersion = 30
targetSdkVersion = 31
versionCode 10 // Change to a higher number
versionName "2.0.10" // Change to a higher number
javaCompileOptions.annotationProcessorOptions.arguments = [
"room.schemaLocation": "${qt5AndroidDir}/schemas".toString()
"room.schemaLocation": "${qtAndroidDir}/schemas".toString()
]
}
@ -139,6 +157,7 @@ android {
debug {
//applicationIdSuffix ".debug"
//versionNameSuffix "-debug"
minifyEnabled false
externalNativeBuild {
cmake {
arguments "-DANDROID_PACKAGE_NAME=${groupName}", "-DGRADLE_USER_HOME=${project.gradle.gradleUserHomeDir}"

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".CameraActivity">
<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View file

@ -1,11 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<array name="qt_sources">
<item>https://download.qt.io/ministro/android/qt5/qt-5.14</item>
</array>
<!-- The following is handled automatically by the deployment tool. It should
not be edited manually. -->
<!-- DO NOT EDIT THIS: This file is populated automatically by the deployment tool. -->
<array name="bundled_libs">
<!-- %%INSERT_EXTRA_LIBS%% -->
@ -19,4 +14,8 @@
<!-- %%INSERT_LOCAL_LIBS%% -->
</array>
<string name="static_init_classes"><!-- %%INSERT_INIT_CLASSES%% --></string>
<string name="use_local_qt_libs"><!-- %%USE_LOCAL_QT_LIBS%% --></string>
<string name="bundle_local_qt_libs"><!-- %%BUNDLE_LOCAL_QT_LIBS%% --></string>
<string name="system_libs_prefix"><!-- %%SYSTEM_LIBS_PREFIX%% --></string>
</resources>

View file

@ -1 +1,19 @@
pluginManagement {
repositories {
google()
mavenCentral()
jcenter()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
jcenter()
}
}
include ':shadowsocks'

View file

@ -1,21 +1,9 @@
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
//apply plugin: 'com.novoda.bintray-release'
android {
compileSdkVersion 30
defaultConfig {
@ -47,7 +35,7 @@ androidExtensions {
}
//def lifecycleVersion = '2.0.0'
//def roomVersion = '2.0.0'
def roomVersion = "2.4.3"
//def preferencexVersion = '1.0.0'
dependencies {
@ -57,15 +45,14 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0"
implementation "androidx.core:core-ktx:1.2.0"
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:2.4.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
implementation "androidx.room:room-runtime:2.2.5" // runtime
implementation "androidx.room:room-runtime:$roomVersion" // runtime
implementation "androidx.preference:preference:1.1.0"
implementation "androidx.work:work-runtime-ktx:2.3.4"
implementation "androidx.work:work-runtime-ktx:2.7.1"
implementation "androidx.browser:browser:1.3.0-alpha01"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
implementation "com.google.android.material:material:1.2.0-alpha05"
@ -77,6 +64,7 @@ dependencies {
// api "com.takisoft.preferencex:preferencex:1.0.0"
implementation 'com.takisoft.preferencex:preferencex:1.1.0'
api 'org.connectbot.jsocks:jsocks:1.0.0'
kapt "androidx.room:room-compiler:2.2.5"
kapt "androidx.room:room-compiler:$roomVersion"
kapt "androidx.lifecycle:lifecycle-compiler:2.4.0"
}

View file

@ -68,7 +68,8 @@
android:name="org.amnezia.vpn.shadowsocks.core.BootReceiver"
android:directBootAware="true"
android:enabled="false"
android:process=":QtOnlyProcess">
android:process=":QtOnlyProcess"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />

View file

@ -84,8 +84,9 @@ object Core {
fun init(app: Application, configureClass: KClass<out Any>) {
Core.app = app
configureIntent = {
PendingIntent.getActivity(it, 0, Intent(it, configureClass.java)
.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0)
PendingIntent.getActivity(it, 0,
Intent(it, configureClass.java).setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
}
if (Build.VERSION.SDK_INT >= 24) { // migrate old files
@ -99,7 +100,6 @@ object Core {
// overhead of debug mode is minimal: https://github.com/Kotlin/kotlinx.coroutines/blob/f528898/docs/debugging.md#debug-mode
System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON)
WorkManager.initialize(deviceStorage, Configuration.Builder().build())
// handle data restored/crash
if (Build.VERSION.SDK_INT >= 24 && DataStore.directBootAware &&

View file

@ -3,7 +3,7 @@ package org.amnezia.vpn;
import android.content.Context;
import android.app.KeyguardManager;
import android.content.Intent;
import org.qtproject.qt5.android.bindings.QtActivity;
import org.qtproject.qt.android.bindings.QtActivity;
import static android.content.Context.KEYGUARD_SERVICE;

View file

@ -99,7 +99,7 @@ object NotificationUtil {
val mainActivityName = "org.amnezia.vpn.qt.VPNActivity"
val activity = Class.forName(mainActivityName)
val intent = Intent(service, activity)
val pendingIntent = PendingIntent.getActivity(service, 0, intent, 0)
val pendingIntent = PendingIntent.getActivity(service, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
// Build our notification
sNotificationBuilder?.let {
it.setSmallIcon(org.amnezia.vpn.R.drawable.ic_amnezia_round)

View file

@ -31,6 +31,31 @@ class OpenVPNThreadv3(var service: VPNService): ClientAPI_OpenVPNClient(), Runna
private var mAlreadyInitialised = false
private var mService: VPNService = service
private var bytesInIndex = -1
private var bytesOutIndex = -1
init {
findConfigIndicies()
}
private fun findConfigIndicies() {
val n: Int = stats_n()
for (i in 0 until n) {
val name: String = stats_name(i)
if (name == "BYTES_IN") bytesInIndex = i
if (name == "BYTES_OUT") bytesOutIndex = i
}
}
fun getTotalRxBytes(): Long {
return stats_value(bytesInIndex)
}
fun getTotalTxBytes(): Long {
return stats_value(bytesOutIndex)
}
override fun run() {
val config: ClientAPI_Config = ClientAPI_Config()

View file

@ -41,6 +41,7 @@ import java.io.Closeable
import java.io.File
import java.io.FileDescriptor
import java.io.IOException
import java.lang.Exception
import android.net.VpnService as BaseVpnService
@ -153,31 +154,6 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
private var flags = 0
private var startId = 0
private lateinit var mMessenger: Messenger
internal class ExternalConfigImportHandler(
context: Context,
private val serviceBinder: VPNServiceBinder,
private val applicationContext: Context = context.applicationContext
) : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
IMPORT_COMMAND_CODE -> {
val data = msg.data.getString(IMPORT_CONFIG_KEY)
if (data != null) {
serviceBinder.importConfig(data)
}
}
else -> {
super.handleMessage(msg)
}
}
}
}
fun init() {
if (mAlreadyInitialised) {
return
@ -216,13 +192,6 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
override fun onBind(intent: Intent): IBinder {
Log.v(tag, "Aman: onBind....................")
if (intent.action != null && intent.action == IMPORT_ACTION_CODE) {
Log.v(tag, "Service bind for import of config")
mMessenger = Messenger(ExternalConfigImportHandler(this, mBinder))
return mMessenger.binder
}
Log.v(tag, "Regular service bind")
when (mProtocol) {
"shadowsocks" -> {
when (intent.action) {
@ -306,11 +275,20 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
return mConnectionTime
}
var isUp: Boolean
var isUp: Boolean = false
get() {
return currentTunnelHandle >= 0
return when (mProtocol) {
"openvpn" -> {
field
}
else -> {
currentTunnelHandle >= 0
}
}
}
set(value) {
field = value
if (value) {
mBinder.dispatchEvent(VPNServiceBinder.EVENTS.connected, "")
mConnectionTime = System.currentTimeMillis()
@ -319,17 +297,52 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
mBinder.dispatchEvent(VPNServiceBinder.EVENTS.disconnected, "")
mConnectionTime = 0
}
val status: JSONObject
get() {
val deviceIpv4: String = ""
val status = when (mProtocol) {
"openvpn" -> {
if (mOpenVPNThreadv3 == null) {
Status(null, null, null, null)
} else {
val rx = mOpenVPNThreadv3?.getTotalRxBytes() ?: ""
val tx = mOpenVPNThreadv3?.getTotalTxBytes() ?: ""
Status(
rx.toString(),
tx.toString(),
if (mConfig!!.has("server")) { mConfig?.getJSONObject("server")?.getString("ipv4Gateway") } else {""},
if (mConfig!!.has("device")) { mConfig?.getJSONObject("device")?.getString("ipv4Address") } else {""}
)
}
}
else -> {
Status(
getConfigValue("rx_bytes"),
getConfigValue("tx_bytes"),
if (mConfig!!.has("server")) { mConfig?.getJSONObject("server")?.getString("ipv4Gateway") } else {""},
if (mConfig!!.has("server")) {mConfig?.getJSONObject("device")?.getString("ipv4Address") } else {""}
)
}
}
return JSONObject().apply {
putOpt("rx_bytes", getConfigValue("rx_bytes"))
putOpt("tx_bytes", getConfigValue("tx_bytes"))
putOpt("endpoint", mConfig?.getJSONObject("server")?.getString("ipv4Gateway"))
putOpt("deviceIpv4", mConfig?.getJSONObject("device")?.getString("ipv4Address"))
putOpt("rx_bytes", status.rxBytes)
putOpt("tx_bytes", status.txBytes)
putOpt("endpoint", status.endpoint)
putOpt("deviceIpv4", status.device)
}
}
data class Status(
var rxBytes: String?,
var txBytes: String?,
var endpoint: String?,
var device: String?
)
/*
* Checks if the VPN Permission is given.
* If the permission is given, returns true
@ -677,6 +690,7 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
private fun startOpenVpn() {
mOpenVPNThreadv3 = OpenVPNThreadv3(this)
Thread({
mOpenVPNThreadv3?.run()
}).start()
@ -874,45 +888,4 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
class CloseableFd(val fd: FileDescriptor) : Closeable {
override fun close() = Os.close(fd)
}
fun saveAsFile(configContent: String?, suggestedFileName: String): String {
val rootDirPath = cacheDir.absolutePath
val rootDir = File(rootDirPath)
if (!rootDir.exists()) {
rootDir.mkdirs()
}
val fileName = if (!TextUtils.isEmpty(suggestedFileName)) suggestedFileName else "amnezia.cfg"
val file = File(rootDir, fileName)
try {
file.bufferedWriter().use { out -> out.write(configContent) }
return file.toString()
} catch (e: Exception) {
e.printStackTrace()
}
return ""
}
fun shareFile(attachmentFile: String?) {
try {
val intent = Intent(Intent.ACTION_SEND)
intent.type = "text/*"
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val file = File(attachmentFile)
val uri = FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.fileprovider", file)
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val createChooser = Intent.createChooser(intent, "Config sharing")
createChooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(createChooser)
} catch (e: Exception) {
Log.i(tag, e.message.toString())
}
}
}

View file

@ -32,7 +32,7 @@ class VPNServiceBinder(service: VPNService) : Binder() {
const val resumeActivate = 7
const val setNotificationText = 8
const val setFallBackNotification = 9
const val shareConfig = 10
const val importConfig = 11
}
/**
@ -75,13 +75,14 @@ class VPNServiceBinder(service: VPNService) : Binder() {
ACTIONS.resumeActivate -> {
// [data] is empty
// Activate the current tunnel
try {
mResumeConfig?.let { this.mService.turnOn(it) }
Log.i(tag, "resume activate")
try {
mResumeConfig?.let { this.mService.turnOn(it) }
} catch (e: Exception) {
Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}")
}
return true
}
}
ACTIONS.deactivate -> {
// [data] here is empty
@ -90,6 +91,7 @@ class VPNServiceBinder(service: VPNService) : Binder() {
}
ACTIONS.registerEventListener -> {
Log.i(tag, "register: start")
// [data] contains the Binder that we need to dispatch the Events
val binder = data.readStrongBinder()
mListener = binder
@ -136,18 +138,21 @@ class VPNServiceBinder(service: VPNService) : Binder() {
return true
}
ACTIONS.shareConfig -> {
val byteArray = data.createByteArray()
val json = byteArray?.let { String(it) }
val config = JSONObject(json)
val configContent = config.getString("data")
val suggestedName = config.getString("suggestedName")
ACTIONS.importConfig -> {
val buffer = data.readString()
val filePath = mService.saveAsFile(configContent, suggestedName)
Log.i(tag, "save file: $filePath")
val obj = JSONObject()
obj.put("config", buffer)
mService.shareFile(filePath)
return true
val resultString = obj.toString()
Log.i(tag, "Transact import config request")
if (mListener != null) {
dispatchEvent(EVENTS.configImport, resultString)
} else {
mImportedConfig = resultString
}
}
IBinder.LAST_CALL_TRANSACTION -> {
@ -179,6 +184,8 @@ class VPNServiceBinder(service: VPNService) : Binder() {
val data = Parcel.obtain()
data.writeByteArray(payload?.toByteArray(charset("UTF-8")))
it.transact(code, data, Parcel.obtain(), 0)
} else {
Log.i(tag, "Dispatching event: binder NOT alive")
}
}
} catch (e: DeadObjectException) {
@ -197,23 +204,7 @@ class VPNServiceBinder(service: VPNService) : Binder() {
const val statisticUpdate = 3
const val backendLogs = 4
const val activationError = 5
const val configImport = 6
}
fun importConfig(config: String) {
val obj = JSONObject()
obj.put("config", config)
val resultString = obj.toString()
Log.i(tag, "Transact import config request")
if (mListener != null) {
Log.i(tag, "binder alive")
dispatchEvent(EVENTS.configImport, resultString)
} else {
Log.i(tag, "binder NOT alive")
mImportedConfig = resultString
}
const val permissionRequired = 6
const val configImport = 7
}
}

View file

@ -3,8 +3,8 @@ package org.amnezia.vpn.qt
import android.content.res.Configuration
import org.amnezia.vpn.shadowsocks.core.Core
import org.amnezia.vpn.shadowsocks.core.VpnManager
import org.qtproject.qt5.android.bindings.QtActivity
import org.qtproject.qt5.android.bindings.QtApplication
import org.qtproject.qt.android.bindings.QtActivity
import org.qtproject.qt.android.bindings.QtApplication
import android.app.Application
class AmneziaApp: Application() {

Some files were not shown because too many files have changed in this diff Show more